How to avoid downloading same data from firebase realtime database? - android

I'm creating an Android app with an activity with a bottom navigation control that lets the user navigate between different fragments. In these fragments i have lists of data coming from a firebase backend that i show with a RecyclerView.
The problem is that every time i navigate between these fragments all the data is downloaded again, while i would want to use cached data and just listen for changes.
What i have done so far is to use ViewModel and LiveData and they work fine. Moreover if i disconnect the phone from the Internet the data is showed (and of course is not downloaded), even if i navigate between the fragments.
In the fragment that shows the data i have:
LiveData<List<UncompletedTask>> taskLiveData = viewModel.getTaskLiveData();
taskLiveData.observe(this, new Observer<List<UncompletedTask>>() {
#Override
public void onChanged(List<UncompletedTask> uncompletedTasks) {
myAdapter.submitList(uncompletedTasks);
listener.onTodoListElementsLoaded(uncompletedTasks.size());
}
});
In the viewmodel i have:
private TodoTaskRepository repository;
#NonNull
public LiveData<List<UncompletedTask>> getTaskLiveData() {
return repository.getTaskLiveData();
}
In the TodoTaskRepository i initialize FirebaseQueryLiveData in the contructor and return it in getTaskLiveData().
Finally FirebaseQueryLiveData is like this:
public class FirebaseQueryLiveData extends LiveData<DataSnapshot> {
private static final String LOG_TAG = "FirebaseQueryLiveData";
private final Query query;
private final MyValueEventListener listener = new MyValueEventListener();
public FirebaseQueryLiveData(Query query) {
this.query = query;
}
#Override
protected void onActive() {
query.addValueEventListener(listener);
}
#Override
protected void onInactive() {
query.removeEventListener(listener);
}
private class MyValueEventListener implements ValueEventListener {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
setValue(dataSnapshot);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.e(LOG_TAG, "Can't listen to query " + query, databaseError.toException());
}
}
}
How can i download all the data the first time but then just listen for changes and don't download the same data while navigating between fragments if nothing is changed?

If you have enabled disk persistence then data will not be download again unless data has changed
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
When you run your ValueEventListener the first time data is downloaded alright, the second time the same ValueEventListener runs then data is coming from local cache persistent
Moreover if disconnect the phone from the Internet the data is indeed coming from the same local cache.

Related

How to display data in Fragment and Activity (both independent) from same Snapshot Listener (Firebase)

