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.
Related
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.
I am trying to retrieve data from Firebase realtime-database and put it on a CardView inside a RecyclerView inside a Fragment.
But the Fragment shown is blank white with no error. I retrieve the data inside OnCreate method and add it into a List.
While debugging the application, found out that even after assigning the retrieved data inside the onCreate method, the list is still NULL inside the onCreateView method.
Fragment Dashboard List Class:
public class fragment_dashboard_list extends Fragment {
List<ibu> ibu_ibu;
FirebaseDatabase database;
DatabaseReference myRef ;
String a;
public fragment_dashboard_list() {}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ibu_ibu = new ArrayList<>();
database = FirebaseDatabase.getInstance();
myRef = database.getReference("Guardian");
myRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// This method is called once with the initial value and again
// whenever data at this location is updated.
for(DataSnapshot dataSnapshot1 : dataSnapshot.getChildren()){
ibu value = dataSnapshot1.getValue(ibu.class);
ibu ibu_val = new ibu();
String alamat = value.getAlamat();
String foto = value.getFoto();
String hp = value.getHp();
String ktp = value.getKtp();
String nama = value.getNama();
String privilege = value.getPrivilege();
String ttl = value.getTtl();
ibu_val.setAlamat(alamat);
ibu_val.setFoto(foto);
ibu_val.setHp(hp);
ibu_val.setKtp(ktp);
ibu_val.setNama(nama);
ibu_val.setPrivilege(privilege);
ibu_val.setTtl(ttl);
// Here the List ibu_ibu is not NULL
ibu_ibu.add(ibu_val);
}
}
#Override
public void onCancelled(DatabaseError error) {
// Failed to read value
Log.w("Hello", "Failed to read value.", error.toException());
}
});
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_dashboard_list, container, false);
RecyclerView myrv = (RecyclerView) view.findViewById(R.id.dashboard_recycler_view);
//Here the List ibu_ibu is null
adapter_list_ibu myAdapter = new adapter_list_ibu(ibu_ibu);
LinearLayoutManager LinearLayoutManager = new LinearLayoutManager(getContext());
myrv.setLayoutManager(LinearLayoutManager);
myrv.setAdapter(myAdapter);
return view;
}
}
I expected the List to be not NULL inside OnCreateView so the Fragment wont Blank
Firebase APIs are asynchronous, which means that onDataChange() method returns immediately after it's invoked, and the callback will be called some time later. There are no guarantees about how long it will take. So it may take from a few hundred milliseconds to a few seconds before that data is available.
Because that method returns immediately, your ibu_val list that you're trying to use it outside the onDataChange() method, will not have been populated from the callback yet and that's why is always empty.
Basically, you're trying to use a value of variable synchronously from an API that's asynchronous. That's not a good idea, you should handle the APIs asynchronously as intended.
A quick solve for this problem would be to notify the adapter once you got all elements from the database using:
myrv.notifyDatasetChanged();
So add this line of code right after where the for loop ends.
If you intent to use that list outside the callback, I recommend you see the last part of my anwser from this post in which I have explained how it can be done using a custom callback. You can also take a look at this video for a better understanding.
I'm trying to get a user's profile from a Firebase DB. Then using the user's information I want to set TextViews in my Fragment's layout to reflect the user's individual stats.
The problem is that the rootViw is being returned prior to having recieved the user's profile. And so I get a null object reference error.
My understanding of the fragment's life cycle is that onCreate() is created first and so I tried placing the DB code there but I get the same problem. I then figured that if accessing the DB is slower than my onCreateView() I'll place a Thread.sleep() timer to wait for the DB call to complete and then perform the rest of my code. Which I know is a stupid solution but just wanted to test my theory; that also failed so obviously my understanding is wrong.
Where should I place my DB call so that it completes prior to returning my rootView? Why does placing the DB listener in OnCreate() not work and why does the Thread.sleep() delay not work?
Leaderboard Fragment
public class Leaderboard extends Fragment{
private FirebaseAuth mFirebaseAuth;
private DatabaseReference mUserDatabaseReference;
private User user;
private TextView scoreView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_leaderboard,
container, false);
scoreView = rootView.findViewById(R.id.leaderboard_score);
mFirebaseAuth = FirebaseAuth.getInstance();
final String userUID = mFirebaseAuth.getCurrentUser().getUid();
mUserDatabaseReference =
FirebaseDatabase.getInstance().getReference("users");
mUserDatabaseReference.addListenerForSingleValueEvent(new
ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
if (snapshot.hasChildren()) {
for (DataSnapshot messageSnap: snapshot.getChildren()) {
if(messageSnapshot.getKey().equals(userUID)) {
user = messageSnapshot.getValue(User.class);
}}}}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
//Causes error because user==null
scoreView.setText("Score: " + user.getScore());
return rootView;
}
}
All Firebase APIs are asynchronous. You should expect that listeners may be called after any amount of time, based on the quality of the hardware and its network connection. Don't ever use Thread.sleep() to try to control the timing of things - that is an anti-pattern.
My suggestion to you is to inflate a "loading" screen in onCreateView() to display immediately, so the user doesn't have to look at a blank screen when your fragment starts. Then, when your listener is called with the data you want to display, add or update other views as needed.
Why not set the score after you get the DB result ?
mUserDatabaseReference.addListenerForSingleValueEvent(new
ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
if (snapshot.hasChildren()) {
for (DataSnapshot messageSnap: snapshot.getChildren()) {
if(messageSnapshot.getKey().equals(userUID)) {
user = messageSnapshot.getValue(User.class);
}
}
scoreView.setText("Score: " + user.getScore());
}}
#Override
public void onCancelled(DatabaseError databaseError) {}
});
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
}
}
}
I'm using the FirebaseListAdapter and I need to display a ProgressDialog, but I don't know how to know when it finishes loading.
public class TabFragment1 extends ListFragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.tab_fragment_1, container, false);
ListView eventosView = (ListView) rootView.findViewById(android.R.id.list);
Firebase ref = new Firebase(//myappurl);
Firebase usersRef = new Firebase(//myappurl/eventos/users);
Singleton singletonUser = Singleton.getInstance();
setUser(singletonUser);
Firebase ref = new Firebase(//myappurl/eventos);
FirebaseListAdapter<Evento> mAdapter = new FirebaseListAdapter<Evento>(getActivity(), Evento.class, R.layout.event_row, ref) {
#Override
protected void populateView(View view, Evento evento) {
((TextView)view.findViewById(R.id.eventTitle)).setText(evento.getNome());
((TextView)view.findViewById(R.id.eventPlace)).setText(evento.getLocal());
}
};
setListAdapter(mAdapter);
return mAdapter;
return rootView;
}
private void setUser(Singleton singletonUser){
singletonUser.getToken();
singletonUser.setUuid();
singletonUser.getUuid();
}
}
When can I place the ProgressDialog.dismiss()?
Firebase is built on the premise of synchronizing data. For this reason there is never really a moment when it is done synchronizing.
The initial set of data for a location is downloaded in one go and results in a call to onChildAdded() for each child, followed by a single onDataChanged(). If you want to hide the progress bar when this initial data has loaded, you can register a ValueEventListener for a single event:
ref.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// the initial data has been loaded, hide the progress bar
}
#Override
public void onCancelled(DatabaseError firebaseError) {
}
});
In the above snippet, all the children will have been added to your ListView already. Depending on your use-case, this may be the right time to hide the progress bar or it may be later than wanted.
If you want to know when the first data has been rendered, you can register an observer with your adapter:
adapter.registerDataSetObserver(new DataSetObserver() {
#Override
public void onChanged() {
super.onChanged();
// the first time you get here, hide the progress bar
}
#Override
public void onInvalidated() {
super.onInvalidated();
}
});
You could use this approach to hide the progress bar as soon as the first data has arrived from Firebase.
Since january 2017 FirebaseListAdapter has the following method that can be overridden (commit by Frank on github):
/**
* This method will be triggered each time updates from the database have been completely processed.
* So the first time this method is called, the initial data has been loaded - including the case
* when no data at all is available. Each next time the method is called, a complete update (potentially
* consisting of updates to multiple child items) has been completed.
* <p>
* You would typically override this method to hide a loading indicator (after the initial load) or
* to complete a batch update to a UI element.
*/
protected void onDataChanged() {
}