Getting Started With The Composable Architecture

Learn how to structure your iOS app with understandable and predictable state changes using Point-Free’s The Composable Architecture (TCA) framework. By David Piper.

Leave a rating/review
Download materials
Save for later

With SwiftUI and Combine popping up in more and more apps, managing state is becoming more important. The Composable Architecture (TCA) is a framework providing many useful tools. It helps to structure your app with understandable and predictable state changes.

TCA focuses on state management, composition and testing. It’s developed by Brandon Williams and Stephen Celis from Point-Free. They have numerous videos providing information about functional programming and Swift development.

In this tutorial, you’ll create an app that shows the latest public GitHub repositories of Additionally, it presents user information about the GitHub account. You’ll learn about:

  • State management and how it helps you create better apps.
  • Developing and testing features in isolation.
  • Managing dependencies and side effects in an understandable way.
  • Which tools the Composable Architecture offers to help you structure your app.

This tutorial assumes you’re already familiar with SwiftUI and Combine so that you can dive right into the Composable Architecture.

Note: Ideas behind TCA framework are very close to finite-state machines (FSMs) or finite-state automata (FSA) and the state design pattern. You may find it useful to familiarize yourself with these concepts in conjunction with reading this tutorial — see the Where to Go From Here? section for some references.

Getting Started

Start by downloading the project materials using the Download Materials button at the top or bottom of this tutorial. The starter project RepoReporter already includes the Composable Architecture Swift package.

Build and run the starter project. It will look like this:

Start project of RepoReporter, all three screens

The user feature is already implemented, but the rest looks a little bit empty. In this tutorial, you’ll complete the repository feature using the Composable Architecture.

The repository feature provides information like description and number of stars and forks. It also allows you to mark your favorite repositories to revisit them later.

Exploring the Composable Architecture

TCA focuses on different aspects around developing apps of different size and complexity. It offers concepts to solve various problems, including:

  • State management: Each app consists of some sort of state. TCA offers a concept to manage and share state.
  • Composition: This enables you to develop smaller features in isolation and compose them together to form the whole app.
  • Side effects: These are often hard to understand and test. TCA tries to change this by defining a way to handle them.
  • Testing: This is always important and TCA makes it easy to accomplish.
  • Ergonomics: A framework is available that provides a convenient API to implement all components.

Understanding the Components of TCA

An app built with TCA consists of five main components that help to model your app:

  • State: Often, a collection of properties represents the state of an app or a feature spread over many classes. TCA places all relevant properties together in a single type.
  • Actions: An enumeration including cases for all events that can occur in your app, e.g., when a user taps a button, when a timer fires or an API request returns.
  • Environment: A type wrapping all dependencies of your app or feature. For example, these can be API clients with asynchronous methods.
  • Reducer: A function that uses a given action to transform the current state to the next state.
  • Store: A place your UI observes for changes and where you send actions. Based on these actions, it runs reducers.

You might be wondering why you would use the Composable Architecture. There are many advantages:

  • The data flow through the different components is clearly defined and unidirectional. This makes it easy to follow and understand.
  • The environment contains all dependencies. You can understand and manage connections to the outside world from one single place. It’s possible to switch a live environment with a development or test environment. This allows you to configure or mock your dependencies without much effort.
  • By composing separate features together, each feature can be planned, built and tested on its own. Thus, using TCA can change the way you work on apps, allowing you to focus on one part of the app at a time and even run it in isolation.
  • Only reducers transform the state by processing actions. Thus, testing a feature boils down to running the reducer with actions and comparing the resulting state with the expectation.

Using the Composable Architecture

That sounds like a lot to do, but don’t worry, the Composable Architecture is a framework that makes it easy to get started. :]

The framework is distributed via the Swift Package Manager. You can include it in your project by adding a new Swift Package. However, the starter project already includes this framework, so you can start right away.

Modeling States and Actions

You’ll start by adding a list of repositories to the first tab of the starter project. You’ll do this using TCA’s state and actions, good starting points to dip your toes into using TCA.

Open RepositoryFeature.swift, where you find RepositoryState, RepositoryAction, RepositoryEnvironment and repositoryReducer.

These are the components you need to create for a feature. The store is already provided by the framework, so no need to create a new one.

RepositoryState defines the state of the repository feature in a single place. To show repositories and mark them as your favorite, you need two properties. Add these two inside RepositoryState:

var repositories: [RepositoryModel] = []
var favoriteRepositories: [RepositoryModel] = []

As the property names suggest, you’ll store all repositories in these two arrays.

Next, you need to define which actions can happen inside the repositories tab. Add these to RepositoryAction:

// 1
case onAppear
// 2
case dataLoaded(Result<[RepositoryModel], APIError>)
// 3
case favoriteButtonTapped(RepositoryModel)

Here’s a description of each action:

  1. RepoReporter loads new data when a user selects the Repositories tab and the view appears. Thus, there needs to be an action to send the API request.
  2. The second action represents the event that the API request finished, either with a list of repositories or an error.
  3. Finally, you need another action for when a user taps the Favorite button on a repository.