I could not find any information, if it's a bad idea to use LiveData without a lifecycle owner. And if it is, what could be the alternative?
Let me give you just a simple example
class Item() {
private lateinit var property: MutableLiveData<Boolean>
init {
property.value = false
}
fun getProperty(): LiveData<Boolean> = property
fun toggleProperty() {
property.value = when (property.value) {
false -> true
else -> false
}
}
}
class ItemHolder {
private val item = Item()
private lateinit var observer: Observer<Boolean>
fun init() {
observer = Observer<Boolean> { item ->
updateView(item)
}
item.getProperty().observeForever(observer)
}
fun destroy() {
item.getProperty().removeObserver(observer)
}
fun clickOnButton() {
item.toggleProperty();
}
private fun updateView(item: Boolean?) {
// do something
}
}
You can register an observer without an associated LifecycleOwner object using the
observeForever(Observer) method
like that:
orderRepo.getServices().observeForever(new Observer<List<Order>>() {
#Override
public void onChanged(List<Order> orders) {
//
}
});
You can register an observer without an associated LifecycleOwner object using the observeForever(Observer) method. In this case, the observer is considered to be always active and is therefore always notified about modifications. You can remove these observers calling the removeObserver(Observer) method.
Ref
https://developer.android.com/topic/libraries/architecture/livedata.html#work_livedata
For me LiveData has two benefits:
It aware of life cycle events and will deliver updates only in an appropriate state of a subscriber (Activity/Fragment).
It holds the last posted value, and updates with it new subscribers.
As already been said, if you're using it out of the life cycle components (Activity/Fragment) and the delivered update could be managed anytime, then you can use it without life cycle holder, otherwise, sooner or later, it may result in a crash, or data loss.
As an alternative to the LiveData behavior, I can suggest a BehaviourSubject from RxJava2 framework, which acts almost the same, holding the last updated value, and updating with it new subscribers.
Related
For example, let's say that we have a product catalog view with an option to add product to a cart.
Each time when user clicks add to cart, a viewModel method addToCart is called, that could look like this:
//inside viewModel
fun addToCart(item:Item): LiveData<Result> = liveData {
val result = repository.addToCart(item) // loadUser is a suspend function.
emit(result)
}
//inside view
addButton.onClickListener = {
viewModel.addToCart(selectedItem).observe (viewLifecycleOwner, Observer () {
result -> //show result
}
}
What happens after adding for example, 5 items -> will there be 5 livedata objects in memory observed by the view?
If yes, when will they be cleanup? And if yes, should we avoid livedata builder for one-shot operations that can be called multiple times?
Your implementation seems wrong! You are constantly returning a new LiveData object for every addToCard function call. About your first question, it's a Yes.
If you want to do it correctly via liveData.
// In ViewModel
private val _result = MutableLiveData<Result>()
val result: LiveData<Result>
get() = _result;
fun addToCart(item: Item) {
viewModelScope.launch {
// Call suspend functions
result.value = ...
}
}
// Activity/Fragment
viewModel.result.observe(lifecycleOwner) { result ->
// Process the result
...
}
viewModel.addToCart(selectedItem)
All you have to do is call it from activity & process the result. You can also use StateFlow for this purpose. It also has an extension asLiveData which converts Flow -> LiveData as well.
According to LiveData implementation of:
public void observe(#NonNull LifecycleOwner owner, #NonNull Observer<? super T> observer) {
assertMainThread("observe");
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
owner.getLifecycle().addObserver(wrapper);
}
a new Observer (wrapper) is added every time you observe a LiveData. Looking at this I would be carefull creating new Observers from a view (click) event. At the moment I can not tell if a Garbage Collector can free this resources.
As #kaustubhpatange mentioned, you should have one LiveData with a state/value that can be changed by the viewModel, with every new result. That LiveData can be observed (once) in your Activity or Fragment onCreate() function:
fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.result.observe(lifecycleOwner) { result ->
// handle the result
}
}
Using MutableLiveData in your ViewModel, you can mostly create LiveData only once, and populate it later with values from click events, responses etc.
TL;DR
If your operation is One-Shot use Coroutine and LiveData.
If your operation serving with Streams you can use Flow.
For one-shot operations, your approach it's OK.
I think with liveData builder there is no any Memory leak.
If you use for example private backing property for LiveData and observe an public LiveData it might occurs different behavior like get latest value before assign new value to that.
Scenario
Hi,
I have an Activity with a ViewPager. In the ViewPagerAdapter, I create instances of a same fragment with different data.
And in each instance I initialize a ViewModel
val dataViewModelFactory = this.activity?.let { DataViewModelFactory(it) }
mainViewModel = ViewModelProviders.of(this, dataViewModelFactory).get(MainViewModel::class.java)
In my fragment, I observe two MutableLiveData when I call APIs
mainViewModel.isResponseSuccessful.observe(this, Observer { it ->
if(it) {
//do Something
}else{
Toast.makeText(activity, "Error in Sending Request", Toast.LENGTH_SHORT).show()
}
})
mainViewModel.isLoading.observe(this, Observer {
if (it) {
println("show progress")
} else {
println("dismiss progress")
}
})
In each fragment, on a button click I load another fragment. And if required call and API to fetch data.
PROBLEM
The code comes to the observe block multiple times in my fragment. When I comeback from one fragment to previous fragment, even though no API is called, the code on observe block is executed.
What I tried
I tried using an activity instance in the ViewModel initialization
mainViewModel = ViewModelProviders.of(activity,dataViewModelFactory).get(MainViewModel::class.java)
But it did not work.
Please help,
If you want to prevent multiple calls of your observer u can just change MutableLiveData to SingleLiveEvent. Read this
It might help you:
import java.util.concurrent.atomic.AtomicBoolean
class OneTimeEvent<T>(
private val value: T
) {
private val isConsumed = AtomicBoolean(false)
private fun getValue(): T? =
if (isConsumed.compareAndSet(false, true)) value
else null
fun consume(block: (T) -> Unit): T? =
getValue()?.also(block)
}
fun <T> T.toOneTimeEvent() =
OneTimeEvent(this)
First, when you want to post a value on LiveData, use toOneTimeEvent() extension function to wrap it in a OneTimeEvent:
liveData.postValue(yourObject.toOneTimeEvent())
Second, when you are observing on the LiveData, use consume { } function on the delivered value to gain the feature of OneTimeEvent. You'll be sure that the block of consume { } will be executed only once.
viewModel.liveData.observe(this, Observer {
it.consume { yourObject ->
// TODO: do whatever with 'yourObject'
}
})
In this case, when the fragment resumes, your block of code does not execute again.
I'm using LiveData's version "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha05". Once my LiveData block executes successfully I want to explicitly trigger it to execute again, e.g.
I navigate to a fragment
User's data loads
I click delete btn while being in the same fragment
User's data should refresh
I have a fragment where I observe my LiveData, a ViewModel with LiveData and Repository:
ViewModel:
fun getUserLiveData() = liveData(Dispatchers.IO) {
val userData = usersRepo.getUser(userId)
emit(userData)
}
Fragment:
viewModel.getUserLiveData.observe(viewLifecycleOwner,
androidx.lifecycle.Observer {..
Then I'm trying to achieve desired behaviour like this:
viewModel.deleteUser()
viewModel.getUserLiveData()
According to the documentation below LiveData block won't execute if it has completed successfully and if I put a while(true) inside the LiveData block, then my data refreshes, however I don't want this to do since I need to update my view reactively.
If the [block] completes successfully or is cancelled due to reasons other than [LiveData]
becoming inactive, it will not be re-executed even after [LiveData] goes through active
inactive cycle.
Perhaps I'm missing something how I can reuse the same LiveDataScope to achieve this? Any help would be appreciated.
To do this with liveData { .. } block you need to define some source of commands and then subscribe to them in a block. Example:
MyViewModel() : ViewModel() {
val commandsChannel = Channel<Command>()
val liveData = livedata {
commandsChannel.consumeEach { command ->
// you could have different kind of commands
//or emit just Unit to notify, that refresh is needed
val newData = getSomeNewData()
emit(newData)
}
}
fun deleteUser() {
.... // delete user
commandsChannel.send(RefreshUsersListCommand)
}
}
Question you should ask yourself: Maybe it would be easier to use ordinary MutableLiveData instead, and mutate its value by yourself?
livedata { ... } builder works well, when you can collect some stream of data (like a Flow / Flowable from Room DB) and not so well for plain, non stream sources, which you need to ask for data by yourself.
I found a solution for this. We can use switchMap to call the LiveDataScope manually.
First, let see the official example for switchMap:
/**
* Here is an example class that holds a typed-in name of a user
* `String` (such as from an `EditText`) in a [MutableLiveData] and
* returns a `LiveData` containing a List of `User` objects for users that have
* that name. It populates that `LiveData` by requerying a repository-pattern object
* each time the typed name changes.
* <p>
* This `ViewModel` would permit the observing UI to update "live" as the user ID text
* changes.
**/
class UserViewModel: AndroidViewModel {
val nameQueryLiveData : MutableLiveData<String> = ...
fun usersWithNameLiveData(): LiveData<List<String>> = nameQueryLiveData.switchMap {
name -> myDataSource.usersWithNameLiveData(name)
}
fun setNameQuery(val name: String) {
this.nameQueryLiveData.value = name;
}
}
The example was very clear. We just need to change nameQueryLiveData to your own type and then combine it with LiveDataScope. Such as:
class UserViewModel: AndroidViewModel {
val _action : MutableLiveData<NetworkAction> = ...
fun usersWithNameLiveData(): LiveData<List<String>> = _action.switchMap {
action -> liveData(Dispatchers.IO){
when (action) {
Init -> {
// first network request or fragment reusing
// check cache or something you saved.
val cache = getCache()
if (cache == null) {
// real fecth data from network
cache = repo.loadData()
}
saveCache(cache)
emit(cache)
}
Reload -> {
val ret = repo.loadData()
saveCache(ret)
emit(ret)
}
}
}
}
// call this in activity, fragment or any view
fun fetchData(ac: NetworkAction) {
this._action.value = ac;
}
sealed class NetworkAction{
object Init:NetworkAction()
object Reload:NetworkAction()
}
}
First add implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" to your gradle file. Make your ViewModel as follows:
MyViewModel() : ViewModel() {
val userList = MutableLiveData<MutableList<User>>()
fun getUserList() {
viewModelScope.launch {
userList.postValue(usersRepo.getUser(userId))
}
}
}
Then onserve the userList:
viewModel.sessionChartData.observe(viewLifecycleOwner, Observer { users ->
// Do whatever you want with "users" data
})
Make an extension to delete single user from userList and get notified:
fun <T> MutableLiveData<MutableList<T>>.removeItemAt(index: Int) {
if (!this.value.isNullOrEmpty()) {
val oldValue = this.value
oldValue?.removeAt(index)
this.value = oldValue
} else {
this.value = mutableListOf()
}
}
Call that extension function to delete any user and you will be notified in your Observer block after one user get deleted.
viewModel.userList.removeItemAt(5) // Index 5
When you want to get userList from data source just call viewModel.getUserList() You will get data to the observer block.
private val usersLiveData = liveData(Dispatchers.IO) {
val retrievedUsers = MyApplication.moodle.getEnrolledUsersCoroutine(course)
repo.users = retrievedUsers
roles.postValue(repo.findRolesByAll())
emit(retrievedUsers)
}
init {
usersMediator.addSource(usersLiveData){ usersMediator.value = it }
}
fun refreshUsers() {
usersMediator.removeSource(usersLiveData)
usersMediator.addSource(usersLiveData) { usersMediator.value = it }
The commands in liveData block {} doesn't get executed again.
Okay yes, the observer in the viewmodel holding activity get's triggered, but with old data.
No further network call.
Sad. Very sad. "Solution" seemed promisingly and less boilerplaty compared to the other suggestions with Channel and SwitchMap mechanisms.
You can use MediatorLiveData for this.
The following is a gist of how you may be able to achieve this.
class YourViewModel : ViewModel() {
val mediatorLiveData = MediatorLiveData<String>()
private val liveData = liveData<String> { }
init {
mediatorLiveData.addSource(liveData){mediatorLiveData.value = it}
}
fun refresh() {
mediatorLiveData.removeSource(liveData)
mediatorLiveData.addSource(liveData) {mediatorLiveData.value = it}
}
}
Expose mediatorLiveData to your View and observe() the same, call refresh() when your user is deleted and the rest should work as is.
How do I remove the observer after I receive the first result? Below are two code ways I've tried, but they both keep receiving updates even though I have removed the observer.
Observer observer = new Observer<DownloadItem>() {
#Override
public void onChanged(#Nullable DownloadItem downloadItem) {
if(downloadItem!= null) {
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
return;
}
startDownload();
model.getDownloadByContentId(contentId).removeObservers((AppCompatActivity)context);
}
};
model.getDownloadByContentId(contentId).observeForever(observer);
model.getDownloadByContentId(contentId).observe((AppCompatActivity)context, downloadItem-> {
if(downloadItem!= null) {
this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
return;
}
startDownload();
model.getDownloadByContentId(contentId).removeObserver(downloadItem-> {});
} );
There is a more convenient solution for Kotlin with extensions:
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
observe(lifecycleOwner, object : Observer<T> {
override fun onChanged(t: T?) {
observer.onChanged(t)
removeObserver(this)
}
})
}
This extension permit us to do that:
liveData.observeOnce(this, Observer<Password> {
if (it != null) {
// do something
}
})
So to answer your original question, we can do that:
val livedata = model.getDownloadByContentId(contentId)
livedata.observeOnce((AppCompatActivity) context, Observer<T> {
if (it != null) {
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
}
startDownload();
})
The original source is here: https://code.luasoftware.com/tutorials/android/android-livedata-observe-once-only-kotlin/
Update: #Hakem-Zaied is right, we need to use observe instead of observeForever.
Your first one will not work, because observeForever() is not tied to any LifecycleOwner.
Your second one will not work, because you are not passing the existing registered observer to removeObserver().
You first need to settle on whether you are using LiveData with a LifecycleOwner (your activity) or not. My assumption is that you should be using a LifecycleOwner. In that case, use:
Observer observer = new Observer<DownloadItem>() {
#Override
public void onChanged(#Nullable DownloadItem downloadItem) {
if(downloadItem!= null) {
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
return;
}
startDownload();
model.getDownloadByContentId(contentId).removeObservers((AppCompatActivity)context);
}
};
model.getDownloadByContentId(contentId).observe((AppCompatActivity)context, observer);
I love the generic solutions by Vince and Hakem Zaied, but to me the lambda version seems even better:
fun <T> LiveData<T>.observeOnce(observer: (T) -> Unit) {
observeForever(object: Observer<T> {
override fun onChanged(value: T) {
removeObserver(this)
observer(value)
}
})
}
fun <T> LiveData<T>.observeOnce(owner: LifecycleOwner, observer: (T) -> Unit) {
observe(owner, object: Observer<T> {
override fun onChanged(value: T) {
removeObserver(this)
observer(value)
}
})
}
So you end up with:
val livedata = model.getDownloadByContentId(contentId)
livedata.observeOnce(context as AppCompatActivity) {
if (it != null) {
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists")
}
startDownload();
}
Which I find cleaner.
Also, removeObserver() is called first-thing as the observer is dispatched, which makes it safer (i.e. copes with potential runtime error throws from within the user's observer code).
Following on CommonsWare answer, instead of calling removeObservers() which will remove all the observers attached to the LiveData, you can simply call removeObserver(this) to only remove this observer:
Observer observer = new Observer<DownloadItem>() {
#Override
public void onChanged(#Nullable DownloadItem downloadItem) {
if(downloadItem!= null) {
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
return;
}
startDownload();
model.getDownloadByContentId(contentId).removeObserver(this);
}
};
model.getDownloadByContentId(contentId).observe((AppCompatActivity)context, observer);
Note: in removeObserver(this), this refers to the observer instance and this works only in the case of an anonymous inner class. If you use a lambda, then this will refer to the activity instance.
I agree with Vince above, but I believe that we either skip passing lifecycleOwner and use observerForever as below:
fun <T> LiveData<T>.observeOnce(observer: Observer<T>) {
observeForever(object : Observer<T> {
override fun onChanged(t: T?) {
observer.onChanged(t)
removeObserver(this)
}
})
}
Or, using the lifecycleOwner with observe as below:
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
observe(lifecycleOwner, object : Observer<T> {
override fun onChanged(t: T?) {
observer.onChanged(t)
removeObserver(this)
}
})
}
Java version of observeOnce method is already suggested by many users. But here we'll se the implementation in the main code.
First, we need to create Util class method
public class LiveDataUtil {
public static <T> void observeOnce(final LiveData<T> liveData, final Observer<T> observer) {
liveData.observeForever(new Observer<T>() {
#Override
public void onChanged(T t) {
liveData.removeObserver(this);
observer.onChanged(t);
}
});
}}
Now, we need to call this class where we need our ViewModel.
LiveDataUtil.observeOnce(viewModel.getUserDetails(), response-> {
if(response.isSuccessful()){
//Do your task
}
}
That's All!
Here's a Java version of the observeOnce method suggested in the other answers (an util class method instead of a Kotlin extension function) :
public class LiveDataUtil {
public static <T> void observeOnce(final LiveData<T> liveData, final Observer<T> observer) {
liveData.observeForever(new Observer<T>() {
#Override
public void onChanged(T t) {
liveData.removeObserver(this);
observer.onChanged(t);
}
});
}
}
You are creating live data instance (model.getDownloadByContentId(contentId)) more than one time that is the problem here.
Try this:
LiveData myLiveData =model.getDownloadByContentId(contentId);
myLiveData.observe(getViewLifecycleOwner(), downloadItem-> {
if(downloadItem!= null) {
this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
return;
}
startDownload();
myLiveData.removeObservers(getViewLifecycleOwner());
} );
The solution proposed by #CommonsWare and #Toni Joe didn't solve the issue for me when I needed to remove the observers after receiving the first result from a DAO query in my ViewModel. However, the following solution found at Livedata keeps observer after calling removeObserer did the trick for me with a little of my own intuition.
The process is as follows, create a variable in your ViewModel where the LiveData is stored upon request, retrieve it in a create observer function call in the activity after doing a null check, and call a remove observers function before calling the flushToDB routine in an imported class. That is, the code in my ViewModel looks as follows:
public class GameDataModel extends AndroidViewModel {
private LiveData<Integer> lastMatchNum = null;
.
.
.
private void initLastMatchNum(Integer player1ID, Integer player2ID) {
List<Integer> playerIDs = new ArrayList<>();
playerIDs.add(player1ID);
playerIDs.add(player2ID);
lastMatchNum = mRepository.getLastMatchNum(playerIDs);
}
public LiveData<Integer> getLastMatchNum(Integer player1ID, Integer player2ID) {
if (lastMatchNum == null) { initLastMatchNum(player1ID, player2ID); }
return lastMatchNum;
}
In the above, if there is no data in the LiveData variable in the ViewModel, I call initLastMatchNum() to retrieve the data from a function within the view model. The function to be called from the activity is getLastMatchNum(). This routine retrieves the data in the variable in the ViewModel (which is retrieved via the repository via the DAO).
The following code I have in my Activity
public class SomeActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
.
.
.
setupLastMatchNumObserver();
.
.
.
}
private void setupLastMatchNumObserver() {
if (mGameDataViewModel.getLastMatchNum(Player1ID, Player2ID).hasObservers()) {
Log.v("Observers", "setupLastMatchNumObserver has observers...returning");
return;
}
Log.v("Setting up Observers", "running mGameDataViewModel.get...observer()");
mGameDataViewModel.getLastMatchNum(Player1ID, Player2ID).observe(this, new Observer<Integer>() {
#Override
public void onChanged(Integer MatchNumber) {
if (MatchNumber == null ) {
matchNumber = 1;
Log.v( "null MatchNumber", "matchNumber: " + matchNumber.toString());
}
else {
matchNumber = MatchNumber; matchNumber++;
Log.v( "matchNumber", "Incrementing match number: " + matchNumber.toString());
}
MatchNumberText.setText(matchNumber.toString());
}
});
}
private void removeObservers() {
final LiveData<Integer> observable = mGameDataViewModel.getLastMatchNum(Player1ID, Player2ID);
if (observable != null && observable.hasObservers()) {
Log.v("removeObserver", "Removing Observers");
observable.removeObservers(this);
}
}
What's going on in the above, is 1.) I call the setupLastMatchNumObserver() routine in the onCreate method of the activity, to update the class's variable matchNum. This keeps track of the match numbers between players in my game which is stored in a database. Every set of players will have a different match number in the database based upon how often they play new matches with each other. The first solutions in this thread seemed a little weary to me as calling remove observers in the onChanged seems strange to me and would constantly change the TextView object after every database flush of each move of the players. So matchNumber was getting incremented after every move because there was a new value in the database after the first move (namely the one matchNumber++ value) and onChanged kept being called because removeObservers was not working as intended. setupLastMatchNumObserver() checks to see if there are observers of the live data and if so does not instantiate a new call each round. As you can see I am setting a TextView object to reflect the current matchnumber of the players.
The next part is a little trick on when to call removeObservers(). At first I thought if I called it directly after setupLastMatchNumObserver() in the onCreate override of the activity that all would be fine. But it removed the observer before the observer could grab the data. I found out that if I called removeObservers() directly prior to the call to flush the new data collected in the activity to the database (in separate routines throughout the activity) it worked like a charm. i.e.,
public void addListenerOnButton() {
.
.
.
#Override
public void onClick(View v) {
.
.
.
removeObservers();
updateMatchData(data);
}
}
I also call removeObservers(); and updateMatchData(data) in other places in my activity in the above fashion. The beauty is removeObservers() can be called as many times as needed since there is a check to return if there are no observers present.
LiveData class has 2 similar methods to remove Observers. First is named,
removeObserver(#NonNull final Observer<T> observer) (see carefully the name of the method, it's singular) which takes in the observer you want to be removed from the list of Observers of the same LifecycleOwner.
Second method is
removeObservers(#NonNull final LifecycleOwner owner) (see the plural method name). This method takes in the LifecycleOwner itself and removes all the Observers of the specified LifecycleOwner.
Now in your case, you can remove your Observer by 2 ways (there might be many ways), one is told by #ToniJoe in the previous answer.
Another way is just have a MutableLiveData of boolean in your ViewModel which stores true when it's been Observed the first time and just observe that Livedata as well. So whenever it turns to true, you'll be notified and there you can remove your observer by passing that particular observer.
Vince and Hakem Zaied solutions worked well, but in my case, I was trying to get the livedata instance and update a local DB, but the livedata was to be updated from a remote API first, hence I was getting a NullPointer, so I switched to observeForever and I was able to get the data when it was updated, but now I had to dispose of the observer after getting the data, so I modified Vince solution to only observe and emit data when the livedata contained data.
fun <T> LiveData<T>.observeOnce(observer: (T) -> Unit) {
observeForever(object : Observer<T> {
override fun onChanged(value: T) {
//Resource is my data class response wrapper, with this i was able to
//only update the observer when the livedata had values
//the idea is to cast the value to the expected type and check for nulls
val resource = value as Resource<*>
if (resource.data != null) {
observer(value)
removeObserver(this)
}}
})
}
Here is a androidx.lifecycle.Observer Java example:
Observer <? super List<MyEntity>> observer = new Observer<List<MyEntity>>() {
#Override
public void onChanged(List<MyEntity> myEntities) {
Log.d(TAG, "observer changed");
MySearchViewModel.getMyList().removeObserver(this);
}
};
MySearchViewModel.getMyList().observe(MainActivity.this, observer);
In my opinion, Livedata is designed to continually receive oncoming data. If you just want it to be executed only once for the purpose of, say, requesting data from server to initialize UI, I would recommend you design your code in this way:
1、Define your time-consuming method as non-Livedata type inside a Viewmodel. You do not have to start a new thread in this process.
2、Start a new Thread in an Activity, and inside the new Thread, call the method defined above, followed by runOnUiThread() where you write your logic of utilizing the requested data. In thie way the time-consuming method will not block the UI thread, while it blocks the new thread so the runOnUiThread() only runs after your requested data is received successfully.
So consider a replacement of Livedata, if this is what you want.
I read some documentation and saw at the observer the remove method and so I came to this solution:
1: first declare the observer:
// observer for selecting chip in view
View actionView;
Observer selectChipFunction = (action) -> selectChip(actionView, action);
2: then use the observer:
// select an action if set before
if (sharedViewModel.getAction() != null)
sharedViewModel.getAction().observe(getViewLifecycleOwner(), selectChipFunction);
3: then in the selectChip observer remove the observer:
/**
* select action chip
* #param actionView - view to use for selecting action chip
* #param actionObject - action chip to select
*/
private void selectChip(View actionView, Object actionObject)
{
// no need for observing when action is changed so remove.
sharedViewModel.getAction().removeObserver(selectChipFunction);
This way its only triggered once and after that its removed. In my case I needed this because I was setting the "action" that triggered the Observer in the selectChipFunction and if I dont do this you will end in a cyclic observer triggering.
How about this:
fun <T> LiveData<T>.observeOnCondition(lifecycleOwner: LifecycleOwner,
observer: Observer<T>,
condition: () -> Boolean) {
observe(lifecycleOwner) { t ->
if (condition()) {
observer.onChanged(t)
}
}
}
This way you can define a more generic condition if you might want to pick up the data again at a later stage.
you can use such function to observe once then remove the observer
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
var ob: Observer<T>? = null
ob = Observer { value ->
ob?.let {
removeObserver(it)
}
observer.onChanged(value)
}
observe(lifecycleOwner, ob)
}
Unfortunately, all solution didn't work for me. The only example that worked for me see that link.
https://gist.github.com/kobeumut/edb3edd9a2ae9abf6984a42bb2de0441
According to LiveData documentation:
The LiveData class provides the following advantages:
...
Always up to date data: If a Lifecycle starts again (like an activity going back to started state from the back stack) it receives the latest location data (if it didn’t already).
But sometimes I don't need this feature.
For example, I have following LiveData in ViewModel and Observer in Activity:
//LiveData
val showDialogLiveData = MutableLiveData<String>()
//Activity
viewModel.showMessageLiveData.observe(this, android.arch.lifecycle.Observer { message ->
AlertDialog.Builder(this)
.setMessage(message)
.setPositiveButton("OK") { _, _ -> }
.show()
})
Now after every rotation old dialog will appear.
Is there a way to clear stored value after it's handled or is it wrong usage of LiveData at all?
Update
There are actually a few ways to resolve this issue. They are summarized nicely in the article LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case). This is written by a fellow Googler who works with the Architecture Components team.
TL;DR A more robust approach is to use an Event wrapper class, which you can see an example of at the bottom of the article.
This pattern has made it's way into numerous Android samples, for example:
Plaid
Architecture Blueprints
IOSched
Why is an Event wrapper preferred over SingleLiveEvent?
One issue with SingleLiveEvent is that if there are multiple observers to a SingleLiveEvent, only one of them will be notified when that data has changed - this can introduce subtle bugs and is hard to work around.
Using an Event wrapper class, all of your observers will be notified as normal. You can then choose to either explicitly "handle" the content (content is only "handled" once) or peek at the content, which always returns whatever the latest "content" was. In the dialog example, this means you can always see what the last message was with peek, but ensure that for every new message, the dialog only is triggered once, using getContentIfNotHandled.
Old Response
Alex's response in the comments is I think exactly what you're looking for. There's sample code for a class called SingleLiveEvent. The purpose of this class is described as:
A lifecycle-aware observable that sends only new updates after
subscription, used for events like navigation and Snackbar messages.
This avoids a common problem with events: on configuration change
(like rotation) an update can be emitted if the observer is active.
This LiveData only calls the observable if there's an explicit call to
setValue() or call().
If you need simple solution, try this one:
class SingleLiveData<T> : MutableLiveData<T?>() {
override fun observe(owner: LifecycleOwner, observer: Observer<in T?>) {
super.observe(owner, Observer { t ->
if (t != null) {
observer.onChanged(t)
postValue(null)
}
})
}
}
Use it like a regular MutableLiveData
I`m not sure if it will work in your case, but in my case (increasing/decreasing items amount in Room by click on views) removing Observer and checking if there is active observers let me do the job:
LiveData<MenuItem> menuitem = mViewModel.getMenuItemById(menuid);
menuitem.observe(this, (MenuItem menuItemRoom) ->{
menuitem.removeObservers(this);
if(menuitem.hasObservers())return;
// Do your single job here
});
});
UPDATE 20/03/2019:
Now i prefer this:
EventWraper class from Google Samples inside MutableLiveData
/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
*/
public class Event<T> {
private T mContent;
private boolean hasBeenHandled = false;
public Event( T content) {
if (content == null) {
throw new IllegalArgumentException("null values in Event are not allowed.");
}
mContent = content;
}
#Nullable
public T getContentIfNotHandled() {
if (hasBeenHandled) {
return null;
} else {
hasBeenHandled = true;
return mContent;
}
}
public boolean hasBeenHandled() {
return hasBeenHandled;
}
}
In ViewModel :
/** expose Save LiveData Event */
public void newSaveEvent() {
saveEvent.setValue(new Event<>(true));
}
private final MutableLiveData<Event<Boolean>> saveEvent = new MutableLiveData<>();
LiveData<Event<Boolean>> onSaveEvent() {
return saveEvent;
}
In Activity/Fragment
mViewModel
.onSaveEvent()
.observe(
getViewLifecycleOwner(),
booleanEvent -> {
if (booleanEvent != null)
final Boolean shouldSave = booleanEvent.getContentIfNotHandled();
if (shouldSave != null && shouldSave) saveData();
}
});
In my case SingleLiveEvent doesn't help. I use this code:
private MutableLiveData<Boolean> someLiveData;
private final Observer<Boolean> someObserver = new Observer<Boolean>() {
#Override
public void onChanged(#Nullable Boolean aBoolean) {
if (aBoolean != null) {
// doing work
...
// reset LiveData value
someLiveData.postValue(null);
}
}
};
You need to use SingleLiveEvent for this case
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
#MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner, Observer<T> { t ->
if (pending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
#MainThread
override fun setValue(t: T?) {
pending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
#MainThread
fun call() {
value = null
}
companion object {
private const val TAG = "SingleLiveEvent"
}
}
And inside you viewmodel class create object like:
val snackbarMessage = SingleLiveEvent<Int>()
I solved it like that. Live data will clear itself when there is no observer
class SelfCleaningLiveData<T> : MutableLiveData<T>(){
override fun onInactive() {
super.onInactive()
value = null
}
}
The best solution I found is live event library which works perfectly if you have multiple observers:
class LiveEventViewModel : ViewModel() {
private val clickedState = LiveEvent<String>()
val state: LiveData<String> = clickedState
fun clicked() {
clickedState.value = ...
}
}
Might be an ugly hack but... Note: it requires RxJava
menuRepository
.getMenuTypeAndMenuEntity(menuId)
.flatMap { Single.fromCallable { menuTypeAndId.postValue(Pair(it.first, menuId)) } }
.flatMap { Single.timer(200, TimeUnit.MILLISECONDS) }
.subscribe(
{ menuTypeAndId.postValue(null) },
{ Log.d(MenuViewModel.TAG, "onError: ${it.printStackTrace()}") }
)
I know It's not the best way or even a professional way but if you do not hav time to do it the right way you can recreate the MutableLiveDataa after you observed it. it would be like :
private void purchaseAllResultFun() {
viewModel.isAllPurchaseSuccess().observe(getViewLifecycleOwner(), isSuccess -> {
if (!isSuccess) {
failedPurchaseToast();
}else {
successfulPurchaseToast();
}
//reset mutableLiveData after you're done
viewModel.resetIsAllSuccessFull();
});
}
//function in viewmodel class
public void resetIsAllSuccessFull(){
purchaseRepository.reSetIsAllSuccessFull();
}
//function in repository class
public void resetIsAllSuccessFull(){
successLiveData = new MutableLiveData<>();
}
In this way if you need to recall purchaseAllResultFun() function it won't give the stored value.