The Startup
Published in

The Startup

The Composable Architecture — Visualize Data Flows With a Diagram

Welcome back to my SwiftUI tutorial series. In previous parts, I have proven that right now is the time for SwiftUI to shine. And how The (Swift) Composable Architecture gives me the confidence to conclude this. This time, we are going to learn how it actually works through my visualized diagram.

This article will summarize the basics and the mechanism at a high-level. There is plenty of things that need to learn in detail. And I highly recommend you check the PointFree team out.

Image credit: Georgina Guthrie from cacoo.com

Foundation components

View

This is where the UI elements display on the screen like in vanilla SwiftUI. It still complies with one of the most important SwiftUI principles: A view is a function. It means every UI’s change must depend on a corresponding state.

However, the most special and different part of this framework is that:
- In a view, we will not handle any action at all
-> That means we can clean up all the imperative code in the closures of buttons or viewAppear signal, etc. Views, now, are completely declarative as they should be. For example:

// MARK: - Propertiers
@State private var email = ""
@State private var password = ""

// MARK: - View
var body: some View {
VStack() {
Text("https://passiondev.medium.com/ is original")
Text("If you see this article from other pages. They are thieves")
TextField("Email", text: self.$email)
TextField("Password", text: self.$password)

Button("Sign In") {
// do some declarative code here
}
}
}

So, where will the business code be processed? Let’s continue to find the answer. (Action section)

State

This is the component that stores the state of:
- a view
- a coordinator for multiple views
- even the entire App.

`State` is a Struct type. Basically, the responsibility of this component is to store all the states related to a corresponding piece of UI on a higher level than local.
For example, for States whose scope is only contained in a view, we don’t need to be stored in these State components. Simply creating a private one inside the view is enough. So what is kind of this state? Let me give you a hint: isPopupDismissed.

struct LoginState {
var email = ""
var password = ""
var isLoginButtonTapped = false
}

Action

This is an enum type where all actions, that are potential to appear in views or coordinators are stored. Since this is the enum type, in its cases we can give additional parameters. From there, using it will be easier than ever.

enum LoginAction {
case emailTextFieldChanged(text: String)
case passwordTextFieldChanged(text: String)
case didLoginButtonTap
}

Inside the closure, we just need to pass the action we want

Button("Sign In") {
store.send(.didLoginButtonTap)
}

So beautiful!!!

Reducer

This is the brain of this architecture. The reducer itself is formed from many techniques, functions that you have to go deep into the PointFree series to understand. In this article, I would just like to introduce briefly.

This is where you will implement the business logic code for each received action. After that, update a certain state accordingly. See the example below to figure it out

let loginReducer: Reducer<LoginState, LoginAction, LoginEnvironment> = Reducer { state, action, environment in 
switch state {
case .emailTextFieldChanged(let text):
state.email = text
case .passwordTextFieldChanged(let text):
state.password = text
case .didLoginButtonTap:
state.isLoginButtonTapped = true
}
return Effect()
}

Side Effects

Pay attention to the last line of the code above. What is that Effect? Its full name is Side Effects. The name says it all. This is the component handling the side effects that occur in each action.

The effect here covers a lot of cases, but the common use-cases are to send:
- asynchronous signals
- implicit actions mainly due to logic code, not from user interaction.

Environment

If you are really interested in the system, you will definitely feel like missing something. Yes, Dependencies (a.k.a collaborators). This environment is where all of that is located, including network services, database services, Date-Formatter, etc. It’s a good practice to create a separate component that is responsible for managing dependencies like this. It will keep other components focused on their own responsibilities.

Store

Last but not least, Store. This can be considered as a Composition Root. It contains all components above. Although nothing really special, I still recommend that you should read more carefully on the page of the PointFree team.

Data flow

Okay, it’s time to have fun. Sorry for the long section but I tried to narrow it down as much as possible. Do you know how to color? Let's go.

First, I am going to show you an overview of the architecture. Then we will start over. Here it is.

Image credit: Author — Duy Bui

Well, a bunch of arrows, haha. Don't worry, just follow me.

Step 1: Starting point — Build a view you need

The first thing you need to do is to create a view you need with all cases. Whenever you encounter any child View needing a corresponding state, just define all as private ones.

Next step, you need to analyze which ones should be managed by us, which ones do not (live inside that view is enough). Then will move all outside states to our State component.

Image credit: Author — Duy Bui

Step 2: Use Store to send actions

Each View will have a corresponding Store to manage. So, as said above (Action part), inside all closures such as Button’s completion handlers, viewAppear, so on, we just need to send the action we aim to.

Image credit: Author — Duy Bui

Step 3: Handle business logic code at Reducer

Nothing much here since I mentioned almost. Basically and ideally, this is the only place where you see the imperative code. If you do imperative code somewhere else, that means you are going in the wrong direction.

Image credit: Author — Duy Bui

Step 4: Handle Side Effects

Remember to take into account all potential side-effects. For instance, if you send a request, the effect will be a signal telling us whether the communication with the server success. A side-effect also sends an action as the view does.

Image credit: Author — Duy Bui

Usually, we will need dependencies to support us at this point and we just inject them through Environment.

Image credit: Author — Duy Bui

Step 5: Update the State to update the View

And of course, for each action, we will update a state property as we want. When the State is updated, the View will be automatically kept in sync.

Image credit: Author — Duy Bui

Conclusion

As mentioned above, this article mainly analyzes the basic components as well as the flow of data — thereby helping you to approach this framework easier and faster. If going into detail, we will come across many other complicated components such as ViewWithStore or combine reducers, etc. In the next articles, I will apply this architecture to an actual project. From there, if encountering any new components, I will analyze them carefully.

Stay tuned and again, let me know your thoughts in the comments. Also, don’t forget to share this article with your friends, if you’ve found it useful. Looking forward to all opinions.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Duy Bui

Duy Bui

Product & Project management + Business administration and operation