MvRx pronounced “mavericks” is a framework made by Airbnb’s developer team that allows you to build android applications using the Model-View-ViewModel architecture and manage the state of your data without getting bored
MvRx is based on Google jetpack (architecture components) and supports Kotlin coroutines, it can be used only with Kotlin.
State
data class PostState(
val post: Async<List<Post>> = Uninitialized
) : MavericksState
With MvRx each view has a state, which is just a Kotlin data class which extends from MavericksState
The state contains all the data that will be loaded by the view as proprieties.
Async<*>
allows you to easily manage asynchronous sources and allows your state's properties to exist in different states, depending on that of the viewmodel.
These states can be :
Uninitialized (often used by default): Represents that there's not a field value yet.
Loading : Represents that the field's value is loading. This is useful when you want to show a progress bar or loading icon.
Success : When the data has successfully loaded and you can display them on the UI
Fail : Indicates that an error occurred in the request, so you can show an error UI.
In the above example the initial value is Uninitialized
since it won't have any data.
ViewModel
class PostViewModel(
initialState: PostState
) : MavericksViewModel<PostState>(initialState), KoinComponent {
private val repository: PostRepository by inject()
init {
getData()
}
private fun getData() {
repository.getPosts().execute {
copy(post = it)
}
}
}
Here our viewmodel will extend from MavericksViewModel
which takes the state as parameter.
Only the viewmodel can update the state, since Mavericks supports Kotlin coroutines, it provides us the execute
operator on kotlin flow in order to convert it content to Async
Since the state is immutable to update it we use copy()
Note : Mavericks does not yet support constructor injection with Koin, that's why I use property injection. But I often use it with Hilt.
View
class PostFragment : Fragment(), OnItemClickListener, MavericksView {
private val binding by lazy { FragmentPostBinding.inflate(layoutInflater) }
private val adapter by lazy { PostAdapter(this) }
private val postViewModel: PostViewModel by fragmentViewModel()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding.recyclerView.adapter = adapter
return binding.root
}
override fun onItemClick(post: Post) {
Toast.makeText(context,post.title,Toast.LENGTH_SHORT).show()
}
override fun invalidate(): Unit = withState(postViewModel) {
binding.progess.isVisible = it.post is Loading
when (it.post) {
is Success -> {
binding.progess.visibility = View.GONE
adapter.submitList(it.post.invoke())
}
is Fail -> {
Log.e("message", it.post.error.localizedMessage)
}
}
}
}
the view is a Fragment that extends from MavericksView, to initialize the ViewModel we use the delegate by fragmentViewModel()
private val postViewModel: PostViewModel by fragmentViewModel()
The view must override the invalidate()
method and the ViewModel will be used as shown below
override fun invalidate(): Unit = withState(postViewModel) {
binding.progess.isVisible = it.post is Loading
when (it.post) {
is Success -> {
binding.progess.visibility = View.GONE
adapter.submitList(it.post.invoke())
}
is Fail -> {
Toast.makeText( requireContext(), "An error occured", Toast.LENGTH_SHORT).show()
Log.e("message", it.post.error.localizedMessage)
}
}
}
invalidate()
is called each time the state changes.
it represents the state and we check if the data is being loaded the state will be Loading.
If there is an error (e.g. : server bug) MvRx will trigger an error and the status will be Fail and will contain an error message.
Otherwise, in case of success the state is Success in this case all operations can be performed.
You should note that it.post
type isAsync<Post>
and to cast it to Post simply call the invoke()
method
The result looks like below
Conclusion
Mavericks is a very powerful framework ! It contains several tools that we will never know how to expose in a single article, I recommend you to read the official doc.