I am calling methods in viewModel to set data on my mutable live data like below and they are being called one after the other exactly like below:
viewModel.onCountrySelected(country);
viewModel.onLanguageSelected(language);
Then in the view model I set the data on the mutable live data is I am observing in my fragment. The methods in my view model are below:
public void onLanguageSelected(int type) {
clickType.setValue(type);
}
public void onCountrySelected(int type) {
clickType.setValue(type);
}
clickType is my Mutable Live data. Now the problem is that my onChanged is only called once and that too only for the last value that I set on clickType.
If I am not wrong I onChanged should be triggered twice since I am setting the value on the clickType twice.
private void initLiveData()
{
clickType = new MutableLiveData<>();
clickType.setValue(0);
}
initLiveData is where I am initialising the MutableLiveData. Also I am calling the initLiveData after setting the veiwModel variable to the layout.
In the fragment:
binding.setViewModel(viewModel);
viewModel.initLiveData()
viewModel.getClickType().observe(this, new Observer<Integer>() {
#Override
public void onChanged(#Nullable Integer type) {
handleClicks(type);
}
});
Related
I'm using LiveData with MVVM. After updating my database with Room, I am trying to sendback both the Object I inserted into my Room database, and also the adapter position. In my ViewModel class, the method is:
private MutableLiveData<String> insertItemLiveData = new MutableLiveData<>;
public void insertMenuItem(MenuItem menuItem, int adapterPositionToUpdate){
repo.insertOrder(menuItem.getId())
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Integer>() {
#Override
public void onSubscribe(#NonNull Disposable d) {
}
#Override
public void onSuccess(#NonNull Integer integer) {
//The order is successfully inserted into database
//So I return back the name of the inserted order
String s = "Inserted Item: " + menuItem.getNameOfOrder();
insertItemLiveData.setValue(s);
}
#Override
public void onError(#NonNull Throwable e) {
errorLiveData.setValue("Failed to cancel order.");
}
});
}
In the on success method, it returns the String I want to display, but I also want to update the position of the Recyclerview item that has changed. What is the best way to handle this situation?
I can use a wrapper class and have setters for a String and the adapter position, but I feel like there's probably a better way to do this.
A resource wrapper is a good idea for it.MVVM Resource Wrapper With Live Data you can check my code to get an insight on how to use it
I am trying to add an observer in my activity but it never seems to get triggered.
I have a button on my app which makes a sensor connected to my phone start measuring data when the sensors are measuring it hits a callback in my XsDevice() class.
Here is the code in my XsDevice() class
private MutableLiveData<ArrayList<Float>> accelerationData = new MutableLiveData<>();
public LiveData<ArrayList<Float>> freeAccDataLiveData = accelerationData;
#Override
public void onXsensDotDataChanged(String s, XsensDotData xsensDotData) {
ArrayList<Float> result = new ArrayList();
for (Float freeAcc: xsensDotData.getFreeAcc()) {
result.add(freeAcc);
}
accelerationData.postValue(result);
}
When the callback function is hit I am using postValue(result) to update the accelerationData variable, this is where me being a new to android development comes in.
I am presuming after I post the value the freeAccDataLiveData variable is updated and this is what I am observing.
Here is my observer code in my activity
private XsDevice xsDeviceClass = new XsDevice();
protected void onCreate(Bundle savedInstanceState) {
...
xsDeviceClass.freeAccDataLiveData.observe(this, new Observer<ArrayList<Float>>() {
#Override
public void onChanged(ArrayList<Float> freeAccData) {
for(int i = 0; i < freeAccData.size(); i++){
Log.d("Free Acceleration Data", String.valueOf(freeAccData.get(i)));
}
}
});
}
The ... is just a placeholder for the standard onCreate code I haven't included.
The issue I am having is Log.d("Free Acceleration Data", String.valueOf(freeAccData.get(i))); is never logged which must mean the observer isn't working. If I added this log directly to the callback function it works fine but I need to get the data in my MainActivity
Is there something simple I might have missed?
The observer of livedata will be called only when data is changed by set or post. Thus, if you did not initialize the value in the XsDevice class, it won't be called until data is assigned.
If you want to get callback in OnCreate method by default, need to set the default value of accelerationData like this. Then, you can get callback right after you register an observer.
class XsDevice {
private MutableLiveData<ArrayList<Float>> accelerationData = new MutableLiveData<>();
public LiveData<ArrayList<Float>> freeAccDataLiveData = accelerationData;
public XsDevice() {
accelerationData.postValue(new ArrayList<>());
}
...
}
Also, please make sure onXsensDotDataChanged is called as you expected and freeAccData is not empty. Otherwise, you cannot see the log even though it is called.
I have a conventional Room->DAO->Livedata->Repositiry->ViewModel->RecyclerView app. Different buttons of UI must pass different lists of data to RecyclerView.
By button click I want:
Make new #Query in DAO and get new LiveData<`List> object in return.
Put this new data into the RecyclerViewAdapter and call notifyDataSetChanged () to make new List visuals.
The Dao #Query:
#Query("SELECT * FROM entry_table WHERE path LIKE :path ORDER BY priority DESC")
LiveData<List<Entry>> getNotesOfFolder(String path); //Returns LiveData with List of Entries
The recyclerView is updated via onChanged of Observer like this:
public class RecyclerViewActivity extends AppCompatActivity {…
Observer<List<Entry>> entryObserver = new Observer<List<Entry>>() {
#Override
public void onChanged(List<Entry> entries) {
recyclerAdapter.setEntries(entries);
}
};
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.EntryHolder> {…
public void setEntries(List<Entry> entries) {
this.entries = entries; //setting LiveData content to adapter's List (i.e. entries)
notifyDataSetChanged();
The problem is that my Observer does not call the onChange method when LiveData receives new value from DAO. I believe it is because this LiveData’s content is not CHANGED but REPLACED by another LiveData.
I tried to re-subscribe the Observer to LiveData again and it somewhat worked, but when I try to call some conventional Room queries like #Delete, I got multiple (up to 10!) onChange calls and some of them behave weirdly and pass the wrong List to RVadapter.
So there two questions:
How can I just call onChanged() of my Observer?
Is there some other stylish way of passing new LiveData object to RecyclerView dynamically?
1)
In viewModel , create a getter method for live data:
//...
private LiveData<List<Entry>> liveData;
//...
public LiveData<List<Entry>> getLiveData() {
return liveData;
}
in Activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
//...
viewModel.getLiveData().observe(this, new Observer<List<Entry>>() {
#Override
public void onChanged(List<Entry> entryList) {
//set new value here
}
});
}
2) DiffUtil is very helpful to update your list in recycler view and it gives you some nice animations.
I tried to re-subscribe the Observer to LiveData again and it somewhat worked, but when I try to call some conventional Room queries like #Delete, I got multiple (up to 10!) onChange calls and some of them behave weirdly and pass the wrong List to RVadapter.
This would make sense if you didn't first unsubscribe your observer from the old LiveData object... the one you replace when your query changes.
If your query updates, you will need to get a new LiveData from the DAO. If you overwrite your old LiveData with the new one, you will not only need to (re-)subscribe your Observer to the new one, you will also need to unsubscribe it from the old one. Otherwise it will live on and keep updating the observer.
I'm getting a LiveData> from a database in my view model. But I have to add some Foo-objects to the list, before I can forward them to the view.
I'm using the Room API to get access to a database. I'm using the recommended encapsulation with a Dao, a repository and the view model. The repository just forwards the LiveData from the Dao.
In the view model, I call the method from the repository and store the result in a variable. Because I can't use the observe-method of the LiveData-object, I tried it with the Transformations.map-method. But the map-method isn't called at any time.
public class FooViewModel extends AndroidViewModel {
private LiveData<List<Foo>> fromDatabase;
private MutableLiveData<List<Foo>> forView;
public FooViewModel(/*...*/) {
//...
forView = new MutableLiveData<>();
}
//Returns the LiveData<List> for the view, that should be observed
public LiveData<List<Foo>> getViewList() {
return forView;
}
//Loads the data from the database, modifies it and maps it to the LiveData for the view
public void loadFromDatabase(/*Some conditions for query*/) {
fromDatabase = repository.getData(/*Some conditions*/);
Transformations.map(fromDatabase, (foos) -> {
forView.setValue(fillList(foos));
return forView;
}
}
//Fills the list with some other foos
private static List<Foo> fillList(List<Foo> foos) {
//Fill the list
}
}
And in the view I observe the list in a way like this:
public class FooActivity {
protected void onCreate(/*Some inputs*/) {
viewModel.getViewList().observe(this, (foos) -> /*Display the list*/);
viewModel.loadFromDatabase(/*with some conditions*/);
}
}
And then nothing happens...
I tried also to forward the LiveData got from the repository and observe it. That observation works fine. But not the modified one.
//Loads the data from the database, modifies it and maps it to the LiveData for the view
public void loadFromDatabase(/*Some conditions for query*/) {
fromDatabase = repository.getData(/*Some conditions*/);
Transformations.map(fromDatabase, (foos) -> {
forView.setValue(fillList(foos));
return forView;
}
}
This will never work. fromDatabase is replaced but the transformation is done against the previous fromDatabase instance.
You need to set the query conditions into a MutableLiveData to which you do Transformations.switchMap to return the LiveData<List<T>> with the correct filters applied through the DAO.
Then if you modify the conditions live data, the DAO will re-evaluate the new list with the new conditions.
In my app I am trying to use MediatorLiveData to listen to the changes to a livedata. Since DB operations are involved I use an executor service like this.
MediatorLiveData<Content> mediatorLiveData = new MediatorLiveData<>();
appExecutors.diskIO().execute(() -> {
long id = contentDao.insert(content);
Log.i("LIVE", id + "");
LiveData<Content> content = contentDao.getContentById(id);
mediatorLiveData.addSource(content, new Observer<Content>() {
#Override
public void onChanged(#Nullable Content content) {
Log.i("LIVE", "FIRED");
}
});
});
First I try to insert a new content object into the db. I get the id of the inserted object which I log in the next line. I get some id which is good. After this, I use the id to query for the same object. The query returns a LiveData. (If I use content.getValue() at this time, I get null.)
Then I listen to changes in this liveData using a MediatorLiveData. Unfortunately, the onChange method of the mediatorLiveData is never fired. Thus the Log is not printed too.
This is my content dao class
#Dao
public interface ContentDao {
#Insert
long insert(Content content);
#Query("SELECT * FROM my_table WHERE id = :id")
LiveData<Content> getContentById(long id);
}
I can't understand what I am doing wrong. Can someone please help. Thanks!!
Edit: To clarify, this is how the code looks.
return new NetworkBoundResource<Content, CreateContent>(appExecutors) {
#Override
protected void saveCallResult(#NonNull CreateContent item) {
//Something
}
#Override
protected boolean shouldCall(#Nullable Content data) {
//Something;
}
#Override
protected LiveData<Content> createDbCall() {
MediatorLiveData<Content> mediatorLiveData = new MediatorLiveData<>();
appExecutors.diskIO().execute(() -> {
long id = contentDao.insert(content);
Log.i("LIVE", id + "");
LiveData<Content> content = contentDao.getContentById(id);
mediatorLiveData.addSource(content, new Observer<Content>() {
#Override
public void onChanged(#Nullable Content c) {
Log.i("LIVE", "FIRED");
mediatorLiveData.removeSource(content);
mediatorLiveData.postValue(c);
}
});
});
return mediatorLiveData;
}
#NonNull
#Override
protected LiveData<ApiResponse<CreateContent>> createCall() {
//Something
}
}.asLiveData();
The value is returned to the constructor.
#MainThread
public NetworkBoundResource(AppExecutors appExecutors) {
this.appExecutors = appExecutors;
result.setValue(Resource.loading(null));
//TODO:: Add method to check if data should be saved. This should apply for search data.
LiveData<ResultType> dbSource = createDbCall();
result.addSource(dbSource, data -> {
result.removeSource(dbSource);
if (shouldCall(data)) {
fetchFromNetwork(dbSource);
} else {
result.addSource(dbSource, newData -> setValue(Resource.success(newData)));
}
});
}
As discussed you need to make sure the mediatorLiveData has an active observer attached.
If you take a look at the addSource method it checks whether any active observers are attached before subscribing to the source.
https://github.com/aosp-mirror/platform_frameworks_support/blob/d79202da157cdd94c2d0c0b6ee57170a97d12c93/lifecycle/livedata/src/main/java/androidx/lifecycle/MediatorLiveData.java#L95
In case anyone is re initializing a mediator live data, the old object only will be observed, new object will not be observed.
That is , dont do this:
Observe
myViewModel.observe(....)
Trying to allocate new memory to mediator
myMediatorObj = new MediatorLiveData<>(); //this can be the issue. Try removing if you have any lines like this.
//after this point,anything set to the object myMediatorObj will not be observed
In case you are trying to reset the data, pass in some data that signals null/empty/rest.