Skip to content
Devrath edited this page Apr 10, 2024 · 10 revisions

Mavericks

  • Mavericks is a MVI-framework

Simplest screen

/** State classes contain all of the data you need to render a screen. */
data class CounterState(val count: Int = 0) : MavericksState

/** ViewModels are where all of your business logic lives. It has a simple lifecycle and is easy to test. */
class CounterViewModel(initialState: CounterState) : MavericksViewModel<CounterState>(initialState) {
    fun incrementCount() = setState { copy(count = count + 1) }
}

/**
 * Fragments in Mavericks are simple and rarely do more than bind your state to views.
 * Mavericks works well with Fragments but you can use it with whatever view architecture you use.
 */
class CounterFragment : Fragment(R.layout.counter_fragment), MavericksView {
    private val viewModel: CounterViewModel by fragmentViewModel()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        counterText.setOnClickListener {
            viewModel.incrementCount()
        }
    }

    override fun invalidate() = withState(viewModel) { state ->
        counterText.text = "Count: ${state.count}"
    }
}

Core Concepts

Mastering Mavericks only requires using three classes

MavericksState

  • This is the first step in creating a Screen by modeling it as a function of state.
  • The Mavericks state doesn't do anything but signal the intention of your class to be used as a state.
  • Ensuring the state class is a data class.
  • Uses only immutable properties.
  • All the properties have a default value so that it can be immediately loaded.
data class UserState(
    val score: Int = 0,
    val previousHighScore: Int = 150,
    val livesLeft: Int = 99,
) : MavericksState
  • You can create derived properties, The derived properties are the properties inside the body of your state class.
data class UserState(
    val score: Int = 0,
    val previousHighScore: Int = 150,
    val livesLeft: Int = 99,
) : MavericksState {
    // Properties inside the body of your state class are "derived".
    val pointsUntilHighScore = (previousHighScore - score).coerceAtLeast(0)
    val isHighScore = score >= previousHighScore
}
  • Placing logic as a derived property like pointsUntilHighScore or isHighScore means that, You can subscribe to changes to just these derived properties, It will never be out of sync with the underlying state.

MavericksViewModel

  • It is responsible for updating the state.
  • It is responsible for exposing the stream of states that other classes subscribe to.
  • It is similar to JetpackViewModels condition being generic on a MavericksState class.
  • Updating the state involves, From within a viewModel, you call setState { copy(yourProp = newValue) }.
  • copy comes from the state class being a data class.
  • The lambda is not executed synchronously. It is put on a queue and run on a background thread.
  • Handling asynchronous operations with ease was one of the primary goals of Mavericks.

MavericksView

  • It is where you render your state class to the screen.
  • Most of the time, It will be a fragment but it doesn't have to be.
  • By implementing the MavericksView, We get access to
    • MavericksViewModel using view model delegates.
    • We will automatically be able to subscribe from changes and call invalidate().
    • Overriding the invalidate() function so that whenever the state changes the view is notified here.

Clone this wiki locally