Nesting/Sharing State across Modules

I've been enjoying using the Swift Composable Architecture over the last year. It comes with a promising sell and it mostly pulls it off. Thing is, I'm having a little trouble sharing state across my apps and am not sure the best way forward.

I've created a little example project and I'm wondering what is the best way to go about implementing the minimum amount of code to get the example project working.

The app is a fictional app where we have four Feature Modules that depend on some shared state to derive their own state. Oh, and when I refer to a Module, I mean a collection of related State, Action, Environment and Reducer objects.

A general gist of the app is that we have a TabBar with two tabs. A Home Tab and an Account Tab. The Home Tab must show a list of the last 10 messages received by the WebSocket. The Account Tab must show the total number of messages received by the WebSocket.

The Modules are split out into:

AppFeature Module
    Responsible for the App's root most view and TabBar
WebSocketFeature Module
    Responsible for managing the (fake) webSocket connection and state
AccountFeature Module
    Responsible for showing the total number of messages sent
TimelineFeature Module
    Responsible for showing a timeline of TimelineItems in a list and keeping track of the id of the last selected item so we can render the text in a different color.

Right now, on the initial commit, I've fleshed out the initial version of this where each module works in their own right except from the AppFeature Module. In order to get the AppFeature, and thus the app, working, we'll have to somehow share the state of the WebSocket and derive the related parts of the AccountState and TimelineState.

Now, the question is, what is the most ideal way to get this example project working?

I've added and described three possible solutions to this problem, but they all have different downsides. I'm hoping that I'm missing something simple, and that one of you have a better solution that I! :grinning:

Please check out a more detailed brief of the problem in the repo. I'm looking for ideas and input, so any comment are appreciated.

2 Likes

So, I may be misunderstanding and I did not checkout the repo. However to me it seems that I would make a WebSocketClient Module that would house the interface.

public struct WebSocketClient {
  // Receive a new message and store in some `State`.
  var receiveMessage: (Message) -> Effect<Void> // or whatever
}

You would then have a WebSocketClientLive Module

extension WebSocketClient {
  // Create the live implementation for the application.
  public static func live(connection: WebSocketConnection) -> WebSocketClient {
    return WebSocketClient(
      receiveMessage: { message in
        ...
      }
  )
}

The AppFeature Module would be responsible for setting up the WebSocketClient connection and live implementation and hold onto it in their Environment. The AppFeatureState object would have a list of all the messages that could then be scoped down to their child views. Therefore the child Modules would only need to be able to handle the Message objects.

Hopefully that makes some sense for your use case.

I guess another way to say it is that the WebSocketFeature really seems like something that should go into the Environment (like a database / api client would) and not really be responsible for any State, only starting / stopping / re-starting the connection, fetching messages, and / or receiving messages once a connection is made.

The State should be housed, likely high up in the AppFeatureState and house all the messages. This could then be scoped down into the child views / states / features. So the AccountFeature would receive all the messages and the HomeTab could be scoped down to only receive the first 10 messages.

Terms of Service

Privacy Policy

Cookie Policy