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.
Related
I want to save data from the API in the RecyclerView so that when rotating the screen is not reloaded
I think I can use onSaveInstanceState but still don't really understand how to use it
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final RecyclerView rvTVShow = view.findViewById(R.id.rv_shows);
rvTVShow.setHasFixedSize(true);
rvTVShow.setLayoutManager(new LinearLayoutManager(getActivity()));
ApiInterface apiService =
ApiClient.getClient().create(ApiInterface.class);
Call<MovieResponse> call = apiService.getTVShow(API_KEY);
call.enqueue(new Callback<MovieResponse>() {
#Override
public void onResponse(#NonNull Call<MovieResponse> call, #NonNull Response<MovieResponse> response) {
final List<Movies> movies = Objects.requireNonNull(response.body()).getResults();
TvShowAdapter tvShowAdapter = new TvShowAdapter(movies , R.layout.list_movies);
rvTVShow.setAdapter(tvShowAdapter);
....
}
I will explain how savedInstanceState works while refactoring your code.
First: Create a global Movie object and an Adapter for it
List<Movies> movies = new ArrayList();
TvShowAdapter tvShowAdapter = null;
Re-initialize adapter under activity onCreate
tvShowAdapter = new TvShowAdapter(movies , R.layout.list_movies);
rvTVShow.setAdapter(tvShowAdapter);
Create a new method to handle movie
data population
public void populateRV(List<Movies> movies)
{
this.movies = movies;
//notify adapter about the new record
tvShowAdapter.notifyDataSetChanged();
}
Insert data to Movies object under your Response callback
movies = Objects.requireNonNull(response.body()).getResults();
populateRV(movies);
Everytime the orientation of an activity changes Android resets the states of all views by redrawing them. This causes non persistent data to be lost. But before redrawing views it calls the method onSavedInstanceState
Hence we can prevent state loss by saving the state of our views using the already defined onSavedInstanceState method provided by android.
Add the following block inside the overridden onSavedInstanceState method of your activity
//this saves the data to a temporary storage
savedInstanceState.putParcelableArrayList("movie_data", movies);
//call super to commit your changes
super.onSaveInstanceState(savedInstanceState);
Next is to recover the data after orientation change is completed
Add the following block in your activity onCreate and make sure it comes after initializing your adapter
//...add the recyclerview adapter initialization block here before checking for saved data
//Check for saved data
if (savedInstanceState != null) {
// Retrieve the data you saved
movies = savedInstanceState.getParcelableArrayList("movie_data");
//Call method to reload adapter record
populateRV(movies);
} else {
//No data to retrieve
//Load your API values here.
}
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.
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 want to receive a string from addValueEventListener() method I use to resell the data from the database Firebase. The data arrive correctly.
But when certain to get the string out of that method to use it in another, it returns nothing.
You have tips?
I already tried putExtras and also create a method on purpose but it did not work.
final DatabaseReference mPostReference = FirebaseDatabase.getInstance().getReference().child("user-daily").child(getUid()).child("2017-Year");
mPostReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
final ArrayList<String> labels = new ArrayList<String>();
for (DataSnapshot data : dataSnapshot.getChildren()){
final DailyItem dailyItem = data.getValue(DailyItem.class);
labels.add(dailyItem.mese);
}
title.setText(labels.get(position));
a = title.getText().toString();
}
#Override
public void onCancelled(DatabaseError databaseError) {
Toast.makeText(view.getContext(),"database error",
Toast.LENGTH_SHORT).show();
}
});
//this return null... why?
String title = a;
The data is loaded from Firebase asynchronously. By the time you run title = a, the onDataChange method hasn't been called yet. Set some breakpoints in a debugger to verify this, it's key to understanding how asynchronous loading works.
The solution is to reframe your problem from "first get the object, then do blabla with the title" to "start getting the object; once the object is available, do blabla with the title".
In code this translates to:
final DatabaseReference mPostReference = FirebaseDatabase.getInstance().getReference().child("user-daily").child(getUid()).child("2017-Year");
mPostReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
final ArrayList<String> labels = new ArrayList<String>();
for (DataSnapshot data : dataSnapshot.getChildren()){
final DailyItem dailyItem = data.getValue(DailyItem.class);
labels.add(dailyItem.mese);
}
title.setText(labels.get(position));
// Do blabla with the title
String title = title.getText().toString();
}
#Override
public void onCancelled(DatabaseError databaseError) {
Toast.makeText(view.getContext(),"database error",
Toast.LENGTH_SHORT).show();
}
});
Many developers new to Firebase (and other modern web APIs, as they all work this way) struggle with this problem. So I recommend you also check out their questions and answers:
Cannot access firebaseObjectObservable outside of set
Android Firebase get value of child without DataChange
Value of a global variable is reset after it is initialised in ValueEventListener
can't get values out of ondatachange method
ArrayList not updating inside onChildAdded function
Setting Singleton property value in Firebase Listener
and most others in this list of search results
In order to retrieve the string from method addValueEventListener in viewmodel or any other network call, it is recommended to use the either MutableLiveData<T> or LiveData<T> and observe the same in your activity. Observer will observe the changes, and as soon as string got filled up, the observer method will automatically give you string which you are looking.
You need to create reference variable for the LiveData<T> reference_variable wherever your addValueEventLister is located and set its value in your addValueEventListener.
And then in your viewmodel create the returning value function like below...
Observe this function in your activity and you will have your string.
public MutableLiveData<TotalRunsWicketsAndData> getDisplayableDetails() {
return observableLiveData;
}
I am using MutableLiveData here.
This is a trick which does it. It would be easy to do so if you have less data to retrieve from ValueEventListener.
Inside the onDataChange(), use a setText to set the required value in it. Keep the visibility of this text view as "Gone". Then retrieve using getText outside the ValueEventListener.
You can retrieve the whole list by using GenericTypeIndicator. Follow the official guide on here
I have an activity and a model called CourseDetails.
String getData;
DatabaseReference mRef = FirebaseDatabase.getInstance().getReference().child("courses").child("Business");
mRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
CourseDetails c = dataSnapshot.getValue(CourseDetails.class);
getData = c.getCourseName();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
textview1.setText(getData);
Using above code throws NullPointerException at last line above. But if I put textview1.setText(getData) into the ValueEventListener, under getData = c.getCourseName(), the data can be displayed correctly.
Methods I found working are using SharedPreferences or setting data from a method such as public void display(String data) { textview1.setText(data); }. But what are the other ways to keep the retrieved data even if the data is outside ValueEventListener?
For instance I want to persist the data added into an ArrayList.
ArrayList<String> listData;
DatabaseReference mRef = FirebaseDatabase.getInstance().getReference().child("courses").child("Business");
mRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot ds : dataSnapshot.getChildren()) {
CourseDetails c = dataSnapshot.getValue(CourseDetails.class);
String code = c.getCourseCode();
String name = c.getCourseName();
String CodeName = code + " " + name;
listData.add(CodeName);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
// data in ArrayList should be able to display here
StringBuilder builder = new StringBuilder();
for (String s : listData) {
builder.append(s + "\n");
}
textview1.setText(builder.toString());
How to achieve this kind of persistence?
As per my understanding, Firebase will notify all it's data listener attached to specific references (database references wherever the addValueEventListener is added) when those specific data gets modified. That is when
onDataChange will be called, when there is modification of the data at those database references,
(besides modification the method will always be called first time).
And this happens
asynchronously, so in the first case where null occurs because we don't know whether data is retreived from Firebase and
as far as I know, Android's main thread cannot be put on hold or pause until we retreive the data that's why we use Asynchronous tasks in Android.
So, I think the best way to do specific updates or task on data change is within onDataChange method. So, like you stated it could be
done by making those changes within onDataChange itself or by calling some other method from onDataChange.
Or, if you are using
adapter then, notifying adapter about the change within onDataChange. Also, you can take a look at other choice i.e. FirebaseRecyclerAdapter then,
it will handle the update automatically without any extra effort.