Issue #96
Another cool thing about ios-oss is how it manages dependencies. Usually you have a lot of dependencies, and it’s good to keep them in one place, and inject it to the objects that need.
The Environment is simply a struct that holds all dependencies throughout the app
/**
A collection of **all** global variables and singletons that the app wants access to.
*/
public struct Environment {
/// A type that exposes endpoints for fetching Kickstarter data.
public let apiService: ServiceType
/// The amount of time to delay API requests by. Used primarily for testing. Default value is `0.0`.
public let apiDelayInterval: DispatchTimeInterval
/// A type that exposes how to extract a still image from an AVAsset.
public let assetImageGeneratorType: AssetImageGeneratorType.Type
/// A type that stores a cached dictionary.
public let cache: KSCache
/// ...
}
Then there’s global object called AppEnvironment that manages all these Environment
in a stack
public struct AppEnvironment {
/**
A global stack of environments.
*/
fileprivate static var stack: [Environment] = [Environment()]
/**
Invoke when an access token has been acquired and you want to log the user in. Replaces the current
environment with a new one that has the authenticated api service and current user model.
- parameter envelope: An access token envelope with the api access token and user.
*/
public static func login(_ envelope: AccessTokenEnvelope) {
replaceCurrentEnvironment(
apiService: current.apiService.login(OauthToken(token: envelope.accessToken)),
currentUser: envelope.user,
koala: current.koala |> Koala.lens.loggedInUser .~ envelope.user
)
}
/**
Invoke when we have acquired a fresh current user and you want to replace the current environment's
current user with the fresh one.
- parameter user: A user model.
*/
public static func updateCurrentUser(_ user: User) {
replaceCurrentEnvironment(
currentUser: user,
koala: current.koala |> Koala.lens.loggedInUser .~ user
)
}
public static func updateConfig(_ config: Config) {
replaceCurrentEnvironment(
config: config,
koala: AppEnvironment.current.koala |> Koala.lens.config .~ config
)
}
// Invoke when you want to end the user's session.
public static func logout() {
let storage = AppEnvironment.current.cookieStorage
storage.cookies?.forEach(storage.deleteCookie)
replaceCurrentEnvironment(
apiService: AppEnvironment.current.apiService.logout(),
cache: type(of: AppEnvironment.current.cache).init(),
currentUser: nil,
koala: current.koala |> Koala.lens.loggedInUser .~ nil
)
}
// The most recent environment on the stack.
public static var current: Environment! {
return stack.last
}
}
Then whenever there’s event that triggers dependencies update, we call it like
self.viewModel.outputs.logIntoEnvironment
.observeValues { [weak self] accessTokenEnv in
AppEnvironment.login(accessTokenEnv)
self?.viewModel.inputs.environmentLoggedIn()
}
The cool thing about Environment
is that we can store and retrieve them
// Returns the last saved environment from user defaults.
public static func fromStorage(ubiquitousStore: KeyValueStoreType,
userDefaults: KeyValueStoreType) -> Environment {
// retrieval
}
And we can mock in tests
AppEnvironment.replaceCurrentEnvironment(
apiService: MockService(
fetchDiscoveryResponse: .template |> DiscoveryEnvelope.lens.projects .~ [
.todayByScottThrift,
.cosmicSurgery,
.anomalisa
]
)
)