The final step covers listening to state changes. We do that by adding listeners to the Store.

665

Adding a listener

In our todo app example, we can add a listener that gets notified whenever the todo list items change:

let subscription = store.addListener(forStateType: TodoList.self) { state in 
  // Use the state in a view
  someView.state = state
}
val subscription = store.addListener(TodoList::class.java, { state ->
  // Use the state in a view
  someView.state = state;
})
Subscription subscription = store.addListener(TodoList.class, state -> {
  // Use the state in a view
  someView.update(state);
});

When adding the listener, we can also pass a filter block that decides whether to notify the listener or not.

For instance, we can add a listener that gets notified if the number of todo items is bigger than 2:

let subscription = store.addListener(
  forStateType: TodoList.self,
  if: { oldState, newState in newState.todos.count > 2 }
) { state in
  print("Other listener called \(state.todos)")
}
val subscription = store.addListener(
  TodoList::class.java,
  { oldState, newStat -> newStat.todos.size > 2 }
) { state ->
  println("Other listener called ${state.todos}")
}
store.addListener(
  TodoList.class,
  (oldState, newState) -> newState.todos.size() > 2,
  state -> {
    System.out.println("Other listener called " + state);
});

The filter is a function that receives both the oldState and the newState. If you return true from the filter, the listener will be notified, otherwise, it will be skipped. We cover the filtering of listeners in greater length in filtering listeners page.

Using the Subscription

When calling addListener we get a subscription back. This subscription can be used to remove the listener when we're done with it.

subscription.removeListener()
subscription.removeListener()
subscription.removeListener();

We also use the subscription to trigger a manual notification to the listener about the currently held state in the store.

let subscription = store.addListener(forStateType: TodoList.self) {...}

// Later inform about the current state
subscription.informWithCurrentState()
val subscription = store.addListener(TodoList::class.java) { }

// Later inform about the current state
subscription.informWithCurrentState()
Subscription subscription = store.addListener(TodoList.class, state -> { ... });

// Later inform about the current state
subscription.informWithCurrentState();

Calling informWithCurrentState on the subscription invokes the listener callback block with whatever state the store currently have. This can be used to force trigger the listener notification block without dispatching an action to the store.

iOS Only: Linking the listener lifecycle to an object

Finally, the subscription can be used to link the listener life cycle to an object. When the linked object is deallocated, so is the listener.

For example, we can create a listener that updates our TodoView with the new state. Instead of remembering to remove the listener when the TodoView is deallocated, we can link the listener to the TodoView:

// The TodoView
let todoView = ...

let subscription = store.addListener(forStateType: TodoList.self) { state in
  // Setting the state in the view
  todoView.state = state
}

// Connect the listener lifecycle to the todoView
subscription.linkLifeCycleTo(object: todoView)

When the TodoView gets removed from the screen and gets deallocated, the listener will be removed from the store.

🚧

iOS: Using Weak self in Listeners

If you are using self inside the listener notification, make sure to capture self weakly.

let subscription = store.addListener(forStateType: TodoList.self) { [weak self] state in
  // Setting the state in the view
  self?.state = state
}

This is important to prevent strong memory cycles that lead to leaking.

State Converters

When adding a listener, we can customize the notification process by passing a stateConverter. Setting the state converter is covered in Using the StateConverter page.

Action Listeners

Sometimes you might need to be informed when a particular action happens. Especially when the action is intended to alert the UI about a change that is not related to any particular State. In these cases, you can add an ActionListener.

let subscription = store.addActionListener { action in
  // When any action occurs, this callback will invoked
  
  if action is SomeAction {
    // Do something with the action
    // Notice that we only get the action here
  }
}
val subscription = store.addListener<Any>("someAction") { state ->
  // Do something with the action
}
Subscription subscription = store.addListener("someAction", state -> {
	// Do something with the action
});

ActionListeners you add are informed about any action dispatched to the store. The notification of the action happens before the action is sent to the middlewares or the reducer.

At a later time, as with the Listener, we need to remove the action listener by calling:

subscription.removeListener()
subscription.removeListener()
subscription.removeListener();

In the iOS version of Suas, also as in Listener, you can link the life cycle of the action listener with an object so that when the object is deallocated, the action listener is deallocated too. This is done by calling linkLifeCycleTo on the subscription.

// The TodoView
let todoView = ...

let subscription = store.addActionListener { ... }

// Connect the listener lifecycle to the todoView
subscription.linkLifeCycleTo(object: todoView)

🚧

iOS: Using Weak self in Listeners

If you are using self inside the listener notification, make sure to capture self weakly.

let subscription = store.addActionListener { [weak self] action in
  // use self
}

This is important to prevent strong memory cycles that lead to leaking.

🚧

Note on more advanced usages

If your application contains multiple screens that are not logically or functionally tied; for example, in your todo app you might have a settings screen. In this case, you can add listeners to a specific type (or path) of the state. Head to applications with multiple states to learn more.

What's Next

This concludes our exploration of Suas core concepts. Head to filtering listeners to read about how to customize the listener notification logic.

Alternatively, read about how you can further customize the Suas store dispatching logic by using middlewares.

Finally, check the list of examples built with Suas.

Related Topics

Todo app example

Advanced topics
Applications with multiple states
Async Actions
Filtering Listener