104

In recent Android Architecture Components library released by Google, we have two static functions in the Transformations class. While the map function is straight forward and easily understandable, I am finding it hard to properly understand the switchMap function.

The official documentation of switchMap can be found here.

Can somebody explain how and where to use the switchMap function with a practical example?

1

7 Answers 7

164

In the map() function

LiveData userLiveData = ...;
LiveData userName = Transformations.map(userLiveData, user -> {
     return user.firstName + " " + user.lastName; // Returns String
});

everytime the value of userLiveData changes, userName will be updated too. Notice that we are returning a String.

In the switchMap() function:

MutableLiveData userIdLiveData = ...;
LiveData userLiveData = Transformations.switchMap(userIdLiveData, id ->
    repository.getUserById(id)); // Returns LiveData

void setUserId(String userId) {
     this.userIdLiveData.setValue(userId);
}

everytime the value of userIdLiveData changes, repository.getUserById(id) will be called, just like the map function. But repository.getUserById(id) returns a LiveData. So everytime that the value of the LiveData returned by repository.getUserById(id) changes, the value of userLiveData will change too. So the value of userLiveData will depend on changes of userIdLiveData and changes of the value of repository.getUserById(id).

Practical example of switchMap(): imagine you have a user profile with a follow button and a next profile button which sets another profile info. Next profile button will call setUserId() with another id so userLiveData will change and UI will change. Follow button will call the DAO to add one follower more to that user, so the user will have 301 followers instead of 300. userLiveData will have this update that comes from the repository, which comes from the DAO.

7
  • 1
    so basically, it's a way to listen to a few change-sources of your data at once. If the id changes, your userLiveData changes, and if the values of the actual user changes, your userLiveData changes as well. Correct? (You could probably stack a few transformation together to connect even more LiveData's together, though you probably should use MediatorLiveData as well.) Commented Nov 11, 2018 at 10:12
  • 4
    Some architecture background of Transformations, they are most used at the ViewModel since it allows to transform the data of the type coming from the DAO to a type that needs to be shown at the UI, so, imagine you have an add to cart feature, you will be adding elements to a cart , lets say this cart is a hashmap that corresponds each product id with an item in a list, this hashmap comes from the DAO, but instead of passing this hashmap to the UI, we use a transformation to convert this hashmap into a friendly list to show in the UI, now HashMap<String,Cart> will be transformed as List<Cart> Commented Mar 23, 2020 at 14:20
  • Is this a good example? It seems to imply that you make a database call in the switchMap callback, while the doc states: "The given function func will be executed on the main thread. "
    – Maarten
    Commented Jun 13, 2020 at 18:23
  • How to set default value for transformations query? Commented Nov 24, 2020 at 13:43
  • Maarten, we should only return the userLiveData for observing and along with that do actual async call to the database and then call postValue on this livedata.
    – RoK
    Commented Mar 6, 2021 at 7:55
34

Adding my 2 cents to @DamiaFuentes answer.

MutableLiveData userIdLiveData = ...;
LiveData userLiveData = Transformations.switchMap(userIdLiveData, id ->
repository.getUserById(id)); // Returns LiveData

void setUserId(String userId) {
     this.userIdLiveData.setValue(userId);
}

Transformations.switchMap method will only be called when you have at least one observer for userLiveData

3
  • 5
    Thanks a ton @Prakash i was struggling to find out why my switchMap was not listening to the changes in trigger.
    – Android
    Commented Sep 24, 2018 at 13:48
  • 11
    I used to be a simple man. When I couldn't understand this concept, I went and made something of my own. In this case, I created a whole class with custom DataSource, builders, etc until I hit a mental block. Your answer made be a simple man again. I deleted that class. Commented Sep 19, 2019 at 17:05
  • repository.getUserById(id); How do you handle the case when switchmap() is called on the getUserById(), the mutabledata != null condition Commented Nov 22, 2020 at 13:15
30

For those who want more explanation of @DamiaFuentes switchmap() function example given below:

 MutableLiveData userIdLiveData = ...;
 LiveData userLiveData = Transformations.switchMap(userIdLiveData, id ->
     repository.getUserById(id));

 void setUserId(String userId) {
      this.userIdLiveData.setValue(userId);
 }

In a scenario where the repository contains User(1, "Jane") and User(2, "John"), when the userIdLiveData value is set to "1", the switchMap will call getUser(1), that will return a LiveData containing the value User(1, "Jane"). So now, the userLiveData will emit User(1, "Jane"). When the user in the repository gets updated to User(1, "Sarah"), the userLiveData gets automatically notified and will emit User(1, "Sarah").

