Redux Approach in Android Using MutableLiveData

  • 28th Jan, 2021
  • Farhan S.
Share
  • LinkedIn-icon
  • WhatsApp-icon

Redux Approach in Android Using MutableLiveData

28th Jan, 2021 | Farhan S.

  • Software Development
Redux

You must have heard about the word “Redux” while working with frontend developers or somewhere else, No ? Let me explain.

Redux is a pattern and library for managing and updating application state, using events called "actions". It serves as a centralized store for state that needs to be used across your entire application, with rules ensuring that the state can only be updated in a predictable fashion.

This article delves into the integration of the Redux approach in Android development, particularly leveraging the power of MutableLiveData.

Understanding Redux in Android

Redux is a state management pattern and library that originated in the JavaScript and web development ecosystem but has found its way into various other platforms, including Android.

Redux provides a predictable and centralized way to manage the state of an application, making it easier to develop and maintain complex user interfaces.

In the context of Android, Redux is not a specific library or framework but rather a set of principles and patterns that can be implemented using existing tools and libraries.

Redux Principles

It was designed and developed for JavaScript but we can follow the Redux pattern in Android as well, Redux can be described in three fundamental principles.

  • CheckSingle source of truth ( Store )
  • CheckState is read-only ( State )
  • CheckChanges are made with pure functions ( Reducers )

Android Implementation

According to the Redux principles we will create three components but with slight variation. For state update callback we will use MutableLiveData an AndroidX lifecycle component which allows us to observe the change in the data.

What are LiveData and Observers?

Observers are the functions which observe the changes in the LiveData and get automatically called when it receives any updates.

LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state.

We are using Kotlin to show code examples.

State

A state contains data that is accessible throughout the App. Here we are creating a To Do App for our sample project, so we will be storing the list of tasks in the App State.

To make our todo list observable we will wrap it with LiveData, similarly you can wrap any type of class with LiveData and make it observable. Moreover you can add as many as variables using LiveData.

data class AppState(
    val tasks: LiveData<ArrayList<ToDoTask>>,
    var isLoading: LiveData<Boolean>
)

Reducer

Reducer contains the logical part, in this class we will create functions to apply CRUD operations. Here we are using a different approach with respect to the Redux principles, we are creating separate functions for different actions instead of action-dispatch functions.

You can not modify LiveData directly, so first we will cast it to MutableLiveData and then apply changes. For more details about MutableLiveData read official documentation here.

class Reducer(private val state: AppState) {

    fun addTask(task: ToDoTask) {
        val taskList = state.tasks.value!!
        taskList.add(task)

        // If you are working on Main thread you can use #setValue(),
        // for background thread use #postValue()
        (state.tasks as MutableLiveData).postValue(taskList)
    }

    fun deleteTask(task: ToDoTask): Unit {
        val taskList = state.tasks.value!!
        taskList.remove(task)
        (state.tasks as MutableLiveData).postValue(taskList)
    }

    fun updateTask(index: Int, isChecked: Boolean): Unit {
        val taskList = state.tasks.value!!
        taskList[index].isCompleted = isChecked
        (state.tasks as MutableLiveData).postValue(taskList)
    }

}

Store

Store is a global object which contains the AppState and the Reducer. We will create our Store class with AppState and Reducer, Initialise AppState with initial state and pass the initial state to the reducer.

class Store {
    var state: AppState
    var reducer: Reducer

    init {
        // Initial state
        state = AppState(
            MutableLiveData(ArrayList()),
            MutableLiveData(false)
        )
        reducer = Reducer(state)
    }
}

Ok! But, How to Combine these Things Together?

The Store should be accessible throughout the application, so we will need to create a global instance of store. Consequently we will extend the Application class and create a companion object, which will allow us to access the store object App wide.

class ThisApp : Application() {

    companion object {
        val store = Store()
    }

}

Don’t forget to add ThisApp to AndroidManifest.xml

CRUD Operations

To modify the App State we need to call reducer functions which we created earlier. Below is the code which shows how to call reducer functions.

