Suas iOS & Android Unidirectional Flow Architecture

Welcome to the Suas documentation. You'll find comprehensive guides and documentation to help you start working with Suas as quickly as possible, as well as support if you get stuck. Let's jump right in!

Get Started    Guides

Getting started

How to get up and running with Suas

When using Suas you will build your applications by defining a Store that contains a Reducer. You dispatch Actions to the store that inform Listeners.

Head to Core concepts for an in-depth explanation of Suas components.

Let's get started with using Suas.

Installing Suas in your project

Install Suas by following the installations steps.

Building a counter

When building apps in Suas, we start by defining the state for our counter. In this example, the counter state is a struct that contains the counter value.

struct Counter {
  var value: Int
}
data class Counter(val value: Int = 0)
class Counter {      
  private final int count;
  
  // constructor + getter
}

We then define the actions that affect the state. For the counter, we need increment and decrement actions.

struct IncrementAction: Action {
  let incrementValue: Int
}

struct DecrementAction: Action {
  let decrementValue: Int
}
data class IncrementAction(val value: Int) : Action<Int>("increment", value)

data class DecrementAction(val value: Int) : Action<Int>("decrement", value)
private static final String INCREMENT_ACTION = "increment";
private static final String DECREMENT_ACTION = "decrement";

private Action getDecrementAction(int value) {
  return new Action<>(DECREMENT_ACTION, value);
}

private Action getIncrementAction(int value) {
  return new Action<>(INCREMENT_ACTION, value);
}

Now that we have both the State and the Actions, we need to specify how actions are going to affect the state. This logic is implemented in the reducer. The counter state reducer looks like the following:

let counterReducer = BlockReducer(state: Counter(value: 0)) { state, action in

  // Handle increment action
  if let action = action as? IncrementAction {
    var newState = state
    newState.value += action.incrementValue
    return newState
  }
  
  // Handle decrement action
  if let action = action as? DecrementAction {
    var newState = state
    newState.value -= action.decrementValue
    return newState
  }

  // Important: If action does not affec the state, return nil
  return nil
}
class CounterReducer : Reducer<Counter>() {

  override fun reduce(oldState: Counter, action: Action<*>): Counter? {
    
    return when (action) {
      
      // Handle increment action
      is IncrementAction -> {
        oldState.copy(value = oldState.value + action.value)
      }
      
      // Handle decrement action
      is DecrementAction -> {
        oldState.copy(value = oldState.value - action.value)
      }
      
      // Important: If action does not affec the state, return null
      else -> null
    }
  }
    
  // Provide a default value
  override fun getEmptyState(): Counter = Counter()
}
private static class CounterReducer extends Reducer<Counter> {

  @Nullable
  @Override
  public Counter reduce(@NonNull Counter oldState, @NonNull Action<?> action) {
    switch (action.getActionType()) {
        
      // Handle increment action
      case INCREMENT_ACTION: {
        int incrementValue = action.getData();
        return new Counter(oldState.count + incrementValue);
      }
        
      // Handle decrement action
      case DECREMENT_ACTION: {
        int decrementValue = action.getData();
        return new Counter(oldState.count - decrementValue);
      }
        
      // Important: If action does not affec the state, return null
      default: {
        return null;
      }
    }
  }

  @NonNull
  @Override
  public Counter getEmptyState() {
    // Provide a default value
    return new Counter(0);
  }
}

The reducer defines two things:

  1. The initial state for the store. i.e. the initial Counter value.
  2. The reduce function, which receives both the dispatched Action and the current State. This function decides what State to return based on the Action. If the reducer did not change the state, it should return nil/null

The Store is the main component we interact with in the application. The store contains:

  1. The app's state.
  2. The reducer, or reducers.
  3. The middlewares (covered in the advanced section).

We create a store with the following snippet:

let store = Suas.createStore(reducer: counterReducer)
val store = Suas.createStore(CounterReducer()).build()
Store store = Suas.createStore(new CounterReducer()).build();

Now we can dispatch actions to the store and add listeners to it.

Let's start by adding a listener that prints the new value whenever the state changes.

let subscription = store.addListener(type: Counter.self) { state in
  print("State changed to \(state.value)")
}
val subscription = store.addListener(Counter::class.java) { _, (value) ->
  println("State changed to $value")
}
Subscription subscription = store.addListener(Counter.class, (oldState, newState) ->
  System.out.println("State changed to " + newState.count)
);

Notice that we specified the type of the State. We do this because the state can contain multiple sub-states. We will cover sub-states in the applications with multiple states article.

Finally, we dispatch actions to the store whenever there is an event, such as a user tapping a button.

store.dispatch(action: IncrementAction(incrementValue: 10))
store.dispatch(action: IncrementAction(incrementValue: 1))
store.dispatch(action: DecrementAction(decrementValue: 5))
store.dispatchAction(IncrementAction(10))
store.dispatchAction(IncrementAction(1))
store.dispatchAction(DecrementAction(5))
store.dispatchAction(getIncrementAction(10));
store.dispatchAction(getIncrementAction(1));
store.dispatchAction(getDecrementAction(5));

This would print the following:

State changed to 10
State changed to 11
State changed to 6

When we no longer need the listener, we remove it by calling:

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

That's it. We implemented our first Suas application. I hope it wasn't that hard :hamster:

📘

iOS - Linking listener life cycle to an object

We can also tie the listener life cycle to an object (Could be a UIView or UIViewController) by using:

subscription.linkLifeCycleTo(object: self)

Now that it's linked, we don't have to call subscription.removeListener() as it will be removed when the linked object is deallocated.

What's next

Check the list of example applications built with Suas for a runnable version of the Counter example and other examples.

Related Topics

How to install Suas in your project
List of example applications built with Suas
Suas core architecture concepts and elements

Developer experience and tooling
Logging in Suas
Using Suas Monitor

Updated 3 years ago

Getting started


How to get up and running with Suas

Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.