When the setUserId method is called with userId = "2", the value of the userIdLiveData changes and automatically triggers a request for getting the user with id "2" from the repository. So, the userLiveData emits User(2, "John"). The LiveData returned by repository.getUserById(1) is removed as a source.

From this example, we can understand that the userIdLiveData is the trigger and the LiveData returned by the repository.getUserById is the "backing" LiveData.

For more reference, check out: https://developer.android.com/reference/android/arch/lifecycle/Transformations

2
5

The function passed to switchMap returns LiveData. Use it when your repository itself returns LiveData.

4

Another point to consider whether choosing between switchMap or map, you have to remember that map always wraps the returned value around LiveData e.g.

fun getUser(id: Int): User 
...
val userId = MutableLiveData(1)
val user = userId.map { // LiveData<User>
   repository.getUser(it)
}

You might consider using map if repository.getUser(it) returns a plain simple User object instead of LiveData so the type of user becomes LiveData<User>.

If repository.getUser(it) returns a LiveData<User> then it's better to use switchMap

fun getUser(id: Int): LiveData<User>
...
val userId = MutableLiveData(1)
val user = userId.switchMap { // LiveData<User>
   repository.getUser(it)
}

The user type would be LiveData<User>

1
  • This is pretty much same as Optional.map() and Optional.flatMap() in Java. It unwraps the T from Optional<T> to avoid having Optional<Optional<T>> in output. Same with LiveData, this LiveData<LiveData<User>> is nonsense.
    – Max_Payne
    Commented Apr 16, 2023 at 15:18
3

And yet another point for understanding. One may think that as we always return new value (new reference) of LiveData inside switchMap() so how can we observe actual values with the observer having set just one time? The point is the returned value of Transformations.switchMap is MediatorLiveData which adds new LiveData reference to as a new source (and deactivates the other sources).

1
  • You read my mind, thanks!!
    – Amr Salah
    Commented Aug 1, 2022 at 10:09
0

Since Transformations are lazily calculated and behave in a lifecycle aware manner they can be used within the ViewModel, in combination with domain-level LiveData publishers, for publishing UI updates.

So switchmap is designed for dealing with asynchronous events. Lets say you're querying a database - an asynchronous operation - and publishing the results via LiveData in the manner described above. Lets say your application makes 2 queries in quick succession - query 1 returning response 1 (used to update the UI), and query 2 returning response 2. And let's say that although you sent query 1 first, it completes and arrives after query 2. With the regular Transformations.map() operator response 1 will be published last, and will determine the final application state. With Transformations.switchMap() the moment you send query 2 the first response will be effectively unsubscribed to, and you will only receive the second response. The way this works is the LiveData returned by Transformations.switchMap() delegates to another, internal LiveData publisher within the switchMap implementation. This internal LiveData publisher is recreated / resubscribed to on every update. But note that this wouldn't guarantee synchronization with the database in this example. You'd still receive response number 1 from the database... you just wouldn't be listening to it.

This would only really be useful in cases where the request you’re sending is non-mutating, the UI is intended to drive the application state, and either your client or the service you're interacting with can't guarantee ordering of requests / responses. Assuming you're using TCP, responses arriving out of order is not even possible. Not only does TCP guarantee ordering of requests / responses, but the OS manages an internal connection pool and ensure that your client has just one TCP connection per origin; or if it does spawn multiple connections (sometimes a performance optimization) this will be transparent to the application layer. So this scenario isn't even possible without using some custom protocol / low level connection management. If you believe your architecture behaves this way you probably are either confused or there are deeper problems in your application. Perhaps the Android devs who added this method were confused too, it's hard for me to imagine a practical application for this method, but I digress...

The docs give an example of using switchMap, where the user is inputting an address and a service is returning a zipcode. The user input is what should drive the application state here, so synchronization with the backend service isn't an issue - and the service is non mutating. But again, the OS is going to maintain just 1 TCP connection for that origin, and requests / responses will be in order, so it's kind of a meaningless example: https://developer.android.com/topic/libraries/architecture/livedata

note that, if there were some scenario where you genuinely needed switchMap - maybe you had a custom protocol and multiple connections with the server you were managing - in this scenario if you were to just naively use switchMap() and send requests to a service on a background thread... you could end up sending these requests in any order, and they could be processed in any order, but you'd still only ever receive the second response on the client... if the requests are mutating it will result in your client falling out of sync with your service.

It's almost certain that you will never need switchMap. Certainly not if you're using TCP. The scenario in the docs is not even a valid use case for it, and the Android devs were probably just confused when they created it. But in general it is better to just use Flows in the domain layer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.