Would like to have your help on my weird problem that currently I am facing. I tried for couple of days but no luck and finally decided to post here to take help.
I created a Snapshot Listener attached to a Collection in Firebase defined as follows :-
public class FirebaseTypingStatusLiveData extends LiveData<List<documentSnapshot>> {
// Logging constant
private static final String TAG = "FirebaseQueryLiveData";
// Document Reference
private final DocumentReference documentReference;
// Listener
private final MyDocumentListener listener = new MyDocumentListener();
// Handler
private final Handler handler = new Handler();
private ListenerRegistration listenerRegistration;
// Flag to remove listener
private boolean listenerRemovePending = false;
private MutableLiveData <List<documentSnapshot> mutableLiveData = new MutableLiveData<>();
// Constructor
public FirebaseTypingStatusLiveData(DocumentReference documentReference) {
this.documentReference = documentReference;
}
public LiveData<List<documentSnapshot>> checknow(){
// Add listener
if (!Listeners.LIVESAMPLE.containsKey(documentReference)) {
listenerRegistration = documentReference.addSnapshotListener(listener);
Listeners.LIVESAMPLE.put(documentReference, listenerRegistration);
} else {
listenerRegistration = Listeners.LIVETYPINGSTATUSSAMPLE.get(documentReference);
}
return mutableLiveData;
}
// Listener definition
private class MyDocumentListener implements EventListener<DocumentSnapshot> {
#Override
public void onEvent(#Nullable DocumentSnapshot documentSnapshot, #Nullable
FirebaseFirestoreException e) {
Log.d(TAG, "onEvent");
// Check for error
if (e != null) {
// Log
Log.d(TAG, "Can't listen to query snapshots: " + documentSnapshot
+ ":::" + e.getMessage());
return;
}
setValue(documentSnapshot);
mutableLiveData.setValue(documentSnapshot);
}
}
}
}
The snapshot reads the data perfectly and advised as and when data is available.
The snapshot data is getting displayed 1. in Fragment (not part of Activity that i am talking about) 2. Activity through two view models that have the same code as follows :
#NonNull
public LiveData<List<documentSnapshot>> getDataSnapshotLiveData() {
Firestore_dB db = new Firestore_dB();
DocumentReference docref = db.get_document_firestore("Sample/"+docID);
FirebaseTypingStatusLiveData firebaseTypingStatusLiveData = new
FirebaseTypingStatusLiveData(docref);
return firebaseTypingStatusLiveData.checknow();
}
The Fragment & Activity code is also same except changing owner which are as follows :-
LiveData<List<documentSnapshot>> liveData = viewmodel.getDataSnapshotLiveData();
liveData.observe(this, new Observer<List<documentSnapshot>>() {
#Override
public void onChanged(DocumentReference docreef) {
String name = docreef.get("name");
stringname.setText(name); // The text is displaying either in Fragment or in Activity but not in both.
});
My problem is i need data in both i.e. Fragment & Activity whereas I am getting data either in Fragment or in Activity depending upon the code which I commented.
Kindly advise where I am making mistake. Thanks in Advance
Honestly, I am not sure that my answer wouldn't lead you away to the false way, but you can try.
My guess is that your problem could be somehow connected with ViewModel sharing.
There is a well-known task How to share Viewmodel between fragments.
But in your case, that can't help, because you have to share ViewModel between activities (now you have two separate ViewModels and that could be problem with Firestore EventListeners).
Technically you can share ViewModel between activities (I haven't try since usually I use Single activity pattern). For that as a owner parameter in ViewModelProvider constructor you can set instance of your custom Application class (but you have implement interface ViewModelStoreOwner for it). After that both in your activity and in your fragment you can get the same ViewModel with the Application class-instance:
val sharedViewModel = ViewModelProvider(mainApplication, viewModelFactory).get(SharedViewModel::class.java)
I made LiveData static that listens to changes in source data and provide updated content were ever required in different Activity.

Observed LiveData doesn't really update after first fetch

I'm building an offline-first app with the database setup as the single source of truth. I am using Room to simplify the database handling, and LiveData to simplify observable data patterns.
I am also using Retrofit to make any network calls required to populate the database with new data.
I have set up an observer in my Fragment as follows:
private void setUpObserver() {
tfViewModel = ViewModelProviders.of(getActivity()).get(TFViewModel.class);
tfViewModel.getAllPosts().observe(getActivity(),
newPosts -> {
if (newPosts != null && newPosts.size() > 0) {
lottieAnimationView.setVisibility(View.INVISIBLE);
mPostsAdapter.updateItems(newPosts);
}
});
tfViewModel.fetchNextData(currentPage);
}
When my app first starts, I'm deleberately truncating each table in my database using Room callbacks so that new data is fetched every time. (For testing. This beats the offline-first experience and must not be done in production.)
Anyway, so when it first starts, it calls the fetchNextData method of the viewmodel which in turn asks the Repository to fetch the data.
Here's my ViewModel:
public class TFViewModel extends AndroidViewModel {
private TFRepository mRepository;
private LiveData<List<Post>> mPostList;
public TFViewModel(Application application) {
super(application);
mRepository = new TFRepository(application);
mPostList = mRepository.getAllPosts();
}
public LiveData<List<Post>> getAllPosts() {
return mPostList;
}
public void fetchNextData(int page) {
mRepository.fetchNextPosts(page);
}
}
In the repository, I use my DAOs to insert posts into the database. To fetch new data, I use a Service Class to fetch new data for me. When the fetch call returns, I use an AsyncTask to insert the new posts to my database. (Details omitted for brevity):
public class TFRepository {
private PostDao postDao;
private LiveData<List<Post>> postList;
private RetrofitSingleton retrofitSingleton;
public TFRepository(Application application) {
TFRoomDatabase db = TFRoomDatabase.getDatabase(application);
postDao = db.postDao();
retrofitSingleton = RetrofitSingleton.getInstance(application.getApplicationContext());
postList = postDao.getAllPosts();
}
public LiveData<List<Post>> getAllPosts() {
return postList;
}
public void fetchNextPosts(int page) {
getPostList(page);
}
private void getPostList(int page) {
APICaller.getInstance(retrofitSingleton).getFeed(page,
new NetworkResponseListener<BaseResponse<FeedResponse>>() {
#Override
public void onResponseReceived(BaseResponse<FeedResponse> feedResponseBaseResponse) {
if (feedResponseBaseResponse == null) return;
List<Post> posts = feedResponseBaseResponse.getData().getPosts();
new insertAllPostsAsyncTask(postDao).execute(posts);
}
#Override
public void onError(String errorMessage) {
}
});
}
}
The OBSERVER I had setup in my fragment gets an empty list the first time around. The API call returns with the first page of posts and it receives 10 posts the second time. The view is popualted. Everything is good.
Problem: As the user scrolls down, the Fragment asks the ViewModel to fetch more data. The ViewModel asks the Repository to fetch new data. The Retrofit call goes and comes back with the new data. It is inserted in the database. BUT THE OBSERVER IS NOT NOTIFIED. What am I missing?
NOTE: I do not want to use a MutableLiveData as I want to maintain the DB as the single source of truth. Also, as the docs state that LiveData is notified whenever the underlying DB changes, my implementation should work with LiveData.

Simultaneous connections in firebase

If I set scoresRef.keepSynced(false) and use Disk Persistence, FirebaseDatabase.getInstance().setPersistenceEnabled(true); to store the data locally, will it lower down the number of "Simultaneous connections" to firebase DB as there will be no active listeners(or it isn't?) ? what may be the consequences?
Codes:
I have a custom adapter "firebaseadapter" and a class "firebasestore" with getter/setter methods. Since "calls to setPersistenceEnabled must be made before any other usage of firebase Database instance", I have made a different class extending Application(or using it in main activity class with static {} is better?).
Utility.calculateNoOfColumns is calculating the number grids to be shown based on screen size.
Moreover, Will the data get updated in client side in real time if I make any changes in firebase DB if the set scoresRef.keepSynced(false)?
public class ThreeFragment extends Fragment {
View viewThree;
ArrayList<firebasestore> list;
DatabaseReference mdatabase;
GridLayoutManager gridLayoutManager;
private firebaseAdapter firebaseAdapter1;
FirebaseDatabase database;
public ThreeFragment() {
// Required empty public constructor
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FirebaseApp.initializeApp(getContext());
database= FirebaseDatabase.getInstance();
mdatabase=database.getReference().child("DBName");
mdatabase.keepSynced(false);
list = new ArrayList<>();
loadStoreDetails();
}
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
viewThree = inflater.inflate(R.layout.fragment_three, container, false);
int mNoOfColumns = Utility.calculateNoOfColumns(getContext());
RecyclerView firebaseRecyclerView = (RecyclerView)
viewThree.findViewById(R.id.recyclerview_threeFragment1);
firebaseRecyclerView.setHasFixedSize(true);
firebaseAdapter1 = new firebaseAdapter(getContext(), list);
firebaseRecyclerView.setLayoutManager(gridLayoutManager);
firebaseRecyclerView.setAdapter(firebaseAdapter1);
return viewThree;
}
// get data from firebase DB
private void loadStoreDetails() {
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
list.clear(); // CLAER DATA BEFORE CHANGING. IF NOT DONE, IT WILL SHOW DUPLICATE DATA
for(DataSnapshot ds : dataSnapshot.getChildren()) {
list.add(ds.getValue(firebasestore.class));
}
firebaseAdapter1.notifyDataSetChanged(); // NOTIFY ADAPTER TO SHOW DATA IN VIEW WITHOUT RELOAD
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.w("LogFragment", "loadLog:onCancelled", databaseError.toException());
}
};
mdatabase.limitToLast(20).addValueEventListener(valueEventListener);
}
}
If there are no active listeners for a minute, the Firebase client will indeed close its connection to the server.
In your code you call loadStoreDetails attaches a listener with addValueEventListener from onCreate. Since you never remove that listener, it will stay active permanently from the moment ThreeFragment is created until the program exits.
To prevent this, and ensure the data is only synchronized (and the connection kept open) while the user has the fragment open, detach the listener in onDestroyView or onDestroy of the fragment.
For that, add a member field to the fragment:
ValueEventListener mFragmentListener;
Then keep a reference to the listener when you attach it:
mFragmentListener = mdatabase.limitToLast(20).addValueEventListener(valueEventListener);
And finally remove the listener when the fragment is destroyed:
#Override
public void onDestroyView() {
mdatabase.limitToLast(20).removeEventListener(mFragmentListener);
}
On a separate note: the call to mdatabase.keepSynced(false); is not needed in your code, as that is the default behavior already.

