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
    ]
  )
)
Start the conversation