Shared global state with infinite derived children

State that needs to be accessible through every layer of your application is best handled as a dependency in the environment that has synchronous access to the playing id, as well as endpoints for playing and stopping:

struct PlayingClient {
  var id: () -> String?
  var play: (String) -> Effect<Never, Never>
  var stop: () -> Effect<Never, Never>
}

Then a live version of this client could manage this state:

extension PlayingClient {
  static var live: Self {
    var id: String?
    return Self(
      id: { id },
      play: { newId in 
        .fireAndForget { id = newId } 
      },
      stop: { 
        .fireAndForget { id = nil } 
      }
    )
  }
}

And then a reducer can use this dependency to play, stop or figuring out if it is currently playing:

let reducer: Reducer<State, Action, Environment> = .init { state, action, environment in
  switch action {
  case .onAppear:
    state.isPlaying = state.id == environment.playingClient.id()
    return .none

  case .play(let id):
    return environment.playingClient.play(id).fireAndForget()

  case .stop:
    return environment.playingClient.stop().fireAndForget()
  }
}

And once you've done all of this you will be in a good position to write tests with a test version of the PlayingClient.

6 Likes