Get data from firebase with Result of LiveData in viewmodel

I want to get 2 objects from firebase and I want to display combine results on the Recyclerview using viewmodel. I have created a viewmodel that gets the one object from firebase and based on id from this object I want to get the other object and update the ui.
some code of view model class
private static final DatabaseReference POST_REF =
FirebaseDatabase.getInstance().getReference("/Post");
private final FirebaseQueryLiveData liveData = new
FirebaseQueryLiveData(POST_REF);
#NonNull
public LiveData<DataSnapshot> getDataSnapshotLiveData() {
return liveData;
}
FirebaseQueryLiveData class
private final MyValueEventListener listener = new MyValueEventListener();
public FirebaseQueryLiveData(DatabaseReference ref) {
this.query = ref;
}
#Override
protected void onActive() {
Log.d(LOG_TAG, "onActive");
query.addValueEventListener(listener);
}
#Override
protected void onInactive() {
Log.d(LOG_TAG, "onInactive");
query.removeEventListener(listener);
}
How to get the second object from firebase using the result of first and notify the livedata about it
First object is post of users and second object is userinfo . I want to display combine result on Ui.
DataBase Structure I Want to get post and based on ids in post I want to get userdetails
I think step# 5 in this tutorial is the answer to your problem.
Here you can update UI once data in firebase changes.

