Giter Site home page Giter Site logo

states's Introduction

⚑️ Lightning talk intro to states

FAQ about States

Graph

╔══════════════════════════╗
β•‘          STATE           β•‘
β•Ÿβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β•’
β•‘ Reducer ◀───▢ Properties β•‘
β•‘    β–²               β”‚     β•‘
β•‘    β”‚               β–Ό     β•‘
β•‘  Events         Outputs  β•‘
β•šβ•β•β•β•β•ͺ═══════════════β•ͺ═════╝
Β·   ─┼─             ─┼─
Β·    β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
Β·    β”œβ”€β”Όβ”Όβ—€ TESTS ◀┼┼──
Β·    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
Β·   ═β•ͺ═             ═β•ͺ═
╔════β•ͺ═══════════════β•ͺ═════╗
β•‘    └─◀ Feedback ↻ β—€β”˜     β•‘
β•Ÿβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β•’
β•‘        CONTROLLER        β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

Why should I use states?

We use states in Trafi for a few reasons:

  • States break down big problems to small pieces
  • States keep our code pure and easily testable
  • States help us share solutions between platforms & languages
  • States make debugging easier with a single pure function

Why shouldn't I use states?

  • It's an unusual development flow
  • Overhead for very simple cases
  • Takes time and practice to integrate into existing code

What is a state?

A state is the brains of a screen. It makes all the important decisions. It's a simple type with three main parts:

  1. Privately stored data
  2. Enum of events to create new state
  3. Computed outputs to be handled by the controller
πŸ”Ž See a simple example

Swift

struct CoinState {

  // 1. Privately stored data
  private var isHeads: Bool = true
  
  // 2. Enum of events
  enum Event {
    case flipToHeads
    case flipToTails
  }
  // .. to create new state
  static func reduce(state: CoinState, event: Event) -> CoinState {
    switch event {
    case .flipToHeads: return CoinState(isHeads: true)
    case .flipToTails: return CoinState(isHeads: false)
    }
  }
  
  // 3. Computed outputs to be handled by the controller
  var coinSide: String {
    return isHeads ? "Heads" : "Tails"
  }
}

Kotlin

data class CoinState(
    // 1. Privately stored data
    private val isHeads: Boolean = true
)

// 2. Enum of events
sealed class Event {
    object FlipToHeads : Event()
    object FlipToTails : Event()
}

// .. to create new state
fun CoinState.reduce(event: Event) = when(event) {
    FlipToHeads -> copy(isHeads = true)
    FlipToTails -> copy(isHeads = false)
}
  
// 3. Computed outputs to be handled by the controller
val CoinState.coinSide: String get() {
    return isHeads ? "Heads" : "Tails"
}

Samples

How do I write states?

There are many ways to write states. We can recommend following these steps:

  • Draft a platform-independent interface:
    • List events that could happen
    • List outputs to display UI, load data and navigate
  • Implement the internals:
    • ❌ Write a failing test that sends an event and asserts an output
    • βœ… Add code to state till test passes
    • πŸ›  Refactor code so it's nice, but all tests still pass
    • πŸ” Continue writing tests for all events and outputs

What can be an event?

Anything that just happened that the state should know about is an event. Events can be easily understood and listed by non-developers. Most events come from a few common sources:

  • User interactions
    • tappedSearch
    • tappedResult
    • completedEditing
    • pulledToRefresh
  • Networking
    • loadedSearchResults
    • loadedMapData
  • Screen lifecycle
    • becameReadyForRefresh
    • becameVisible
    • enteredBackground
  • Device
    • wentOffline
    • changedCurrentLocation

As events are something that just happened we start their names with verbs in past simple tense.

πŸ”Ž See an example

Swift

struct MyCommuteState {
  enum Event {
    case refetched(MyCommuteResponse)
    case wentOffline
    case loggedIn(Bool)
    case activatedTab(index: Int)
    case tappedFavorite(MyCommuteTrackStopFavorite)
    case tappedFeedback(MyCommuteUseCase, MyCommuteFeedbackRating)
    case completedFeedback(String)
  }
}

Kotlin

data class MyCommuteState(/**/)

sealed class Event {
    data class Refetched(val response: MyCommuteResponse) : Event()
    object WentOffline : Event()
    data class LoggedIn(val isLoggedIn: Boolean) : Event()
    data class ActivatedTab(val index: Int) : Event()
    data class TappedFavorite(val favorite: MyCommuteTrackStopFavorite) : Event()
    data class TappedFeedback(val feedback: Feedback) : Event()
    data class CompletedFeedback(val message: String) : Event()
}

What are outputs?

