LiveData With Transformations
| | | |

Android LiveData Transformation With Example | Map And SwicthMap

In this article, we’re going to explore the Transformations of LiveData. Sometime there may be a case where you want to changes in the LiveData value before dispatching it to the Observer or you may need to return different LiveData based on some condition. Here comes the concept of Transformations.

In the last article, we’ve discussed what are the basics of LiveData and also we look at two different example to implement LiveData.(If you haven’t read the previous article I highly recommended you to read before proceeding.)

Transformations:

Transformation basically applies a function to the values stored in the LiveData object and propagates the result to the downstream. There are currently two types of Transformations we have.

  1. Transformations.map: The map lets you apply a function to the output of LiveData and then propagates the result to the downstream.
  2. Transformations.switchMap: The swicthMap function transformation is a lot like a map but for mapping function that emits LiveData instead of values. Again switchMap functions propagate LiveData to downstream instead of single value.

Example of Transformations.map:

Let’s take a scenario where we need to show Snackbar whenever a new user added to the database. The Snackbar shows data of a custom String with username added in it.

 

You see a demo right, let’s create our ViewModel class to hold the LiveData of User.

TransformationViewModel class:
class TransformationViewModel : ViewModel() {

    private val userLiveData = MutableLiveData<User>()

    val userAddedData: LiveData<String> = Transformations.map(userLiveData, ::someFunc)

    private fun someFunc(user: User) = "New user ${user.username} added to database!"

    fun addNewUser(user: User) = apply { userLiveData.value = user }
}

There are two things noticeable here.

  • First, we are not exposing our user LiveData to publically. We have given a public function to just add the User object. By doing this we’re keeping our Immutability principle.
  • Second, for adding the map Transformations we need to provide source LiveData and function from which you need to return your custom value. You see in our case we’re returning a custom String with username add in it.

So, we create our ViewModel and LiveData with map Transformations, let’s see how we gonna use this ViewModel inside our Activity.

MainActivity:
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val viewModel = ViewModelProviders.of(this).get(TransformationViewModel::class.java)
        viewModel.userAddedData.observe(this, object : Observer<String>{
            override fun onChanged(t: String?) {
                Snackbar.make(mainActivityRootView, t!!, Snackbar.LENGTH_SHORT).show()
            }
        })
        addUserButton.setOnClickListener {
            viewModel.addNewUser(User(addNewEditText.text.toString()))
        }
    }
}

The Activity is very basic, in here we’re only observing our LiveData instance. Whenever the user object added to LiveData it automatically calls the onChanged method with the custom String.

Example of Transformations.switchMap:

Let’s take another scenario where I need to search user by username and show the result inside the RecyclerView.

You see the demo right let’s create our ViewModel class to search users with the name.

class UserRepo{
   fun searchUserWithName(name : String) : LiveData<List<User>>{
      ..... logic for search user
   }
}

class UserViewModel : ViewModel() {

    private val query = MutableLiveData<String>()
    private val userRepo = UserRepo()

    val userNameResult: LiveData<List<User>> = Transformations.map(
            query,
            ::temp
    )

    private fun temp(name: String) = userRepo.searchUserWithName(name)

    fun searchUserByName(name: String) = apply { query.value = name }
}

You see in our ViewModel class we’re getting the data from our UserRepo class. So, whenever something is searched, we’ll get users with the name, then the repository creates a new instance of LiveData and returned the list of users. And finally based on the result we display the data.

To clarify the things between map and switchMapI use map method so that, we could see what happened if we use map method instead of swicthMap when we need to return LiveData.

Problem:

Let’s say we’re looking for the username Alice. The repository is creating a new instance of that User LiveData class and after that, we display the users. After some time we need to look for the username Bob there’s the repository creates a new instance of LiveData and our UI subscribes to that LiveData. So at this moment, our UI subscribes to two instances of LiveData because we never remove the previous one. So it means whenever our repository changes the user’s data it sends two times subscription. Now, how do we solve this problem…?

Solution:

What we actually need is a mechanism that allows us to stop observing from the previous source whenever we want to observe a new one. In order to this, we would use switchMap. Under the hood, switchMap uses the MediatorLiveData that removes the initial source whenever the new source is added. In short, it does all the mechanism removing and adding a new Observer for us.

Now our ViewModel class will look like this.

class UserRepo{
   fun searchUserWithName(name  : String) : LiveData<List<User>>{
         .... logic for search user
   }
}

class UserViewModel : ViewModel() {

    private val query = MutableLiveData<String>()
    private val userRepo = UserRepo()

    val userNameResult: LiveData<List<User>> = Transformations.switchMap(
            query,
            ::temp
    )

    private fun temp(name: String) = userRepo.searchUserWithName(name)

    fun searchUserByName(name: String) = apply { query.value = name }
}

All the code remain the same instead of the switchMap method. Now here we only need our Activity class to observe this LiveData. The following shows the MainActivity class.

class MainActivity : AppCompatActivity() {

    private val userList: MutableList<User> = ArrayList()
    private lateinit var userAdapter: UserAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        userRecyclerView.layoutManager = LinearLayoutManager(this)
        userRecyclerView.setHasFixedSize(true)
        userAdapter = UserAdapter(this, userList)
        userRecyclerView.adapter = userAdapter
        val viewModel = ViewModelProviders.of(this).get(UserViewModel::class.java)
        searchUserButton.setOnClickListener { viewModel.searchUserByName(addNewEditText.text.toString()) }
        viewModel.userNameResult.observe(this, Observer {
            if (userList.isNotEmpty())
                userList.clear()
            userList.addAll(it!!)
            userAdapter.notifyDataSetChanged()
        })
    }
}

The Activity is very basic. After setting the RecyclerView properties, whenever the user clicks the button we’re calling our ViewModel class function to search the users with a username. And in the last, we simply observing the LiveData coming from our ViewModel class.

Alright, guys, I’m going to end this blog here. You can also download the complete source code of the above example.

Download Complete Code

If you’ve any queries regarding this post on Transformations or any problem with LiveData please do comment below.

Thank you for being here and keep reading…

Similar Posts

5 Comments

  1. Nice post.

    Few comments.

    1. I think there’s a typo in the switchMap example (addUserButton should be searchUserButton) – just for clarity.

    2. Also, I don’t think UI re-subscription part is clear. UI component doesn’t re-subscribe unless you restart/recreate the activity (or whatever is your controller). If that’s the case, it would been removed already as it would be in DESTROYED state. You need to unsubscribe on the previous LiveData instance and re-subscribe on the new one manually.

    1. Hey Zeki,
      Thanks for your comment.
      1. Resolved the type in swicthTransformation example.
      2. You are right the UI component doesn’t resubscribe unless you restart or recreate your controller. But if you see our userRepo creates LiveData> new instance every time, when the user called searchByName method. For more detailed instructions see this talk by Florina Muntenescu. Only see the LiveData part from 14:20 to 19:00.

Comments are closed.