Firebase Database Android Issues

I have recently been working with Android development. I have been developing a social networking app. For the app, I decided to create a separate helper class for all database methods. In my database, all users have a user id and their information is stored under this id. I have a (non-static) method in this class that would get certain User information when given a DatabaseReference to the user's information location. The method would simply take the reference, add a listener for single value event (addListenerForSingleValueEvent(ValueEventListener)). I was encountering problems with this so I tried putting a Log statement in the onDataChange() method of the ValueEventListener. Oddly enough, this Log method was never reached. Even more strange is the fact that, if I copy and paste the code from this method into one of the locations where I need it, the Log statement is reached. Does anyone have any idea as to why this happens? This is a method that I am using in multiple activities and copying and pasting the code everywhere would make the code very sloppy. Any help would be much appreciated. Thank you!
Update: It turns out the code works if placed in the Database class, but the Log statement will only run after the method is over. Below is the an outline of the class I am using to observe this.
Fragment Class
public class FragmentClass extends Fragment {
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDatabase = DatabaseManager.getInstance();
String userId = "userId";
mDatabase.getUserFromUserId(userId);
}
}
Database Class
public class DatabaseManager {
private static FirebaseDatabaseManager mInstance;
private static FirebaseDatabase mDatabase;
public static FirebaseDatabaseManager getInstance() {
if(mInstance == null) {
mInstance = new FirebaseDatabaseManager();
}
return mInstance;
}
private FirebaseDatabaseManager() {
mDatabase = FirebaseDatabase.getInstance();
}
public void getUserFromUserId(final String userId) {
DatabaseReference userReference = mDatabase.getReference(userId);
userReference.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Log.i("databaseTag", "reached");
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.i("databaseTag", "reached");
}
});
while(true) { // if this part is commented out, the log statement will be executed; otherwise, it won't
}
}
}

Categories

Resources