In my application I return LiveData from Room database (SQLite) in repository, and observe the data on my application Activity.
The problem is: having LiveData in Activity that observes changes in database using MVVM model, and runs some code when data is changed (as this is how observe works).
The method looks like this in repository:
public LiveData<TourWithAllGeoPoints> getTourWithAllGeoPoints(long tourId, boolean mIsFirstTime) {
if (!mIsFirstTime) {
return tourWithAllGeoPoints;
}
MyTourAssistentDatabase.databaseWriteExecutor.execute(()-> {
tourWithAllGeoPoints = toursDAO.getTourWithAllGeoPoints(tourId); //this part finishes after reuturn
});
return tourWithAllGeoPoints; //this part returns
}
mIsFirstTime checks if the Activity (or Fragment) is loading first time or not (if Bundle is null or not).
databaseWriteExecutor.execute() is a ThreadPool executing the code in own thread.
toursDAO.getTourWithAllGeoPoints(tourId) is where I ask and get data from Room database. It returns a LiveData object.
In Activity code I do observe the LiveData:
activeTourViewModel.getTourWithAllGeoPoints(tourId, mIsFirstTime).observe(this, geoPointsPlanned -> {
//Some code here changing UI, other variables, etc.
}
But the problem is that the method returns 'tourWithAllGeoPoints' before the execute() part is finished. So this means it returns an empty LiveData. Or the LiveData we observe on MainActivity is not the same LiveData we get from toursDAO.
And so in Activity it observes the empty LiveData.
My attempted solutions are:
1) I can run the query in main thread like this:
public LiveData<TourWithAllGeoPoints> getTourWithAllGeoPoints(long tourId, boolean mIsFirstTime) {
if (!mIsFirstTime) {
return tourWithAllGeoPoints;
}
tourWithAllGeoPoints = toursDAO.getTourWithAllGeoPoints(tourId);
return tourWithAllGeoPoints;
}
But then it gives warning message about not to run queries to Room database on main thread as it may take long time.
2) Or I can make the toursDAO.getTourWithAllGeoPoints(tourId) return a TourWithAllGeoPoints object rather than a LiveData, and put it into a LiveDataobject, like this:
public LiveData<TourWithAllGeoPoints> getTourWithAllGeoPoints(long tourId, boolean mIsFirstTime) {
if (!mIsFirstTime) {
return tourWithAllGeoPoints;
}
MyTourAssistentDatabase.databaseWriteExecutor.execute(()-> {
TourWithAllGeoPoints twagp = toursDAO.getTourWithAllGeoPoints(tourId);
tourWithAllGeoPoints.postValue(twagp)
});
return tourWithAllGeoPoints;
}
So that it observes the changes in LiveData. But then I can't observe the changes made in database, since it just returns a List. This means I have to run the same method every time I make a change in the database.
3) Or I can put a LiveData inside a LiveData, also like this:
public LiveData<LiveData<TourWithAllGeoPoints>> getTourWithAllGeoPoints(long tourId, boolean mIsFirstTime) {
if (!mIsFirstTime) {
return tourWithAllGeoPoints;
}
MyTourAssistentDatabase.databaseWriteExecutor.execute(()-> {
LiveData<TourWithAllGeoPoints> twagp = toursDAO.getTourWithAllGeoPoints(tourId); //returns LiveData
tourWithAllGeoPoints.postValue(twagp)
});
return tourWithAllGeoPoints;
}
But I don't know if putting LiveData inside a LiveData is a good idea or not.
Or the are other solutions. But how can I solve this problem?
The problem is: having LiveData in Activity that observes changes in database using MVVM model, and runs some code when data is changed (as this is how observe works).
For the specific problem you described (i.e. returning the first TourWithAllGeoPoints and nothing else), it seems LiveData isn't the most appropriate data type you can use here. LiveData is meant to be used when, as the name says, the underlying data is live and it could change anytime, and you need to observe the data everytime it changes. If all you need is one value, it's better not to use LiveData at all. Just make your DAO method getTourWithAllGeoPoints return TourWithAllGeoPoints (without LiveData) and call it from a background thread. Take a look at this link for some ways to do that. It's much easier to use Kotlin coroutines in this case, but you'd need to be using Kotlin for that (which I recommend :) ).
But if the problem you described is generic (not exactly just for returning one value once), you can use a MediatorLiveData to observe a LiveData and post something different (or not) every time it emits a new value. Take a look at this code:
private MediatorLiveData<TourWithAllGeoPoints> mediator;
public YourRepositoryConstructor() {
mediator = new MediatorLiveData<>();
mediator.addSource(toursDAO.getTourWithAllGeoPoints(tourId), data -> {
if (mediator.getValue() != null) {
mediator.setValue(data);
}
});
return mediator;
}
public LiveData<TourWithAllGeoPoints> getTourWithAllGeoPoints(long tourId, boolean mIsFirstTime) {
return mediator;
}
A MediatorLiveData observes one (or many) other LiveData objects and emits a new value according to the changes of the other LiveData objects. It may emit a different data type (i.e. it doesn't have to be the same type of the underlying LiveData objects) or even not emit anything at all. It's all according to your MediatorLiveData code. In this case specifically, every time the result of getTourWithAllGeoPoints emits something new, you MediatorLiveData will react to that and only emit a new value in itself if it's the first time. It can do that by checking if it's value is null, it doesn't need the variable mIsFirstTime (unless null is a valid value for you in that case).
The MediatorLiveData is a more generic approach suitable for the type of scenario you described, but it may be too much effort if you only need one result for that query, which you might solve by not using LiveData at all.
I have a two queries which return two long values. I am setting these two long values to be displayed in individual text views. Finally I have a third text view which displays the combined value of both longs. I am having a problem getting the combined total to show as its setting the value before the livedata is returned.
Below is a snippet of the code
private void getData() {
mViewModelReframing.totalWorkouts(pkUserId).observe(getViewLifecycleOwner(), new Observer<List<ModelStatsTotalWorkouts>>() {
#Override
public void onChanged(List<ModelStatsTotalWorkouts> modelStatsTotalWorkouts) {
for (ModelStatsTotalWorkouts list : modelStatsTotalWorkouts) {
totalReframeWorkouts = list.getTotalWorkouts();
}
if (totalReframeWorkouts == 0) {
tvTotalReframes.setText(0 + getString(R.string.workouts_empty));
} else {
tvTotalReframes.setText("" + totalReframeWorkouts);
}
}
});
mViewModelCheckIn.totalWorkouts(pkUserId).observe(getViewLifecycleOwner(), new Observer<List<ModelStatsTotalWorkouts>>() {
#Override
public void onChanged(List<ModelStatsTotalWorkouts> tableCheckIns) {
for (ModelStatsTotalWorkouts list : tableCheckIns) {
totalCheckInWorkouts = list.getTotalWorkouts();
}
tvTotalCheckIns.setText("" + totalCheckInWorkouts);
// Combine both longs together for a combined total.
totalWorkouts = totalReframeWorkouts + totalCheckInWorkouts;
tvTotalWorkouts.setText("" + totalWorkouts);
}
});
}
Is there a better way to write the logic to achieve the desired result without the issue of the livedata not being returned fast enough?
Whenever you use independent Reactive streams like this (LiveData, RxJava, etc) you are going to have race conditions. You need to make explicit the dependencies for an action to happen - in this case your ability to update the UI in the way that you want had dependencies on BOTH APIs returning. This is the RxJava equivalent of zip. A few tips:
Consider using only a single Viewmodel for a view. The viewmodel should really be preparing data for your view specificially. In this case, it should really be that singular ViewModel that handles combining this data before passing it to your vew at all.
Barring that, since you've chosen LiveData here, you can do what you want by using a MediatorLiveData. Essentially, it acts as a composite stream source that depends on whichever other LiveData streams you add to it as described by that article. In this way, you can explicitly wait for all the needed values to arrive before you try to update the UI.
I solved the question by using this method:
public LiveData<List<ModelStatsTotalWorkouts>> totalWorkoutsCombined(long userId) {
LiveData liveData1 = database.getUsersDao().totalReframeWorkouts(userId);
LiveData liveData2 = database.getUsersDao().totalCheckInWorkouts(userId);
MediatorLiveData liveDataMerger = new MediatorLiveData<>();
liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));
return liveDataMerger;
}
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()
So, I have an Android app that uses realm.io. I have to run queries asynchronously like this :
public static void getProductsByCategoryId(Realm realm,
String categoryId,
OrderedRealmCollectionChangeListener<RealmResults<Product>> callback) {
RealmResults<Product> result = realm.where(Product.class)
.equalTo(CATEGORY, categoryId)
.findAllAsync();
result.addChangeListener(callback);
}
The callback will process this response, but then I need to run another query in sequence. So, you'll have queryA => process response => queryB => process response. So, the callback may have code like this
.....
getProductsByCategoryId(app.getRealmInstance(), "ABC123", firstCallback);
.....
private OrderedRealmCollectionChangeListener<RealmResults<Product>> firstCallback = new OrderedRealmCollectionChangeListener<RealmResults<Product>>() {
#Override
public void onChange(RealmResults<Product> realmProducts, OrderedCollectionChangeSet changeSet) {
mProdList.addAll(mRealm.copyFromRealm(realmProducts));
// get more product info (2nd call)
MainApplication.getMoreProductInfo(mRealm, mCatId, false, secondCallback);
}
};
Currently, my understanding is that you would run queryB in the callback of queryA ? Looking at the requirements for the app, I will end up with chains of 3 or 4 queries. Is this an appropriate approach, or is there a specific pattern I should be using ? I haven't found any guidance yet in the Realm documentation.
It's generally an indication of bad schema design if you need to do multiple queries in order to retrieve your result set, because the way Realm works is that if you can define your query results with one query (and you don't use realm.copyFromRealm() which you generally don't need to use anyways), then its elements and the results itself are all lazy-loaded.
If you cannot accomplish that, then even then, generally you probably shouldn't chain find*Async calls, because any RealmResults that you don't store as a field variable has a chance of being consumed by GC, and its change listener won't be called when isLoaded() is true (because said RealmResults no longer exists).
So what you really seem to want to do is just execute multiple queries on a background thread then return copied results to the main thread, in which case it'd just look like this
Executor executor = Executors.newSingleThreadedPool(); // or some other pool
Handler handler = new Handler(Looper.getMainLooper());
public void getQueryResults(DataLoadedCallback callback) {
executor.execute(() -> {
try(Realm realm = Realm.getDefaultInstance()) {
realm.refresh(); // <-- might not be necessary
RealmResults<XYZ> results1 = realm.where(XYZ.class)./*...*/.findAll();
RealmResults<ZXY> results2 = realm.where(ZXY.class)./*...*/.findAll();
RealmResults<YZX> results3 = realm.where(YZX.class)./*...*/.findAll();
List<Something> someList = new LinkedList<>();
for/*do magic transform things*/
someList.add(blah /* blah is not a managed RealmObject */);
}
handler.post(() -> {
callback.onDataLoaded(Collections.unmodifiableList(new ArrayList<>(someList)));
});
}
});
}
Chaining queries in the callbacks are fine and "should just work", but it would be far more efficient if you can express what you want is as few queries as possible.
Ideally, we should have a query language that is powerful enough to express everything you want in one query. We are not fully there yet, but we would be very interested to hear more about what specific requirements you have.
Also, it isn't clear why you are using copyFromRealm in the method you posted, but in an ideal situation that shouldn't be necessary.