World Flux Architecture in iOS (part 2)

PART 1

Perform a feature using Flux pattern

Learn more about the implementation of the features built into Flux pattern.

In an example in the next two sections, we will use a feature that was used in the production of the PlanGrid application. This feature allows users to filter the notes in the blueprint:

The feature we mentioned will be in the left-side popover section of the screenshot.

Step 1: Specify state

Usually, I start implementing a new feature by specifying the appropriate state for that feature. That state will show everything the UI needs to know to render an instance of a feature.

Our example will show the state of the filter comments:

State includes a list of different filters, a new filter group and a boolean flag, indicating whether any filters are active or not.

This state is adapted to the UI requirements. List of rendered filters in a table view. Filtered filter groups are used to display / hide details about a particular filter group selected. Flag isFiltering is used to determine whether to enable or disable a button to delete all filters in the UI.

Step 2: Format the actions

After formatting the state for a certain feature, I often think about different state mutations in the next step. In the Flux architiecture state mutations are modeled as actions to describe what state change is expected to occur. For annotation filtering, the list of actions is quite short:

Without the ability to deeply understand the feature, the list of actions must be easy to understand, indicating which states have initiated state transitions. One of the benefits of Flux architecture is that this list of actions has a comprehensive view of all state changes that can be enabled for this specific feature.

Step 3: Execute feedback for Actions in Store

In this step, we implement the core business logic of a feature. I personally tend to implement this step using TDD. The implementation of a store can be summarized as follows:

  1. Register the store with the dispatcher for all the actions dispatcher is interested in. A current example includes all AnnotationFilteringActions .
  2. Execute a handler called for each individual action
  3. In the handler, perform the necessary business logic and update the state until it is completed

The following specific instance will help us know how AnnotationFilterStore handles toggleFilterAction :

This example intentionally is not simplified. So split them up. handleToggleFilterAction is enabled whenever the ToggleFilterAction is sent. ToggleFilterAction brings information about which specific filer should be switched mode.

As an almost essential step first when implementing this business logic, the above method will simply convert the filter by converting the filter.enabled value.

After that, we implemented several custom business logic for this feature. When working with defined filters to filter the problem annotations, we encounter a few cases that need to activate issueTypeFilter . You do not need to deeply analyze the PlanGrid feature, but this method can encapsulate any business logic related to conversion filters.

At the end of the method, we will call the _applyFilter() method. This is a shared method and used by many action handlers:

Call self._annotationFilterService.applyFilter() will actually enable filtering of the comments displayed on a sheet. Logical filtering is quite complicated, so you can convert this logic to a separate, dedicated type.

The role of each store is to provide UI-compatible state information and become a coordination point to update the state. This does not mean that the entire business logic needs to be implemented in its own store.

The last step in each action handler is to update the state. In the _applyFilter() method, we update the state isFiltering value by checking whether any filters are active.

There is one important thing to note about this store: you would like to see an additional update of the filter values ​​stored in AnnotationFilterState . Overall, this is how to implement our stores, but this implementation is quite special.

Because filters are stored in AnnotationFilterState requires interaction with our existing Objective-C code, we have decided to model filters into classes. Then, they will be the reference types, store, and annotated UI filters sharing 1 reference to the same instances. Accordingly, all changes to filters in the store are automatically visible by the UI. Overall, we try to avoid this by using only value types in the state structs – but this is a blog post about the real Flux world and in this particular case, compromise the creation of Objective-C interop easily. More has been accepted.

If filters are value types, we need to transfer the updated filters to our state property so that the UI can observe the changes. Because we are using reference types here, we will instead perform an update on phantom state.

Switching to _state property will start the UI update mechanism.

We have deeply analyzed the implementation details, so we want to end the section with reminders of high-level store tasks:

  1. Register store with the dispatcher of all actions dispatcher is interested in. In the recent example, that would be all AnnotationFilteringActions .
  2. Implement 1 handler will be called for each single action
  3. In the handler, perform the necessary business logic and update the state after completion

Next, we will talk about how Ui receives state updates from the store.

Step 4: Mount UI with Store

One of the core Flux concepts is an automatic UI update that will be activated any time the state update occurs. This ensures that the UI always shows the latest state and removes any code required to manually maintain these updates. This step is similar to the constraints of a View with ViewModel in architecture MVVM.

There are many ways to do this – in PlanGrid we decided to use Reactive. The store to store can provide an observable state property. The code below will show how AnnotationFilterStore executes this pattern:

Property _state is used in store to transform state. Customers who want to register for a store will use property state . This allows store registrants to receive state updates but does not allow them to convert live state updates (state conversion only occurs through actions!).

In initializer, the internal property is simply observed to be bound to the external signal producer:

Now, any update of _state will automatically send the latest state value via the signal producer stored in the state .

The left part is the code, ensuring that the UI updates whenever new state values ​​are generated. This may be one of the hardest parts when starting with the Flux pattern in iOS. On his website, Flux works very well with Facebook's React framework. React is designed for a special situation: re-render the UI after the state updates without requiring any additional code .

When working with UIKit, we are not so comfortable, instead we need to implementcasc manual UI updates. The key point is that we have built several components that provide 1 React like API for UITableView and UICollectionView , we will consider them later.

If you want to study these components, check out the recent talk I have made , as well as the 2 GitHub repositories that come with it ( AutoTable , UILib ).

Take a look at some real world code (in this case, the code has been simplified a bit) from the caption filtering feature. This code appears in AnnotationFilterViewController :

In codebase, we follow a convention that each view controller has a _bind method called from within viewWillAppear . This _bind method is responsible for registering the state of the store and providing the UI update code when state changes occur.

Because we need to implement partial UI updates and cannot depend on a framework like React, this method usually has descriptive code how a certain state updates maps into a UI update. ReactiveCool is very handy because it provides many different operators ( skipUntil , take , map …) to support setting these relationships more easily. If you haven't used a Reactive library before, this code can be a bit confusing – but you can study a small part of ReactiveCocoa that we use.

The first line in the _bind method example above ensures that table view is updated whenever a state update occurs. We use the ReactiveCocoa ignoreNil() operator to ensure that we do not start updates for a blank state. After that, we used the map operatore to delineate the latest state from store to 1 describing the display of table view.

Chart outline will be done in the method annotationFilterViewProvider.tableViewModelForState This is when the UIKit wrapper like React is customizable.

I will not study all the details of the implementation, but below is the view of the tableViewModelForState method

Source: IDE Academy via Blog.Benjamin (continued)

Share the news now