/owl

Primary LanguageKotlinApache License 2.0Apache-2.0

Download License

Owl

Logo

State management system for Android Application, inspired by Redux and MVI.

Documentation

This library provides

  • Redux like state management.
  • Easy coroutine integration.
  • Lifecycle aware view state stream with Android Architecture Component.

This is what it looks like:

sealed class CounterIntent : Intent {
  object IncrementIntent : CounterIntent()
  object DecrementIntent : CounterIntent()
}

sealed class CounterAction : Action {
    data class UpdateCountAction(val count: Int) : CounterAction()
}

data class CounterState(val count: Int = 0) : State

class CounterViewModel : OwlViewModel<CounterIntent, CounterAction, CounterState>(initialState = CounterState()) {
    override fun intentToAction(intent: CounterIntent, state: CounterState): CounterAction = when (intent) {
        IncrementIntent -> UpdateCountAction(state.count + 1)
        DecrementIntent -> UpdateCountAction(state.count - 1)
    }

    override fun reducer(state: CounterState, action: CounterAction): CounterState = when (action) {
        is UpdateCountAction -> state.copy(count = action.count)
    }
}

class MainActivity : AppCompatActivity() {

    private val counterViewModel: CounterViewModel by lazy {
        ViewModelProviders.of(this).get(CounterViewModel::class.java)
    }

    private val textCount: TextView by lazy { findViewById<TextView>(R.id.text_count) }
    private val buttonIncrement: Button by lazy { findViewById<Button>(R.id.button_increment) }
    private val buttonDecrement: Button by lazy { findViewById<Button>(R.id.button_decrement) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        buttonIncrement.setOnClickListener {
            counterViewModel.dispatch(IncrementIntent)
        }
        buttonDecrement.setOnClickListener {
            counterViewModel.dispatch(DecrementIntent)
        }

        counterViewModel.state.observe(this, Observer { state ->
            textCount.text = "count: ${state.count}"
        })
    }
}

How it works

The start point of changing state is OwlViewModel#dispatch. When we dispatch Intent, The intent is converted to Action in OwlViewModel#intentToAction and reach to OwlViewModel#reducer where state actually changed.

All state change is notified to OwlViewModel#state livedata, So we can observe it.

Rules

Owl has simple rule for its classes.

Intent

Intent is the sealed class that indicates how we want to change the State. We should not consider current State when we dispatch Intent.

Action

Action is the sealed class that have the data we actually want to apply to State. We can access to Intent dispatched and current State when we create Action.

State

State is just a data class that contains view state. We should not write any logic in it.

Async Programing with Kotlin Coroutine.

Owl has Processor class for coroutine. To connect processor with ViewModel, pass Processor to OwlViewModel constructor.

class CounterViewModel : OwlViewModel<CounterIntent, CounterAction, CounterState>(
    initialState = CounterState(),
    processor = CounterProcessor()
) {
  ...
}

Then OwlViewModel notify Processor when Action dispatched. Processor itself is coroutine. So we can launch coroutine to do async programing. After we finish, we can notify Action to OwlViewModel by calling Processor#put.

class CounterProcessor : Processor<CounterAction>() {
    private fun processDelayedIncrementAction(action: DelayedIncrementAction) = launch {
        delay(1000)
        put(UpdateCountAction(action.count))
    }

    override fun processAction(action: CounterAction) {
        when (action) {
            is DelayedIncrementAction -> processDelayedIncrementAction(action)
        }
    }
}

Because OwlViewModel is fully independent from Processor, we don't have to consider about async job status in OwlViewModel. All OwlViewModel have to do is processing Action in reducer synchronously.

Installation

dependencies {
  implementation "team.itome.owl:${latest_version}"
}

Framework

Framework

Contributing

Pull requests are welcome! We'd love help improving this library. Feel free to browse through open issues to look for things that need work. If you have a feature request or bug, please open a new issue so we can track it.