Skip to content

Latest commit

 

History

History
279 lines (198 loc) · 12.7 KB

README.md

File metadata and controls

279 lines (198 loc) · 12.7 KB

Build Status Codecov branch Platform support CocoaPods Compatible Carthage Compatible

Swift Version Swift Version Swift Version License Join the chat at https://gitter.im/SuasArch/Lobby

Suas is a unidirectional data flow architecture implementation for iOS/macOS/tvOS/watchOS and Android heavily inspired by Redux. It provides an easy-to-use library that helps to create applications that are consistent, deterministic, and scalable.

Suas focuses on providing good developer experience and tooling such as customizable logging and state changes monitoring.

Join our gitter chat channel for any questions. Or check Suas documentatation website.

What's on this page

For more in depth documentation on how to use Suas check Suas website, Suas API Interface or go straight to a list of applications built with Suas.

Suas application flow and components

Suas architecture is composed of five core elements:

  • Store: main component that contains a Reducer (or set of reducers), and the main application State. Listeners subscribe to it for state changes. Actions that cause state changes are dispatched to it.
  • State: defines the state of a component/screen or group of components/screens.
  • Action: each action specifies a change we want to effect on the state.
  • Reducer: contains the logic to alter the state based on a specific action received.
  • Listener: callback that gets notified when the state changes.

The following animation describes the Suas runtime flow.

Why use Suas

Suas helps you to build highly-dynamic, consistent mobile applications:

  • Cross platform; Suas-iOS works on iOS, macOS, tvOS and watchOS. Suas-Android works on all API levels and provides a Kotlin-friendly interface.
  • Focuses on developer experience with plugins/tools like LoggerMiddleware and Suas Monitor.
  • Small code base with low operational footprint. Check the source code 🙂.
  • Static typing and type information are conserved in the Store, Reducers, and Listeners.
  • Fast out of the box, and can be customized by developers to be even faster with filtering listeners.

Installation

Suas on the iOS can be installed with Carthage or CocoaPods

Installing with Carthage

Open your Cartfile and append the following:

github "zendesk/suas-ios"

And then build it with carthage update --platform ...

Installing with CocoaPod

Add pod 'Suas' to your Podfile.

use_frameworks!

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'

pod 'Suas'

Then run pod install

Getting started

Let's get started by building a counter application.

When building applications 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
}

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
}

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:

struct CounterReducer: Reducer {

  var initialState = Counter(value: 0)

  func reduce(state: CounterReducer.StateType, action: Action) -> CounterReducer.StateType? {
    // Handle each action
    if let action = action as? IncrementAction {
      var newState = state
      newState.value += action.incrementValue
      return newState
    }

    if let action = action as? DecrementAction {
      var newState = state
      newState.value -= action.decrementValue
      return newState
    }

    // Important: If action does not affec the state, return ni
    return nil
  }

}

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

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

  1. The application's state.
  2. The reducer, or reducers.
  3. (Advanced) The middlewares

We create a store with the following snippet:

let store = Suas.createStore(reducer: counterReducer)

Now we can dispatch actions to it and add listeners to it. Let's look at our UI.

class CounterViewController: UIViewController {
  @IBOutlet weak var counterValue: UILabel!

  override func viewDidLoad() {
    super.viewDidLoad()
    // Finally: Use the store
    
    self.counterValue.text = "\(store.state.value(forKeyOfType: Counter.self)!.value)"
    
    // Add a listener to the store
    // Notice the [weak self] so we dont leak listeners
    let subscription = store.addListener(forStateType: Counter.self)  { [weak self] state in
      self?.counterValue.text = "\(state.value)"
    }

    // When this object is deallocated, the listener will be removed
    // Alternatively, you have to delete it by hand `subscription.removeListener()`
    subscription.linkLifeCycleTo(object: self)
  }

  @IBAction func incrementTapped(_ sender: Any) {
    // Dispatch actions to the store
    store.dispatch(action: IncrementAction(incrementValue: 1))
  }

  @IBAction func decrementTapped(_ sender: Any) {
    // Dispatch actions to the store
    store.dispatch(action: DecrementAction(decrementValue: 1))
  }
}

Let's break down the code above:

  1. We add a listener to the store by calling store.addListener(forStateType: Counter.self) specifying the state type. Notice that we Must use [weak self] reference in the callback block to prevent strong memory cycles.
  2. By calling subscription.linkLifeCycleTo(object: self) we link the listener lifecycle to the view controller. When the controller is deallocated the listener is removed.
  3. Tapping on the increase or decrease button dispatches actions with store.dispatch(action:) that change the state.

That's it, check our documentation website for a full reference on Suas components and check the list of example built using Suas.

Developer experience and tooling

Suas focuses on developer experience and tooling. It provides two plugins in the form of Middlewares out of the box.

Customizable logging

While the LoggerMiddleware logs all the action received with the state changes.

Read more about how to use the LoggerMiddleware.

State transition monitoring

The MonitorMiddleware helps to track state transition and action dispatch history. When using MonitorMiddleware the Action dispatched and State changes are sent to our Suas Monitor desktop application.

Read how to install and start using the MonitorMiddleware by heading to getting started with monitor middleware article. Under the hood Suas Monitor uses the fantastic Redux DevTools to provide state and action information.

Example applications built with Suas

Check Suas website for an updated list of examples built with Suas.

Where to go next

To get more information about Suas:

Contributing

We love any sort of contribution. From changing the internals of how Suas works, changing Suas methods and public API, changing readmes and documentation topics.

Feel free to suggest changes on the GitHub repos or directly in Saus gitter channel.

For reference check our contributing guidelines.

Contact us

Join our gitter channel to talk to other Suas developers.

For any question, suggestion, or just to say hi, you can find the core team on twitter:

Suas future

To help craft Suas future releases, join us on gitter channel.

Copyright and license

Copyright 2017 Zendesk, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.