...
// Add
ThisApp.store.reducer.addTask(ToDoTask(1,"New Task","Description",false))

// Update
itemView.taskCheck.setOnClickListener {
    ThisApp.store.reducer.updateTask(position,!task.isCompleted)
}

// Delete
itemView.deleteTaskBtn.setOnClickListener {
    ThisApp.store.reducer.deleteTask(task)
}

...

Updating the UI

For our To Do App, we require to list the tasks in the recycler view, so we will create an Adapter for it. We will use our tasks array list as a data source for TaskAdapter.

class TaskAdapter : RecyclerView.Adapter<TaskViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskViewHolder {
        return TaskViewHolder(View.inflate(parent.context, R.layout.task_item_layout, null))
    }

    override fun onBindViewHolder(holder: TaskViewHolder, position: Int) {
        val task = ThisApp.store.state.tasks.value!![position]
        holder.bind(task,position)
    }

    override fun getItemCount(): Int {
        return ThisApp.store.state.tasks.value!!.size
    }
}
...

But there is a problem, the TaskAdapter doesn’t know that the data has been changed, so we need to notify it about that. For this purpose we will add an observer for the tasks list in our activity as shown below

...
override fun onCreate(savedInstanceState: Bundle?) {
    // Add observer to specific live data
    ThisApp.store.state.tasks.observe(this) {
        taskAdapter.notifyDataSetChanged()
        if (it.size == 0) emptyView.visibility = View.VISIBLE
        else emptyView.visibility = View.GONE
    }
}
...

override fun onDestroy() {
    super.onDestroy()
    // Remove all observers
    ThisApp.store.state.tasks.removeObservers(this)
}

......
override fun onCreate(savedInstanceState: Bundle?) {
    // Add observer to specific live data
    ThisApp.store.state.tasks.observe(this) {
        taskAdapter.notifyDataSetChanged()
        if (it.size == 0) emptyView.visibility = View.VISIBLE
        else emptyView.visibility = View.GONE
    }
}
...

override fun onDestroy() {
    super.onDestroy()
    // Remove all observers
    ThisApp.store.state.tasks.removeObservers(this)
}

...

Whenever the list gets updated, it will call taskAdapter.notifyDataSetChanged() which will update the recycler view. Yesss ! we made it. That's how we can apply the Redux approach in Android using LiveData.

You can clone the sample project from github.

Conclusion

Integrating the Redux approach with MutableLiveData in Android provides a robust and scalable architecture for state management.

The unidirectional data flow, immutability, and predictability offered by Redux contribute to a more maintainable and debuggable codebase.

By leveraging MutableLiveData for reactive updates, developers can seamlessly connect the Redux architecture with the Android UI, creating a powerful combination for building modern and responsive applications.

As Android development continues to evolve, embracing such architectural patterns becomes increasingly essential for crafting high-quality, scalable, and maintainable apps.

Note: This approach might not suit all types of projects, in the end it depends on you to choose appropriate architecture as per your project requirements. The LiveData which we used is a part of MVVM architecture recommended by the Android officials.

References:

1. AndroidX

2. Redux

3. Live Data

4. Mutable Live Data

Tags: Android,Kotlin,Redux,Javascript,Design Pattern,Technology

More blogs in "Software Development"

Unit Tests
  • Software Development
  • 31st Mar, 2024
  • Dhanashree K.

Beginner’s Guide on Unit Tests: What, Why and How?

Have you ever made a small change in form validation, tested it, deployed it, and then broken the next few pages or end points of...
Keep Reading
Architecture Decision Records
  • Software Development
  • 10th Dec, 2023
  • Akshay P.

The Ultimate Guide to Architectural Decision Records (ADRs)

In the world of software development, architectural decisions play a crucial role in the success of a project. However, these decisions often get lost in the...
Keep Reading
Object Detection
  • Software Development
  • 3rd Jan, 2024
  • Arjun S.

Object Detection in 2025: The Ultimate Guide

Image Source: Object Detection Ever wondered how your smartphone recognizes faces in photos or how self-driving cars navigate through traffic seamlessly? The answer lies in a...
Keep Reading