Flux pattern in Swift

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?

diagrams_flux_01

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

  • Action
  • Dispatcher
  • Store
  • View

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.

diagrams_flux_02

 

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:

  • StatefulStore: a store that holds all of its internal state in a generic State property.
  • QueryStore: a mechanism for Stores to keep track of queries by consumers and perform operations depending of those.

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 = .idlevar 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!

Share

Leave a Reply

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

%d bloggers like this: