now i'm using google paging library in chat fragment
here's code inside initial in my data source :
Disposable disposable = apiWrapper.getMessages(1, userId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(messagesPagingResponse -> {
if (messagesPagingResponse.getData() != null && messagesPagingResponse.getData().getData() != null) {
callback.onResult(messagesPagingResponse.getData().getData(), null, messagesPagingResponse.getData().getNextPage());
}
}
, throwable -> {
Log.e("throwable", throwable.getLocalizedMessage());
});
compositeDisposable.add(disposable);
and in chat fragment i observe the list
viewModel.getLiveMessages().observe(this, this::setChatList);
private void setChatList(PagedList<Message> messages) {
this.messages = messages;
ChatPagingAdapter chatPagingAdapter = (ChatPagingAdapter) binding.messagesRecyclerView.getAdapter();
if (chatPagingAdapter != null){
chatPagingAdapter.submitList(this.messages);
}
}
it working well until iam trying to add new message to paged list so it show me this error
E/error: The exception could not be delivered to the consumer because it has already canceled/disposed the flow or the exception has nowhere to go to begin with. Further reading: https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling | java.lang.UnsupportedOperationException
when i get new message i try to add it as this
messages.add(0, message);
The Paging Library doesn't currently allow you to add items to the PagedAdapter like it is done for the normal RecyclerView. All updates must come from the datasource.
What I did in a similar situation to yours is persist all chat messages using Room and build the PagedList LiveData with a DataSource.Factory from the Dao(Data Access Object). Whenever there is a new message, all you have to do is persist that message,then room sends the update to your PagedList Livedata and your Chats RecyclerView updates accordingly.
If you are unfamiliar with Room you can read more from the offical documentation
Related
I used the lifecycle callback onCreate to fetch data like below
mWeOutViewModel.getPlaceListLiveData()
.observe(this, weOutItemViewModels -> {
AppLogger.i(getCustomTag() + "adding items " + weOutItemViewModels.size());
if (weOutItemViewModels != null && weOutItemViewModels.size() > 0)
mWeOutListAdapter.addToExisting(weOutItemViewModels);
});
As you can see the AppLogger output the initial size which is 0 when the fragment is displayed, then I fetch the data and call postValue (setValue crashes the app and it expected because I fetch data from the internet using a background thread). So I call post value like below :
private void updatePlaces(List<WeOutGroupedViewModels> weOutGroupedViewModels) {
List<WeOutGroupedViewModels> oldList = placeMutableLiveData.getValue();
oldList.addAll(weOutGroupedViewModels);
AppLogger.i(TAG +" updating places "+oldList.size());
placeMutableLiveData.postValue(oldList);
}
As you can see the other AppLogger before postValue, the size of the list is displayed(not empty), but nothing happens until the app crashes and nothing is shown in the logs. I have no ways of debugging since even on debug mode nothing happens. The post value doesn't trigger the observer.
I initialize the mutableLivedata like this :
private final MutableLiveData<List<WeOutGroupedViewModels>> placeMutableLiveData = new MutableLiveData<>();
and access like this :
public LiveData<List<WeOutGroupedViewModels>> getPlaceListLiveData() {
return placeMutableLiveData;
}
Event when I make the livedata public to access directly the livedata, there is no change (just in case someone thinks that's is where the issue comes from)
Instead of placeMutableLiveData.postValue(oldList);
I recommend using
placeMutableLiveData.postValue(Collections.unmodifiableList(new ArrayList<>(newList));
That way, the next time you access this list, you won't be able to mutate it in place, which is a good thing. You're not supposed to mutate the list inside a reactive state holder (MutableLiveData).
So theoretically it should look like this:
private void updatePlaces(List<WeOutGroupedViewModels> weOutGroupedViewModels) {
List<WeOutGroupedViewModels> newList = new ArrayList<>(placeMutableLiveData.getValue());
newList.addAll(weOutGroupedViewModels);
AppLogger.i(TAG +" updating places "+newList.size());
placeMutableLiveData.postValue(Collections.unmodifiableList(newList));
}
Trying to implement MVVM with room flowable # rxjava + retrofit2.
When the activity starts, I request a list of items from the repository. The repository retrieves them from the database (room) and returns Flowable to the ViewModel. At the same moment, the repository requests the current list from the API, and as soon as the result is returned, I delete the existing entries in the database and insert what arrived from the API.
The observer in activity, of course, updates recyclerview three times. Because of this, ui "blinks" when updating the list:
the list is not empty -> the list is empty -> the list is not empty.
After receiving the flowable from Room for the first time, the observer then receives an empty list (when deleting entries in the database) and then a new list after inserting it into the database from API.
How to avoid this behavior of RecyclerView? Is there any well-established best practice, true way, etc.?
PaymentRepository method for retrieving payment list:
private Flowable<List<Payment>> getPayments(Long accountId) {
// requesting actual data from API
paymentService.getPayments().flatMapCompletable(payments -> paymentDao
.deleteAllByAccountId(accountId)
.andThen(paymentDao.insert(payments))
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
).subscribe();
// and return cached list from db
return paymentDao.findPaidByAccountId(accountId)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}
ViewModel
private CompositeDisposable disposable = new CompositeDisposable();
public MutableLiveData<Resource<List<Payment>>> result = new MutableLiveData<>(Resource.loading());
public PaymentListViewModel() {
disposable.add(
paymentRepository.getPayments().subscribe(
payments -> result.setValue(Resource.success(payments)),
throwable -> result.setValue(paymentService.parseError(throwable).toResource())
)
);
}
Observer in Activity
viewModel.result.observe(this, resource -> {
switch (resource.status) {
case SUCCESS:
adapter.setItems(resource.data);
binding.preloader.setVisibility(View.GONE);
break;
case ERROR:
Toast.makeText(this, resource.message, Toast.LENGTH_LONG).show();
binding.preloader.setVisibility(View.GONE);
break;
case LOADING:
binding.preloader.setVisibility(View.VISIBLE);
break;
}
});
I'm not a Room expert, maybe there is so RX-way to update values without emitting it into listeners.
But here is the way that doesn't request it:
private Flowable<List<Payment>> getPayments(Long accountId) {
// requesting actual data from API
Flowable<List<Payment>> request = paymentService.getPayments()
.flatMapCompletable(payments -> paymentDao
.deleteAllByAccountId(accountId)
.andThen(paymentDao.insert(payments))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
);
return paymentDao.findPaidByAccountId(accountId)
.take(1)
.switchMap(dbList -> request.startWith(dbList))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.distinctUntilChanged();
}
in this scenario you take only first value from DB (take(1)) and then wait for the values from API.
switchMap (also could be flatMap here, no diff in this case) guaranty you that DB value will be first and only after that "listen" for API.
The only problem here if you suppose to update DB values somewhere else, while this Activity is running. In that case this updates will not be displayed.
I have created an app which is relied on my local server which fetch profile image and information about user..Code works fine without any problem but when I change my data in the local server (for example profile picture )the updated profile is not reflecting in the application until activity is restarted but this should not be happened because live data should reflect the change immediately as soon as changes occurred in the database.
below is the code of live data class
private MutableLiveData<Profile> profileMutableLiveData;
public void init(String token){
if (profileMutableLiveData!=null){
return;
}
repository=Repository.getInstance();
profileMutableLiveData=repository.getProfile(token);
}
public LiveData<Profile> getProfile(){
return profileMutableLiveData;
}
here is my Repository code
public class Repository {
private static Repository instance;
public static Repository getInstance(){
if (instance==null){
instance=new Repository();
}
return instance;
}
public MutableLiveData<Profile> getProfile(String token){
MutableLiveData<Profile> data=new MutableLiveData<>();
RetrofitApi retrofitApi=RetrofitInstance.getInstance();
Call<Profile> call=retrofitApi.getProfile(token);
call.enqueue(new Callback<Profile>() {
#Override
public void onResponse(Call<Profile> call, Response<Profile> response) {
Profile profile=response.body();
if (response.isSuccessful()){
data.setValue(profile);
}
}
#Override
public void onFailure(Call<Profile> call, Throwable t) {
}
});
return data;
}
}
Code in main activity to observe changes....
actually I am showing profile image in navigation drawer ... like telegram app
viewModelClass = new ViewModelProvider(this).get(ViewModelClass.class);
viewModelClass.init(token);
viewModelClass.getProfile().observe(this, new Observer<Profile>() {
#Override
public void onChanged(Profile profile) {
Picasso.get().load("http://192.168.43.216:8000" + profile.getProfile_photo()).into(profileImage);
fName = profile.getFirst_name();
lName = profile.getLast_name();
image = profile.getProfile_photo();
nameView.setText("Hello " + profile.getFirst_name());
}
});
}
The code is working fine but I want the data must be updated as soon as changes made in my server...
but data is updated when I restart the activity or opening app again after closing the activity...
May be the problem - is that you begin to observe in your activity one instance of MutableLiveData, and then you replace it with another one.
In your ViewModel:
profileMutableLiveData=repository.getProfile(token);
you override it instead of setting new value with "postValue"
In your Repository:
MutableLiveData<Profile> data=new MutableLiveData<>();
you make another instance of LiveData
You can try to change your return value from a Repository to a "Profile" and set it as a new value of MutableLiveData in your ViewModel with "postValue"
UPDATED
I've read your question more carefully. I think my answer above wouldn't give you what you expect (in case you expect Retrofit should update LiveData instantly like ROOM does)
So my thoughts:
You expect too much using LiveData+Retrofit. Just using them doesn't mean you'll get on-line updates of your data on your server. To achieve that you have to change mechanism of your interaction with your server, not just fix few lines in code you've shown.
There is mechanism LiveData+ROOM that works with local DB (Sqlite) in a way, that you expect from LiveData+Retrofit. But there is no magic there. Room is using mechanic, that built-in in Sqlite for notifying (triggering) when there are some changes in DB tables occur. But Retrofit doesn't implement similar mechanism with Rest Api and actually it's not its responsibility.
To achieve what you want you can look at several possibilities:
To use some Cloud Service API, that contains that built-in mechanism for notifying your device when data changes (Firebase, for example)
To implement some kind of periodic synchronisation of your app data with server. After this synchronisation you'll have all data on device and depending on where you put your data you could observe changes with LiveData+Room or FileObserver.
To simplify your case and refresh your data from the server at activity explicitly after click on Button "Refresh" on your activity. In that case you can implement steps that I wrote at first version of my answer.
I have a list ofFunctions that is retrieved from a local SQLite database using Room and I want to observe every function in that list. At the moment I'm doing the following:
public List<MutableLiveData<Function>> getLiveFunctions() {
if (liveFunctions == null) {
liveFunctions = new ArrayList<>();
for (Function function : functions) {
//Livedata with a default value on background thread (postValue)
liveFunctions.add(new DefaultLiveData<>(function, true));
}
}
return liveFunctions;
}
After a local fetch from the database, I can request the status of a given function using an RPC to my server. When I receive a response, I can set the new value for that function and I want my UI to observe the changes in that function.
Just some clarifications:
The difference between LiveData<List<Function>>> and List<LiveData<Function>> is that the first will only observe whether an object was added, updated or removed in the list, correct? It's not that LiveData<List<Function>>> also listens to changes on their items?
I'm using a MediatorLiveData to combine my observers to a "FunctionObserver". Is this the correct approach to handle all my function callbacks?
[code]
MediatorLiveData<LiveData<Function>> mediator = new MediatorLiveData<>();
List<MutableLiveData<Function>> functions = //functions
for (LiveData<Function> function : functions) {
mediator.addSource(function, functionObserver);
}
mediator.observe(MainActivity.this, new Observer<LiveData<Function>>() {
#Override
public void onChanged(#Nullable LiveData<Function> functionLiveData) {
Log.i(TAG, "Live data change: ");
}
});
Can my code logic be improved? I know that I can request a LiveData<List<Function>>> from Room but I'm having trouble with my parent class having a #Relation annotation which needs the type to be a List or Set (and not LiveData)
So I have Room database and I'm using MutableLiveData to load four lists from database in one screen each of these four lists have separate callbacks which are called when data is loaded and I have progress bar which I show when data starts loading and I want to hide this progress bar when all four lists are loaded, but the problem is that callbacks can be triggered at random times just because of all sorts of delays so whats my options to figure out when all lists are loaded?
Room supports RxJava. You can return RxJava streams and combine your streams with rich RxJava operators. Check the list of available in RxJava Wiki.
Here is untested exmple to give you idea of what am talking about
#Dao
public interface TodoDao {
#Query("SELECT * FROM TodoList")
public Observable<List<ToDoEntity>> getAllTodos();
}
#Dao
public interface AnotherListDao {
#Query("SELECT * FROM AnotherList")
public Observable<List<ToDoEntity>> getAllOtherList();
}
MyDatabase db = ...//get database instance here
//most of the times d is member class that is disposed before object of that class goes out of scope
Disposable d = Observable.merge(db.getAllTodos(), db.getAllOtherList())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(subscription -> {
subscription.request(Long.MAX_VALUE);
//data is loading
})
.subscribe(data -> {
//onNext
//work with data here
},
error -> {
//onError
//an erro happened here, service it
},
() -> {
//onComplete
//task is done
}
);
//to avoid leaking the Observable dispose it in relevant lifecycle
if(d && d.isDisposable())
d.dispose())
Let me know if there is anything that is not clear.