I have followed this example, an integrated ViewModel and LiveData.
I used LiveData for ViewModel to Repository communication, and activity to ViewModel communication
I have few confusions that I want to clear this question.
This is working fine, and display Toast Message after 5 seconds on MainActivity.
MainActivity
MainViewModel homeViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
homeViewModel.getResponseval().observe(this, new Observer<String>() {
#Override
public void onChanged(#Nullable String s) {
Toast.makeText(getApplicationContext(), "MainActivityObserverCalled", Toast.LENGTH_LONG).show();
}
});
MainViewModel
public class MainViewModel extends ViewModel {
public MutableLiveData<String> responseval;
private LoginRepositry loginRepositry;
public MainViewModel(){
loginRepositry = new LoginRepositry();
responseval = loginRepositry.getData("username","password");
}
public MutableLiveData<String> getResponseval() {
return responseval;
}
LoginRepositry
public class LoginRepositry {
private MutableLiveData<String> data = new MutableLiveData<>();
public MutableLiveData<String> getData(String username , String userpass) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
data.setValue("Login Repo Set Value");
}
}, 5000);
return data;
}
These are my 2 questions.
Now with each method, I am returning some data of type LiveData, but
at the time of returning the data, the value is not set. I set it after 5
seconds data.setValue("SomeValue"), So what is the use of return
here, is it only because of method return type, and does nothing in
case of LiveData
In MainActivity, i used homeViewModel.getResponseval().observe
to observer data, but in ViewModel, I didn't use observe, but
ViewModel is still observing the Repository and after 5 seconds
MainActivity gets a result. I am unable to understand whats happening here
.
So let's do this by parts:
Now with each method i am returning some data of type LiveData, but at
the time of returning the data, the value is not set. I set it after 5
seconds data.setValue("SomeValue"), So what is the use of return here,
is it only because of method return type, and does nothing in case of
LiveData
If you check the docs you'll see that LiveData "is an observable data holder class", so it holds data and you can observer it. This is very important to understand why you're returning this LiveData object here. By returning it you're telling the next layer of your architecture (the ViewModel) "hey, here is this holder, I will put some data here at some point, so observer it if you want to receive the data".
The ViewModel doesn't observe it, but simply pass it to the next entity interested in this holder of data, the UI (LifeCycleOwner). So in the owner you start to observe this "holder of data" and will be warned when new data arrives.
In MainActivity, i used homeViewModel.getResponseval().observe to
observer data, but in ViewModel, I didn't use observe, but ViewModel
is still observing the Repository, and after 5 seconds MainActivity
gets result. I am unable to understand whats happening here.
You need a LifeCycleOwner in order to observe updates from a LiveData, also from the docs: "LiveData considers an observer, which is represented by the Observer class, to be in an active state if its lifecycle is in the STARTED or RESUMED state. LiveData only notifies active observers about updates." In order to detect the state, it needs a LifeCycleOwner, that's why when you have the observe method you pass this as a parameter. And that's why you didn't use observe in the ViewModel.
What is the difference between those 2 methods of the LiveData class? The official doc and tutorial are pretty vague on that. In the map() method the first parameter called source but in the switchMap() it called trigger. What's the rationale behind that?
As per the documentation
Transformations.map()
Applies a function on the value stored in the LiveData object, and propagates the result downstream.
Transformations.switchMap()
Similar to map, applies a function to the value stored in the LiveData object and unwraps and dispatches the result downstream. The function passed to switchMap() must return a LiveData object.
In other words, I may not be 100% correct but if you are familiar with RxJava; Transformations#map is kind of similar to Observable#map & Transformations#switchMap is similar to Observable#switchMap.
Let's take an example, there is a LiveData which emits a string and we want to display that string in capital letters.
One approach would be as follows; in an activity or fragment
Transformations.map(stringsLiveData, String::toUpperCase)
.observe(this, textView::setText);
the function passed to the map returns a string only, but it's the Transformation#map which ultimately returns a LiveData.
The second approach; in an activity or fragment
Transformations.switchMap(stringsLiveData, this::getUpperCaseStringLiveData)
.observe(this, textView::setText);
private LiveData<String> getUpperCaseStringLiveData(String str) {
MutableLiveData<String> liveData = new MutableLiveData<>();
liveData.setValue(str.toUpperCase());
return liveData;
}
If you see Transformations#switchMap has actually switched the LiveData. So, again as per the documentation The function passed to switchMap() must return a LiveData object.
So, in case of map it is the source LiveData you are transforming and in case of switchMap the passed LiveData will act as a trigger on which it will switch to another LiveData after unwrapping and dispatching the result downstream.
My observation is that, if your transformation process is fast (Doesn't involve database operation, or networking activity), then you can choose to use map.
However, if your transformation process is slow (Involving database operation, or networking activity), you need to use switchMap
switchMap is used when performing time-consuming operation
class MyViewModel extends ViewModel {
final MutableLiveData<String> mString = new MutableLiveData<>();
final LiveData<Integer> mCode;
public MyViewModel(String string) {
mCode = Transformations.switchMap(mString, input -> {
final MutableLiveData<Integer> result = new MutableLiveData<>();
new Thread(new Runnable() {
#Override
public void run() {
// Pretend we are busy
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int code = 0;
for (int i=0; i<input.length(); i++) {
code = code + (int)input.charAt(i);
}
result.postValue(code);
}
}).start();
return result;
});
if (string != null) {
mString.setValue(string);
}
}
public LiveData<Integer> getCode() {
return mCode;
}
public void search(String string) {
mString.setValue(string);
}
}
map is not suitable for time-consuming operation
class MyViewModel extends ViewModel {
final MutableLiveData<String> mString = new MutableLiveData<>();
final LiveData<Integer> mCode;
public MyViewModel(String string) {
mCode = Transformations.map(mString, input -> {
/*
Note: You can't launch a Thread, or sleep right here.
If you do so, the APP will crash with ANR.
*/
/*
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
*/
int code = 0;
for (int i=0; i<input.length(); i++) {
code = code + (int)input.charAt(i);
}
return code;
});
if (string != null) {
mString.setValue(string);
}
}
public LiveData<Integer> getCode() {
return mCode;
}
public void search(String string) {
mString.setValue(string);
}
}
First of all, map() and switchMap() methods are both invoked on the main thread. And they have nothing to do with being used for fast or slow tasks. However, it might cause lags on UI if you do complex computational or time consuming tasks inside these methods instead of a worker thread, parsing or converting a long and/or complex json response for instance, since they are executed on the UI thread.
map()
map() method's code is
#MainThread
public static <X, Y> LiveData<Y> map(#NonNull LiveData<X> source,
#NonNull final Function<X, Y> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
#Override
public void onChanged(#Nullable X x) {
result.setValue(func.apply(x));
}
});
return result;
}
What it does is, it uses a source LiveData, I is input type, and calls setValue(O) on LiveData where O is output type.
For it to be clear let me give an example. You wish to write user name and last name to textView whenever a user changes.
/**
* Changes on this user LiveData triggers function that sets mUserNameLiveData String value
*/
private MutableLiveData<User> mUserLiveData = new MutableLiveData<>();
/**
* This LiveData contains the data(String for this example) to be observed.
*/
public final LiveData<String> mUserNameLiveData;
now let's trigger changes on mUserNameLiveData's String when mUserLiveData changes.
/*
* map() method emits a value in type of destination data(String in this example) when the source LiveData is changed. In this example
* when a new User value is set to LiveData it trigger this function that returns a String type
*
* Input, Output
* new Function<User, String>
*
* public String apply(User input) { return output;}
*/
// Result<Output> Source<Input> Input, Output
mUserNameLiveData = Transformations.map(mUserLiveData, new Function<User, String>() {
#Override
public String apply(User input) {
// Output
return input.getFirstName() + ", " + input.getLastName();
}
});
And let's do the same thing with MediatorLiveData
/**
* MediatorLiveData is what {#link Transformations#map(LiveData, Function)} does behind the scenes
*/
public MediatorLiveData<String> mediatorLiveData = new MediatorLiveData<>();
/*
* map() function is actually does this
*/
mediatorLiveData.addSource(mUserLiveData, new Observer<User>() {
#Override
public void onChanged(#Nullable User user) {
mediatorLiveData.setValue(user.getFirstName() + ", " + user.getLastName());
}
});
And if you observe MediatorLiveData on Activity or Fragment you get the same result as observing LiveData<String> mUserNameLiveData
userViewModel.mediatorLiveData.observe(this, new Observer<String>() {
#Override
public void onChanged(#Nullable String s) {
TextView textView = findViewById(R.id.textView2);
textView.setText("User: " + s);
Toast.makeText(MainActivity.this, "User: " + s, Toast.LENGTH_SHORT).show();
}
});
switchMap()
switchMap() returns the same MediatorLiveData not a new LiveData every time the SourceLiveData changes.
It's source code is
#MainThread
public static <X, Y> LiveData<Y> switchMap(#NonNull LiveData<X> trigger,
#NonNull final Function<X, LiveData<Y>> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(trigger, new Observer<X>() {
LiveData<Y> mSource;
#Override
public void onChanged(#Nullable X x) {
LiveData<Y> newLiveData = func.apply(x);
if (mSource == newLiveData) {
return;
}
if (mSource != null) {
result.removeSource(mSource);
}
mSource = newLiveData;
if (mSource != null) {
result.addSource(mSource, new Observer<Y>() {
#Override
public void onChanged(#Nullable Y y) {
result.setValue(y);
}
});
}
}
});
return result;
}
Basically what it does is, it creates a final MediatorLiveData and it's set to the Result like map does() but this time function returns LiveData
public static <X, Y> LiveData<Y> map(#NonNull LiveData<X> source,
#NonNull final Function<X, **Y**> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
#Override
public void onChanged(#Nullable X x) {
result.setValue(func.apply(x));
}
});
return result;
}
#MainThread
public static <X, Y> LiveData<Y> switchMap(#NonNull LiveData<X> trigger,
#NonNull final Function<X, **LiveData<Y>**> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(trigger, new Observer<X>() {
LiveData<Y> mSource;
#Override
public void onChanged(#Nullable X x) {
LiveData<Y> newLiveData = func.apply(x);
if (mSource == newLiveData) {
return;
}
if (mSource != null) {
result.removeSource(mSource);
}
mSource = newLiveData;
if (mSource != null) {
result.addSource(mSource, new Observer<Y>() {
#Override
public void onChanged(#Nullable Y y) {
result.setValue(y);
}
});
}
}
});
return result;
}
So map() takes LiveData<User> and transforms it into a String, if User object changes name field changes for instance.
switchMap() takes a String and gets LiveData<User> using it. Query a user from web or db with a String and get a LiveData<User> as a result.
There are already some good answers above, but I still struggled with them till I understood it, so I will try to explain on a concrete example for people with my way of thinking, without going into technical details and code.
In both map and switchMap there is a source (or trigger) live data, and in both cases you want to transform it to another live data. Which one will you use - depends on the task that your transformation is doing.
map
Consider the same simple example that is used everywhere - your source live data contains a User object - LiveData<User>, which points to the currently logged in user. You want to display a text in your UI saying Current user: <USERNAME>. In this case each change signal from source should trigger exactly one signal of the resulting "mapped" LiveData. For example, the current User object is "Bob" then the UI text shows Current user: Bob. Once your LiveData<User> triggers a change your UI will observe it and update text to Current user: Alice. Very simple, linear, one to one change.
switchMap
Consider the following example - you want to create a UI which shows the users whose name matches the given search term. We can be quite smart about it and hold the search term as a LiveData! So it will be a LiveData<String> and every time the user inputs a new query string our Fragment/Activity will simply set the text input value to this live data in the ViewModel. As a result, this live data will fire a change signal. Once we get this signal we start searching for the users. Now let's consider our search is so fast that it immediately returns a value. At this point you think that you can just use a map and return the matching users which will update the UI. Well, you will have a bug now - imagine you update the database regularly and after next update more users appear matching the search term! As you can see, in this scenario the source trigger (search term) does not necessarily result in a single trigger of mapped live data, the mapped live data given to the UI might still need to continue triggering the values after new users are added to the database. At this point you might say, that we could return a "smarter" live data, which will not only wait for source triggers, but will also monitor the database for users matching the given term (you will be able to do that with Room DB out of the box). But then comes another question - what if the search term changes? So your term was x, it triggered a live data which queries the users and keeps an eye on the database, it returns userx, userxx and then after five minutes it returns userx, userxxx and so on. Then the term was changed to y. Now we need to somehow stop listening to the smart live data giving us users with x in it, and switch it with the new smart live data which will monitor and give us users with y in their names. And that is exactly what switchMap is doing! And notice, this switch needs to be done in such a way, that in your UI you just write switchMap(...).observe once, that means that switchMap must return a wrapper LiveData which will stay the same throughout the execution, but will switch the live data sources under the hood for us.
Conclusion
Although they seem to look the same at first glance, the use cases for map and switchMap are different, you will get the feeling of which one to use once you start implementing your case, mostly when you realize that in you mapping function you have to call some code from your other modules (like Repositories) which return LiveData.
Map() is conceptually identical to the use in RXJava, basically you are changing a parameter of LiveData in another one
SwitchMap() instead you are going to substitute the LiveData itself with another one! Typical case is when you retrieve some data from a Repository for instance and to "eliminate" the previous LiveData (to garbage collect, to make it more efficient the memory usually) you pass a new LiveData that execute the same action( getting a query for instance)
switchMap :
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…?
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.
but map is static it used when you don't forced to get new live data every time
With map you have same source livedata in the end but it's data (value) changes with provided function before emitting
With switchMap, you use source livedata just as a trigger for returning a standalone livedata (of course you can use triggers data in your function input)
Trigger: everything that causes livedata's observer's onChanged() invoking
Transformation.map()
fun <X, Y> map(trigger: LiveData<X>, mapFunction: Function<X, Y> ): LiveData<Y>?
trigger - the LiveData variable that once changed triggers mapFunction to execute.
mapFunction - the function to call when a change take place on the trigger LiveData. Parameter X is a reference to trigger (via it). The function returns a result of specified type Y, which ultimately is returned by map() as a LiveData object.
Use map() when you want to perform an operation (via mapFunction) when the trigger LiveData variable changes. map() will return a LiveData object that should be observed for the mapFunction to be called.
Example:
Assume a simple list of bowler names, their average and their average with handicap:
data class Bowler(val name:String, val average:Int, var avgWHDCP:Int)
var bowlers = listOf<Bowler>(Bowler("Steve", 150,150), Bowler ("Tom", 210, 210))
Assume a MutableLiveData Int variable that holds a handicap increment value. When this value changes, avgWHDCP for all bowlers in the list needs to be re-computed. Initially it is set to zero.
var newHDCP:MutableLiveData<Int> = MutableLiveData(0)
Create a variable that calls Tranformation.map(). It’s first argument is newHDCP. It’s second argument is the function to be called when newHDCP changes. In this example, the function will iterate through all the bowler objects, compute the new avgWHDCP for each bowler in the bowlers list, and return the result as an observable list of LiveData Bowler objects. Note that in this example, the original non-LiveData list of bowlers and the returned list of bowlers will reflect the same value, as they are referencing the same data store. However, the result of the function is observable. The original list of bowlers is not as it was not setup as a LiveData.
var updatedBowlers: LiveData<List<Bowler>> = Transformations.map(newHDCP) {
bowlers.forEach { bowler ->
bowler.avgWHDCP = bowler.average + it
}
return#map bowlers
}
Somewhere in your code, add a method to update newHDCP. In my example, when a radio button is clicked, newHDCP will get changed, and the process will trigger to call the function specified in Transformations.map()
rbUpdateBy20.setOnCheckedChangeListener { _, isChecked ->
viewModel.bowlingBallObject.newHDCP.value = 20
}
Finally, all this will only work if updatedBowlers is observed. This would be placed in your Activity or Fragment in a method such as OnViewCreated()
viewModel.updatedBowlers.observe(viewLifecycleOwner, Observer { bowler ->
if (viewLifecycleOwner.lifecycle.currentState == Lifecycle.State.RESUMED) {
refreshRecycler()
}
})
If you wanted to get a little more concise and you really didn’t need a live reference to updatedBowlers, here’s how you can combine updateBowlers with the observer:
Transformations.map(viewModel.newHDCP) {
viewModel.bowlers.forEach { bowler ->
bowler.avgWHDCP = bowler.average + it
}
return#map viewModel.bowlers
}.observe(viewLifecycleOwner, Observer { bowler ->
if (viewLifecycleOwner.lifecycle.currentState == Lifecycle.State.RESUMED) {
refreshRecycler()
}
})
And that’s basically it. Anytime you change the value of newHDCP, the function specified in Transformation.map() will get called, it will transform the bowler object with the newly computed avgWHDCP and return a LiveData object of List<Bowler>
Transformation.switchMap()
fun <X, Y> switchMap(source: LiveData<X>, switchMapFunction: Function<X, LiveData<Y>!>): LiveData<Y>
source - the LiveData variable that once changes triggers switchMapFunction to execute.
switchMapFunction - the function to call when a change take place on the source LiveData. Parameter X is reference to the same source object (via it). The switchMapFunction function MUST returns a LiveData result, which effectively gets returned via Transformation.switchMap(). In essence, this allows you to swap out one reference of a LiveData container object for another.
Use switchMap() when you have a variable referencing a LiveData object, and you want to switch that variable to another, or to say it a different way you want to refresh the existing LiveData container. This is useful, for example, if your LiveData variable is referencing a database data store and you want to requery with different parameters. switchMap allows you to re-execute the query and replace with a new LiveData results.
Example:
Assume a database repository with a bunch of bowling ball queries from a BowlingBall DAO table:
private val repository = BowlingBallRepository(application)
And I want to execute a query that fetches active or inactive bowling balls, depending on what the user specifies. Through the UI, the user can select active or inactive, so my query needs to handle both. So I create a MutableLiveData variable that hold an active or inactive status. In this example, I default to ‘A’ for active.
var activeFlag:MutableLiveData<String> = MutableLiveData(“A”)
Now, we need a LiveData variable that will hold the result of my query to get all the bowling balls of a specific status. So I create a variable called allBowlingBalls of type LiveData<List<BowlingBallTable>>? and assign it to Transformation.switchMap. I pass to the switchMap function the activeFlag variable as well as a lambda function that will receive that same activeFlag variable (via it) and the function makes a call to a query in the DB repository to re-fetch all bowling balls with the passed status. The LiveData result of the lambda function passes back through the switchMap method and is re-assigned to allBowlingBalls.
private var allBowlingBalls: LiveData<List<BowlingBallTable>>? = Transformations.switchMap(activeFlag) {repository.getAllBalls(it)}
I need a way to trigger a refresh of allBowlibgBalls. Again, this will be done when activeFlag changes. Somewhere in your code, add a function to update activeFlag. In my example, when a radio button is clicked, activeFlag will get changed, and the process will trigger to call the function specified in Transformations.switchMap()
rbActive.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
viewModel.activeFlag.value = ActiveInactive.ACTIVE.flag
refreshRecycler()
}
}
Finally, all this will only work if allBowlingBalls is observed. So first create a function to fetch allBowlingBalls:
fun getAllBowlingBalls():LiveData<List<BowlingBallTable>>? {
return allBowlingBalls
}
Then place an observer on getAllBowlingBalls():
viewModel.getAllBowlingBalls()?.observe(viewLifecycleOwner, Observer { balls ->
if (viewLifecycleOwner.lifecycle.currentState == Lifecycle.State.RESUMED) {
refreshRecycler()
}
})
And that’s it’s it. Every time activeFlag changes, allBowlingBalls will be refreshed with a call to the repository and the onChange event of the observer on allBowlingBalls will trigger. A simple technique for essentially building a dynamic search engine.
Let me explain what I understood with an example. Consider a student data class
data class Student(val name: String, val marks: Int)
Transformation.map()
Transforms the value of LiveData into another. It takes the value, applies the Function on the value, and sets the Function’s output as a value on the LiveData it returns. Here’s a example of how this can be used for the above data class:
val student: LiveData<Student> = (get liveData<Student> from DB or network call)
val studentName: LiveData<String> = Transformations.map(student) {it.name}
Here we get a student LiveData from a network or DB and then we take the value from the LiveData which is the Student object and just get the name of the student and maps it to another LiveData.
Transformation.switchMap()
Transforms the value of a LiveData into another LiveData. Consider we want to implement a search feature for Students. Every time the search text changes we want to update search results. The following code shows how that works.
val searchQuery: LiveData<String> = ...
val searchResults: LiveData<List<Student>> =
Transformations.switchMap(searchQuery) { getSearchResults(it) }
fun getSearchResults(query: String): LiveData<List<Student>> = (get liveData<List<Student>> from DB or network call)
So here every time there is a new value in searchQuery, getSearchResults will be called with a new search query and searchResults will be updated.
In short, the naming is analogous to rx map/switchMap.
Map is 1 to 1 mapping which is easy to understand.
SwitchMap on the other hand only mapping the most recent value at a time to reduce unnecessary compute.
Hope this short version of answer can solve everyone's problem easily.
To my experience, both are to build a bridge with what you update (livedata #1) and what you really care/observe (livedata #2) in return. This bridge is necessary so that you can carry the lifecycle of the observer (i.e. your fragment) down to view models and they then can drop the subscription on all the LiveData involved in automatically. This is one of the main promises of LiveData from the beginning. So, this will keep that promise.
In case of switchMap the bridge is dynamic meaning there's always a new LiveData returned from the function (the lambda) - so you switch to this new LiveData. With map it's static.
I hope it helps a bit.
They have different Use case:
if you have a source LiveData and you just want to change the value inside that LiveData into some other data type, use map
If you have a source LiveData and a function that return a LiveData, and you want to create a LiveData that updates value base on the LiveData returned by that function. Use switchMap
Analyzing the source code, we see both switchmap and map return a new instance of MediatorLiveData.
map takes in a function that return a new value for that MediatorLiveData while switchmap takes in a function that return a new instance of LiveData (and then if the value of that new instance of LiveData change, use that to update MediatorLiveData's value)
in another word, switchmap's LiveData value change if that input function's LiveData value change, switchmap also have the added benefit of unregistering the previous LiveData return from that input function.
Here is a brief
If you are expecting the result value to change repeatedly use swithMap()
and if it is just one time operation use map() instead .
Example : If you want to show scores of a live game use swithMap() .
If you want to show list of player of a team use map()