Outputs are the exposed getters of state. Controllers listen to state changes through outputs. Like events, outputs are simple enough to be understood and listed by non-developers. Most outputs can be categorized as:

  • UI. These are usually non-optional outputs that specific UI elements are bound to, e.g:
    • isLoading: Bool
    • paymentOptions: [PaymentOption]
    • profileHeader: ProfileHeader
  • Data. These are usually optional outputs that controllers react to. Their names indicate how to react and their types give associated information if needed, e.g:
    • loadAutocompleteResults: String?
    • loadNearbyStops: LatLng?
    • syncFavorites: Void?
  • Navigation. These are always optional outputs that are just proxies for navigation, e.g.:
    • showStop: StopState?
    • showProfile: ProfileState?
    • dismiss: Void?

What to store privately?

Any properties that are needed to compute the necessary outputs can be stored privately. We strive for this to be the minimal ground truth needed to represent any possible valid state.

πŸ”Ž See an example

Swift

struct PhoneVerificationState {
    private let phoneNumber: String
    private var waitBeforeRetrySeconds: Int
}

Kotlin

data class PhoneVerificationState(
    private val phoneNumber: String,
    private val waitBeforeRetrySeconds: Int
)

What does the reducer do?

The reducer is a pure function that changes the state's privately stored properties according to an event.

πŸ”Ž See an example

Swift

struct CoinState {
    private var isHeads: Bool = true

    static func reduce(_ state: CoinState, event: Event) -> CoinState {
        var result = state
        switch event {
        case .flipToHeads: result.isHeads = true
        case .flipToTails: result.isHeads = false
        }
        return result
    }
}

Kotlin

data class CoinState(private val isHeads: Boolean) {

    fun reduce(event: Event) = when(event) {
        FlipToHeads -> copy(isHeads = true)
        FlipToTails -> copy(isHeads = false)
    }
}

How do I write specs?

We write specs (tests) in a BDD style. For Swift we use Quick and Nible, for Kotlin Spek.

πŸ”Ž See an example

Swift

class MyCommuteSpec: QuickSpec {

    override func spec() {

        var state: MyCommuteState!
        beforeEach {
            state = .initial(response: .dummy, now: .h(10))
        }

        context("When offline") {

            it("Has no departues") {
                expect(state)
                    .after(.wentOffline)
                    .toTurn { $0.activeFavorites.flatMap { $0.departures }.isEmpty }
            }

            it("Has no disruptions") {
                expect(state)
                    .after(.wentOffline)
                    .toTurn { $0.activeFavorites.filter { $0.severity != .notAffected }.isEmpty }
            }
        }
    }
}

Kotlin

object NearbyStopsStateSpec : Spek({
    describe("Stops near me") {

        describe("when location is present") {
            var state = NearbyStopsState(hasLocation = true)
            beforeEach { state = NearbyStopsState(hasLocation = true) }

            describe("at start") {
                it("shows progress") { assertEquals(Ui.Progress, state.ui) }
                it("tries to load stops") { assertTrue(state.loadStops) }
            }
        }
    }
}

How do I use states?

States become useful when their outputs are connected to UI, network requests, and other side effects.

Reactive streams compose nicely with the states pattern. We recommend using RxFeedback.swift / RxFeedback.kt to connect states to side effects in a reactive way.

πŸ”Ž See an example

Swift

Driver.system(
        initialState: input,
        reduce: PhoneVerificationState.reduce,
        feedback: uiBindings() + dataBindings() + [produceOutput()])
    .drive()
    .disposed(by: rx_disposeBag)

States are versatile and can be used with more traditional patterns, e.g. observer / listener patterns. On Android we use a simple state machine implementation which you can find in the Kotlin state tools.

πŸ”Ž See an example

Kotlin (Android)

private val machine = StateMachine(PhoneVerificationState("+00000000000"))

machine.subscribeWithAutoDispose(viewLifecycleOwner) { boundState, newState ->
    // do things with newState
}

states's People

Contributors

justasm avatar olegas-murasko-trafi avatar

Stargazers

Rafal. avatar Art Shendrik avatar Liudas Baronas avatar  avatar  avatar masterAtyan avatar Audrius avatar hyl87 avatar Mikas Dominas avatar Remigijus Klimovas avatar Jaroslav_ avatar Oleksandr Voronov avatar ABHISHEK RAJ avatar Gleb Skibitsky avatar  avatar Tim Kersey avatar Ricardo Pallas avatar  avatar Kristaps Grinbergs avatar Sebastian Sellmair avatar

Watchers

Paulius MorkΕ«nas avatar James Cloos avatar  avatar  avatar Simas Abramovas avatar  avatar Benediktas avatar Ieva DapΕ‘evičiΕ«tΔ— avatar Remigijus Klimovas avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.