Changes are not observable by viewmodel - android

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.

Related

Where to place logic when extracting data from database in mvvm pattern, using android room?

I am refactoring old application to mvvm pattern, using room, repository, viewmodel, ets.
I have an old code, which contains Content provider helper class with many functions like this:
public static int deleteOldLogs(int NumDays) {
//get NumDays before today, then constract a content provider delete command and run
...
}
or
public static Cursor getTodayLogs() {
//get a day from today, then constract a content provider query and run
...
}
or
public static boolean isActionValid(Context context, int id_order, int id_actionh) {
//get all products from database table, then check if all products match some criteria, then return boolean result
...
}
My question is in what layer to place this logic? Is it a repository or viewmodel should contain? All the examples that I see in the net is very simple and not suit my goals.
View model helps us to provide data between repository and UI . For direct interaction with room database , we use repository . Once we get the data from repo we can perform all sort of computation (i.e sorting , filtering etc ) in ViewModel .
In order to display data from the database, we use an observer who will observe the data changes, LiveData in the ViewModel.
We use ViewModelProvider which is going to create a ViewModel for us. We need to connect our ViewModel with the ViewModelProvider, and then in the onChanged method, we always get our updated data which we can display on the screen.
For eg . We want to get some record from our database .
For this we need to create a repository that will interact directly with database or carrying the logic to fetch data from database .
public class ABCRepository {
#Inject
DrugsDao mABCDao;
#Inject
public ABCRepository(){
}
public LiveData<List<NameModel>> getNameByLetter(String letter) {
return mABCDao.getName(letter);
}
}
Now in View Model
public class SearchViewModel extends ViewModel {
#Inject
ABCRepository mABCRepository;
LiveData<List<GlobalSearchModel>> getNameList(String queryText) {
MutableLiveData<List<GlobalSearchModel>> mGlobalSearchResults = new
MutableLiveData<>();
List<NameModel> synonymsNameList=mABCRepository.getNameByLetter(queryText);
new Thread(() -> {
List<GlobalSearchModel> globalSearchModelList =
mABCRepository.getNameByLetter(queryText)
// this is where you can perform any action on list . either sorting or.
filtering and then return the new list to your UI.
mGlobalSearchResults.postValue(globalSearchModelList);
}).start();
return globalSearchModelList;
}
}
In your fragment or activity you can observe this data ,
getViewModel().getAllCountries().observe(this, this::addSearchResultsInRecycler);
Hope this is helpful . Though not explained good but you can have reference from
https://medium.com/#skydoves/android-mvvm-architecture-components-using-the-movie-database-api-8fbab128d7

Previous task(s) waiting on Firebase Realtime Database need to complete first before new one starts

