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;
}
I'm working on an application that fetches data from a graphql server via apollo-android.
I do a single fetch on my aws rds database. I do this fetch right at the onCreate() of my CalendarFragment.
The thing is, at onViewCreated(), I want to set my textview to one of the fields that is fetched, first and last name. So, I run my getBarberFullName method which returns the String value of mBarberFullName. I'm trying to follow the UI controller displays while the view model handles all the logic approach. getBarberFullName resides within my ViewModel.
public String getBarberFullName() {
if (appointmentsAreNull()) return mBarberFullName.getValue();
AppointmentModel am = mMasterAppointments.getValue().get(0);
String fullName = am.bFirstName;
fullName = fullName.concat(" " + am.bLastName);
// Get the logged in barber's full name and set it as mBarberFullName.
mBarberFullName.setValue(fullName);
return mBarberFullName.getValue();
}
where mMasterAppointments is a MutableLiveData<List<AppointmentModel>>. In my onViewCreated() callback, I run
String barberName = mBarberViewModel.getBarberFullName();
mTxtv_barberName.setText(barberName);
However, mMasterAppointments is always null so it just returns the default value of mBarberFullName which is a String.
However, if I were to run the following code, in the same onViewCreated(), I get the desired result where the textview is updated with the desired barber's full name.
mBarberViewModel.getAllAppointments().observe(getViewLifecycleOwner(), am -> {
if (am.isEmpty()) {
Log.d(TAG, "No barber.");
return;
}
String barberGreeting;
barberGreeting = am.get(0).bFirstName;
barberGreeting = barberGreeting.concat(" " + am.get(0).bLastName);
mTxtv_barberName.setText(barberGreeting);
});
getAllAppointments returns an observer to mMasterAppointments located in my ViewModel.
Although getAllAppointments and getBarberFullName are called within onViewCreated(), one is able to access the pending values of mMasterAppointments while the other is not. Why?
I don't want to do the logic in my Fragments onViewCreated callback, so how can I wait on the pending mMasterApointmentData in my ViewModel's getBarberFullName()? Are there tools within LiveData and ViewModel that would aid me in this situation?
Use LiveData's Transformations class
when you need to perform calculations, display only a subset of the
data, or change the rendition of the data.
First add a new String LiveData for BarberFullName in the viewmdoel, and give it the value of transforming (mapping) the source LiveData mMasterAppointments into the desired String:
val fullBarberName: LiveData<String> = Transformations.map(mMasterAppointments) { am ->
" ${am[0].bFirstName} ${am.get(0).bLastName}"
}
Now you can observe this String LiveData in your fragment, the way you in did your second snippet.
Note that the code I provided is in Kotlin, I use it nowadays. I hope you get it.
My database query operations can take a long time, so I want to display a ProgressBar while the query is in progress. This is especially a problem when the user changes the sorting options, because it displays the old list for a while until the new list comes in and the RecyclerView is updated. I just don't know where to capture the Loading and Success states for a query like this.
Here's my method for getting the PagedList from the database:
fun getGameList(): LiveData<PagedList<Game>> {
// Builds a SimpleSQLiteQuery to be used with #RawQuery
val query = buildGameListQuery()
val dataSourceFactory: DataSource.Factory<Int, Game> = database.gameDao.getGameList(query)
val data: LiveData<PagedList<Game>> = LivePagedListBuilder(dataSourceFactory, DATABASE_PAGE_SIZE)
.build()
return data
}
And I update my list by observing this:
val games = Transformations.switchMap(gameRepository.sortOptions) {
gameRepository.getGameList()
}
Do I need a custom DataSource and DataSource.Factory? If so, I have no idea where to even begin with that. I believe it would be a PositionalDataSource, but I can't find any examples online for implementing a custom one.
I also tried adapter.registerAdapterDataObserver() on my RecyclerView adapter. This fires various callbacks when the new list data is being displayed, but I can't discern from the callbacks when loading has started and stopped.
I was ultimately able to fix this by observing the games LiveData. However, it wasn't exactly straightforward.
Here's my DatabaseState class:
sealed class DatabaseState {
object Success : DatabaseState()
object LoadingSortChange: DatabaseState()
object Loading: DatabaseState()
}
Capturing the Loading state was easy. Whenever the user updates the sort options, I call a method like this:
fun updateSortOptions(newSortOptions: SortOptions) {
_databaseState.value = DatabaseState.LoadingSortChange
_sortOptions.value = newSortOptions
}
The Success state was the tricky one. Since my sorting options are contained in a separate Fragment from the RecyclerView, the games LiveData observer fires twice upon saving new sort options (once when ListFragment resumes, and then again a bit later once the database query is completed). So I had to account for this like so:
/**
* The observer that triggers this method fires once under normal circumstances, but fires
* twice if the sort options change. When sort options change, the "success" state doesn't occur
* until the second firing. So in this case, DatabaseState transitions from LoadingSortChange to
* Loading, and finally to Success.
*/
fun updateDatabaseState() {
when (databaseState.value) {
Database.LoadingSortChange -> gameRepository.updateDatabaseState(DatabaseState.Loading)
DatabaseState.Loading -> gameRepository.updateDatabaseState(DatabaseState.Success)
}
}
Finally, I needed to make some changes to my BindingAdapter to smooth out some remaining issues:
#BindingAdapter("gameListData", "databaseState")
fun RecyclerView.bindListRecyclerView(gameList: PagedList<Game>?, databaseState: DatabaseState) {
val adapter = adapter as GameGridAdapter
/**
* We need to null out the old list or else the old games will briefly appear on screen
* after the ProgressBar disappears.
*/
adapter.submitList(null)
adapter.submitList(gameList) {
// This Runnable moves the list back to the top when changing sort options
if (databaseState == DatabaseState.Loading) {
scrollToPosition(0)
}
}
}
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()