Swift And Pizza
Swift And Pizza

April 2024
M T W T F S S
1234567
891011121314
15161718192021
22232425262728
2930  

Categories


Flux pattern in Swift

An easy approach to a unidirectional data flow.

Daniele BogoDaniele Bogo

In the bucket of architectural patterns we see and often use, there’s one in particular I want to write about since I joined Automattic . Its name is Flux.

But what is Flux?

It’s a unidirectional data flow application architecture used by Facebook for building client-side web applications. It complements React’s composable view components.
You can find more in depth information about it here.

Structure and unidirectional flow

As mentioned before Flux has a unidirectional data flow. But what does it mean?

Unidirectional data flow

As you can see in the diagram above the pattern is mainly structured in 4 components:

An Action, originated from a user interactions with the view, is sent to the Dispatcher, central hub observed by the Store. After the action is dispatched and executed, the Store will emit a change to alert the view the data layer has changed.

The Store is what holds the data and business logic about a specific domain in the application. It’s the only component allowed to modify data in response to a dispatched Action. It always decides when to initiate a network request, either when an Action modifies the data.

How it works in real life?

In Automattic we use this pattern a lot, specially in our WordPress iOS application.
To make our life easy we have built a library called WordPressFlux to help us with the pattern implementation. It also provides two versions of a Store object:

That said let’s see how Flux works in an iOS application. The first step is define the Store and its State.

struct InfoStoreState { 
 
	var status: FetchingStatus = .idle

	var sections: [InfoSection] = []

}

class InfoStore<Service: RemoteService>: StatefulStore<InfoStoreState> {
	private weak var service: Service?

	init(service: Service) {
		self.service = service

		super.init(initialState: InfoStoreState())

	}

	override func onDispatch(_ action: Action) {

		guard let action = action as? InfoStoreAction else { 

			return
		}
		switch action {

		case .fetch:

			fetchInfo()

		}
	}

	
func getSections() -> [InfoSection] {
		return state.sections
	}
}

Then we need to define all the actions that can be performed

enum InfoStoreAction: Action {

    case fetch

}


InfoStoreState is the entity used to store the data for this specific domain. The InfoStore, initialized with the State, observes the global dispatcher and executes an InfoStoreAction in

override func onDispatch(_ action: Action) {

    ...

}

Dispatching an Action is very easy

store.onDispatch(InfoStoreAction.fetch)

If a dispatched and executed Action modifies the data layer (State) the Store needs to emit that change to alert the View.
To do that the Store can use the method

emitChange()

or use the helper method provided by the StatefulStore (or QueryStore) to group several state changes into the same change event

transaction { state in

    state.sections = result.getSuccess()?.sections ?? []

    state.status = .fetchingCompleted(error: result.getError())

}

Bind the View

To bind the view when the Store State changes I can use a view model implementing the Observable protocol the library provides.

The view model will keep a reference to the Store, its Receipt and will observe the Store State changes

init(store: InfoStore<Service>) {

    self.store = store

    storeReceipt = store.onStateChange { [weak self] (_, state) in

        switch state.status {

        case .idle:

            self?.state = .stationary

        case .fetching:

            self?.state = .loading

        case .fetchingCompleted(let error):

            self?.state = .completed(error == nil)

        }

    }

}

Having the view model compliant to the Observable protocol makes its implementation very easy

receipt = viewModel.onChange { [unowned self] in

    self.updateView()

}

I made a small example app you can use to play with Flux

Where to go from here

This pattern can be used with SwiftUI and Combine. I’m working on an example to show you how Flux matches perfectly our expectations.

Stay Tuned!

Mobile Wrangler aka iOS Software Engineer | Pizza lover

Comments 0
There are currently no comments.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from Swift And Pizza

Subscribe now to keep reading and get access to the full archive.

Continue reading