I'm using the Task API in my app to retrieve data from Firebase Database, which is usually from different nodes. I have a helper class for Firebase Database like so:
public class FirebaseDbHelper {
public Task<DataSnapshot> getData() {
TaskCompletionSource<DataSnapshot> source = new TaskCompletionSource<>();
DatabaseReference dbRef = FirebaseDatabase.getInstance().getReference(FIRST_NODE).child(SUB_NODE);
dbRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
source.setResult(dataSnapshot);
}
#Override
public void onCancelled(DatabaseError databaseError) {
source.setException(databaseError.toException());
}
});
return source.getTask();
}
}
As you can see, getData() returns a Task object, which I use on my interactor class (I'm using the MVP architecture for my app) like so:
public class TestDbInteractor {
private FirebaseDbHelper mDbHelper;
private Listener mListener;
public TestDbInteractor(#NonNull Listener listener) {
mDbHelper = new FirebaseDbHelper();
mListener = listener;
}
void getData() {
mDbHelper.getData().addOnCompleteListener(task -> {
if (task.isSuccessful()) {
mListener.onGetDataSuccess(new MyObject(task.getResult()));
} else {
mListener.onGetDataFailed(task.getException());
}
});
}
public interface Listener {
void onGetDataSuccess(MyObject object);
void onGetDataFailed(Exception exception);
}
}
This works as expected. However, we noticed a behavior that when retrieving a lot of data, even if the activity that started the task is already finish()ed, the task still proceeds and attempts to complete. This I believe, is something that could be considered as a memory leak, since a process is still going even though it's supposed to be stopped/destroyed already.
What's worse is that when I try to get a different data (using a different Task in a different activity to a different node in Firebase), we noticed that it waits for the previous task to complete first before proceeding with this new one.
To give more context, we're developing a chat app similar to Telegram, where users could have multiple rooms and the behavior we saw is happening when a user enters a room. This is the flow:
User enters room, I request data for the room details.
Upon getting the room details, I display it, then request for the messages. I only retrieve the most recent 10. During this time, I just show a progress bar on the activity.
In order for the message details to be complete, I get data from different nodes on Firebase, this is where I use Tasks mainly.
After getting the messages, I pass it on to the View, to display the messages, then I attach a listener for new messages. Everything works as expected.
The behavior I mentioned at the beginning is noticeable when the user does something like this:
User enters a room with messages, room details are retrieved instantly, messages are still loading.
User leaves the room (presses the back button), this gets the user back to the room list, and enters a different one.
At this point, the retrieval of the room details takes such a long time - which we thought was odd, since the data isn't really that big to begin with.
After a few more testing, we concluded that the long retrieval time was caused by the current task (get room details) is still waiting for the previous task (get messages) started in a different activity, to finish first before starting.
I attempted to implement my answer here, trying to use a CancellableTask, but I am at a loss on how to use it with my current implementation, where I use a TaskCompletionSource, where you could only set a result or an exception.
I was thinking this could work if I move the task completion source to the interactor class level instead of the helper -- I haven't tried it yet. I think it's possible, but would take a lot of time to refactor the classes I already have.
So I figure why not try Doug's answer, using activity-scoped listeners. So I tested it like below.
In my activity, I added a getActivity() method, which can be called in the presenter:
public class TestPresenter
implements TestDbInteractor.Listener {
private View mView;
private TestDbInteractor mDbInteractor;
#Override
void bindView(View view) {
mView = view;
mDbInteractor = new TestDbInteractor(this);
}
#Override
void requestMessages() {
mDbInteractor.getData(mView.getActivity());
}
// Listener stuff below
}
and updated my getData() like so:
void getData(#NonNull Activity activity) {
mDbHelper.getData().addOnCompleteListener(activity, task -> {
if (task.isSuccessful()) {
mListener.onGetDataSuccess(new MyObject(task.getResult()));
} else {
mListener.onGetDataFailed(task.getException());
}
});
}
Unfortunately, this doesn't seem to work though, exiting the activity still waits for the tasks to complete, before the new task initiated in a different activity starts.
If you kick off a query to Realtime Database, it will always run to completion, whether or not there are any listeners attached to the Task that was returned. There is no way to cancel that work, neither by removing the last listener manually, nor by using activity-scoped listeners that are removed automatically. Queries in motion stay in motion. Also, all traffic to and from RTDB is pipelined over a single socket, which implies that the results of subsequent queries after one that's incomplete will have to wait for the everything ahead of it in the queue to complete first. This is likely the root cause for your observation - you have an incomplete query that other queries are waiting on, regardless of your use of the Task API.
Fortunately, if you have persistence enabled, the second query should be served by the cache of the first query, and not require another round trip to the server.
If you need to make sure that you retain the results of the first query across configuration changes that destroy the activity, then you should use something like LiveData from the Android architecture components to manage this, so that you can pick up the query where it left off after a configuration change. If you do this, don't use activity-scoped listeners.
I've written a three-part blog post about using architecture components with Firebase, which may also be of interest.
Hey You can use childEventListener. use dataSnapshot.getChildrenCount().
dbFriend=FirebaseDatabase.getInstance().getReference("Friend");
dbFriend=dbFriend.child(mPreferences.getString("username","")).child("already");
dbFriend.addChildEventListener(new ChildEventListener() {
int already=0;
#Override
public void onChildAdded(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
Username u=dataSnapshot.getValue(Username.class);
already=alread+1;
if(already >= dataSnapshot.getChildrenCount()){
//get to know when data fetching got completed
}
}
#Override
public void onChildChanged(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
}
#Override
public void onChildRemoved(#NonNull DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});

Android live data - observe always fires after config change

I'm currently refactoring my code to include ViewModel with LiveData provided by android.arch library. I have a simple activity that sends request for a password change to server and acts according to HTTP response code.
For that purpose I have created class that extends ViewModel for data and a repository class to call server. My ViewModel class has one MutableLiveData field which I'm subscribing to from my activity using .observe(...) method. The issue is that code inside .observe(...) fires all the time after configuration changes (i.e. screen rotation) and I have no idea why.
Here is the code of ViewModel, Repository and Activity classes accordingly:
ChangePasswordViewModel
public class ChangePasswordViewModel extends ViewModel{
private MutableLiveData<Integer> responseCode;
private PasswordChangeRepository passwordChangeRepository;
public ChangePasswordViewModel() {
responseCode = new MutableLiveData<>();
passwordChangeRepository = new PasswordChangeRepositoryImpl();
}
public MutableLiveData<Integer> responseCodeLiveData() {
return responseCode;
}
public void sendChangePasswordRequest(String newPassword){
passwordChangeRepository.changePassword(newPassword, passChangeCallback());
}
// Callback that fires after server sends a response
private Callback passChangeCallback(){
...
responseCode.postValue(serverResponse)
...
}
PasswordChangeRepository
public class PasswordChangeRepositoryImpl {
public void changePassword(String newPassword, Callback<Void> callback){
//Sending new password to server and processing response in callback
ServerCalls.changePassword(newPassword, callback);
}
}
Activity
public class ChangePasswordActivity extends AppCompatActivity{
...
private void init(){
//Getting appropriate view model
passwordViewModel = ViewModelProviders.of(this).get(ChangePasswordViewModel.class);
// Starting to observe LiveData
passwordViewModel.getResponseCode().observe(this, responseCode -> {
Log.info("Server response is " + responseCode);
});
//Sending new password to server
buttonPassChange.setOnClickListener(view ->
passwordViewModel.sendChangePasswordRequest("newPass")
);
}
...
}
Problem is that after the first time I send request to server using sendChangePasswordRequest(...) observe code in activity
passwordViewModel.getResponseCode().observe(this, responseCode -> {
Log.info("Server response is " + responseCode);
});
fires every time after I rotate the screen. Why is that happening? Value of MutableLiveData responseCode hasn't been updated since the last server call, so why does .observe() fires if there were no changes to live data?
That is an intended behavior, as you can see in documents:
observe (LifecycleOwner owner,
Observer observer) Adds the given observer to the observers list within the lifespan of the given owner. The events are
dispatched on the main thread. If LiveData already has data set, it
will be delivered to the observer.
If you want to observe the change in view state then you should create and observe a view state instead of a network request, google already provided an example for cases like this.
In addition to answer above, it's important to understand the scenarios in which using ViewModel & LiveData observers, to observe only once, this article explains them and shows a way to deal with it easily: Working with LiveData and Events
I have used MutableSharedFlow instead of MutableLiveData, and solved the same problem as yours.
You can try this:
private val responseCode = MutableSharedFlow<Int>()
...
fun passChangeCallback() {
viewModelScope.launch {
responseCode.emit(serverResponse)
}
Because MutableSharedFlow don't replay a value that has already emited by defalut.

Android MVP - RxJava and retrofit - best approach

I'm figuring out how to develop an Android app, using MVP, RxJava2 and retrofit.
In my presenter, here is the code:
public void loadData() {
compositeDisposable.dataModelRepository.getDataList().subscribeOn(Schedulers.io())
.observeOn(mainScheduler).subscribe(new Consumer<List<Data>>() {
#Override
public void accept(List<Data> dataList) throws Exception {
if (!dataList.isEmpty())
view.displayData(dataList);
else
view.displayEmpty();
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
System.out.println(throwable.toString());
view.displayError("boooom");
}
});
}
Retrofit interface has been defined in the following way:
#GET("/fooURL")
Single<List<Data>> getDataList();
And the repository is just
public Single<List<Data>> getDataList() {
return retrofitApi.getDataList();
}
And it is working fine. Question is as follows: my intention is to fetch network data only when data is not available locally, in db.
Having this in mind, is it correct that schedulers are managed in the presenter? Or should they be managed in the Repository?
My guess is that presenter is the correct place, as it creates a thread so repository can do its stuff sequentially (fetch db, if nothing, then fetch network/cache; return data wherever it has been fetched), and when data is provided, notify the view inside the accept method of the Consumer.
Is it correct? Or should it be done in a different way?
Another point is: how can i test using Mockito the repository? The dataModelRepository.getDataList() method i mean? Not sure how to do any Assert for Single objects...
Thanks in advance!
I suggest you to offload all business logic that is related to fetching, retrieving data to a central repository.
One way to achieve somewhat similar to what you have described is to use a concat operator.
Observable<List<Data>> getData() {
return Observable
.concat(localRepository.getData(), remoteRepository.getData())
.first();
}
This will try to get data from your local repository first and if it has no data it will make a network request.
I assume your local and remote repositories will be observed on a new thread, but if you need to perform any action on the UI, simply subscribe on a main thread in your presenter.

Retrofit, Otto and storing models in RAM

In our project we are forbidden to store data from server (confidentional information). But once downloaded and parsed models are used in multiple fragments. Data on server updates once a day and forced logout occurs in midnight. I want to store parced models in memory and remove it at exit from app.
In current implementation I have two single-instance classes - RestClient and DataStorage, which in constructors are registered to Otto bus. I instance them in Application class:
#Override
public void onCreate() {
super.onCreate();
new RestClient(this);
new DataStorage();
}
DataStorage for every model have metods:
#Subscribe
public void onModelComplete(Model model) {
this.model = model;
}
#Produce
public Model produceModel() {
return model;
}
Fragments send events throw bus to RestClient and receive results from RestClient or "produced" results from DataStorage (if data is received while app in background).
The problem is that when the user exits the application, data is not deleted because the application does not die. How to implement storing in memory with clearing data on exit without killing process?

Categories

Resources