For an app i'm creating I have a RecyclerView that gets filled with data from a Firebase Database. This RecyclerView is also in a fragment that's used in a Viewpager. All and all I am able to make it work, but what I notice is, is that on the first run it takes a while for the fragment to show it's content in the RecyclerView, so I wanted to add a ProgressBar to it as an Asynchronous Task.
My Task:
private class ProgressTask extends AsyncTask<Void, Void, Void> {
#Override
protected void onPreExecute() {
mProgressLinearLayout.setVisibility(View.VISIBLE);
}
#Override
protected Void doInBackground(Void... params) {
loadColleagueData();
mListener = initRealTime();
mAdapter = new CustomAdapter(mUserList, getActivity());
return null;
}
#Override
protected void onPostExecute(Void res) {
mRecyclerView.setAdapter(mAdapter);
mProgressLinearLayout.setVisibility(View.GONE);
}
}
Now this works actually. The ProgressBar gets shown and also is hidden again once it's done. But the problem is, is that it's done in such a short time that it's not visible. So I only know that it works because of debugging. What still takes a while is the RecyclerView actually showing the content.
So what I guess my problem is, is that not so much getting the data from the Database is the thing that takes a while, just more setting the Adapter to the RecyclerView and showing it's content is the thing that takes a while.
Now from what I understood is, is that it's not possible to set the adapter in the doInBackground since you can't do anything with the views outside of the main ui thread. So I was wondering if there is a way to hide the ProgressBar only once the content is visible in my RecyclerView?
(If this is a duplicate question i'm awfully sorry, my search on the internet did not manage to help me)
You should use the FirebaseDatabase callbacks, you don't need to use an AsyncTask for this. If you what to listen for single event, that means that you just want to download the data, then use this:
mProgressLinearLayout.setVisibility(View.VISIBLE);
DatabaseReference node = FirebaseDatabase.getInstance().getReference().child("your_node");
node.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
loadColleagueData();
mListener = initRealTime();
mAdapter = new CustomAdapter(mUserList, getActivity());
mRecyclerView.setAdapter(mAdapter);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
mProgressLinearLayout.setVisibility(View.GONE);
If you want your Adapter to be allways up-to-date you can attach listeners to that specific nodes using addChildEventListener() or addValueEventListener() (docs here). Or you can use this grate library, you just need to copy one file:
https://github.com/mmazzarolo/firebase-recyclerview
Related
I want to put data from firebase database in a list, however I get the error : "java.lang.IndexOutOfBoundsException: Invalid index 0, size is 0", I even tried to add to the list manually inside the valueEventListener as shown in the comment, but still it's empty
this is my code :
public class Playing extends AppCompatActivity implements View.OnClickListener {
public static List<Question> list_question=new ArrayList<>();
Question currentQuestion;
int index=0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_playing);
final DatabaseReference myRef = FirebaseDatabase.getInstance().getReference().child("quiz");
myRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
/*
Question question=new Question();
question.setQuestion("eeee");
question.setAnswerA("zzzzz");
question.setAnswerB("aazss");
question.setAnswerC("ytyty");
question.setAnswerD("jkjkjkjk");
question.setCorrectAnswer("A");
list_question.add(question);
*/
for (DataSnapshot ds:dataSnapshot.child("questions").getChildren())
{
Question question=new Question();
question.setQuestion(ds.child("question").getValue().toString());
question.setAnswerA(ds.child("A").getValue().toString());
question.setAnswerB(ds.child("B").getValue().toString());
question.setAnswerC(ds.child("C").getValue().toString());
question.setAnswerD(ds.child("D").getValue().toString());
question.setCorrectAnswer(ds.child("sol").getValue().toString());
list_question.add(question);
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
currentQuestion = list_question.get(index);
What is missing in my code ?
addValueEventListener is asynchronous and returns immediately. Your code goes on to execute list_question.get(index), but list_question is still empty. The listener you provided will not get invoked until some time later, after the database query is complete. There is no guarantee how long it will take.
If you want to use the results of a query, you must wait until the asynchronous database operation is complete. This means that you can only use the results inside the listener callback itself.
This methods to retrieve data is asynchronous. Which means that this isn't executed in the chronogical order.
Try to use the debug mode and you will see by yourself that it executes the inside of the listener after executing the rest of the code.
Put the
currentQuestion= list_question.get(index)
Inside the listener, just after the line where you add your question and you will see that it works.
It is just that you are trying to access the list before it is getting filled.
You can check for the size of the list by putting an if.
if(list_question.size() > 0)
currentQuestion = list_question.get(index);
Also make sure that you are doing this logic once a data change event happened.
Hope this helps.
Happy coding :)
I'm developing an application that display a list of items and allows the users to add more items and check the items' details.
Let's say that I keep my database references in the activity, adding ChildEventListener listeners onResume() and removing them on onPause():
public void onResume() {
super.onResume();
mItemChildEventListener = mDatabaseReference.child('items').addChildEventListener(new ChildEventListener() {
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
mAdapter.addItem(dataSnapshot.getValue(Item.class));
}
...
};
}
public void onPause() {
super.onPause();
if (mItemChildEventListener != null) {
mDatabaseReference.removeEventListener(mItemChildEventListener);
}
}
In these ChildEventListener, I'm adding item by item to the list of items and calling notifyItemInserted(itemArray.size() - 1) in the adapter.
public void addItem(Item item) {
if (mItems == null) {
mItems = new ArrayList<>();
}
mItems.add(item);
notifyItemInserted(mItems.size() - 1);
}
Also, to keep the application running smoothly, I'm keeping the list of items in a Reteiner Fragment, saving this values onSaveInstanceState() and restoring them on onCreate().
protected void onCreate(#Nullable Bundle savedInstanceState) {
if (savedInstanceState != null) {
mItems = getState(STATE_ITEMS);
} else {
mItems = new ArrayList<>();
}
mAdapter = new ItemAdapter(mItems);
}
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
saveState(STATE_ITEMS, mItems);
}
public <T> T getState(String key) {
//noinspection unchecked
return (T) mRetainedFragment.map.get(key);
}
public void saveState(String key, Object value) {
mRetainedFragment.map.put(key, value);
}
public static class RetainedFragment extends Fragment {
HashMap<String, Object> map = new HashMap<>();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// retain this fragment
setRetainInstance(true);
}
}
However, if I unregister and register the listener again, for example, by any orientation change or leaving and returting to the activity, as expected, onResume() will be called, and by adding the ChildEventListener again, the same data is retrieved, which, in case if it's not compared with the data that is already contained in the list of items, mItems, it will be duplicated.
Also, if I do not keep the list of items, mItems, and retrieve the data every time onResume() is called, the list loses its scroll position and also blinks, since it takes some time to retrieve the data.
Question
How can I keep the list of items retrieved without retrieving them again again, but keep listening for changes in the items already retrieved and for new items added? Is there a better solution than comparing the items already retrieved with the new items, for example, by their key?
I've tried looking in the documentation and other questions, but I couldn't find any information relevant for this situation. Also, I have tried to keep the reference to mDatabaseReference in onSaveInstanceState(), as I have done with the list of items mItems, but it has't worked either.
If you enable persistence in the Firebase Realtime Database SDK, that will help prevent unnecessary fetches of data that's already been received. The SDK will cache fetch data locally and prefer to use that data first when it's available:
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
Be sure to read the documentation to understand how this works.
Also bear in mind that it's traditional to start and stop listener during onStart and onStop. This will avoid even more refetches if the app loses focus for a moment (for example, it pops up a dialog, or some other transparent activity appears on top).
Just make a Firebase call inside a Loader and save the data in a static variable or list or whatever data structure suits you.
Initialize the Loader in onCreate.
In the Loader, override the onStartLoading() method and in onStart() call this method.
Inside onStartLoading() simply check if the static variable is null or not. If it is null, startLoading. Else do not load, and set the previous data as data source.
The advantage of using Loaders is, in case of orientation changes, it won't make network calls as AsyncTask does.
I Googled lot about this notifydataset change issue, still i m unable to find answer, i have a listview containing custom object.
My implementation looks like this
1) A list of custom objects
2) A Adapter to which i provide the custom object list
My Quesion:
When i delete any item from list, in backend i'm simply calling remove from the custom object list. and if i call notifyDatasetchanged, its not working :(..
Its not refreshing the list, i dont no where is it missing. Kindly help me what is the procedure to update list in this senarios
Take a look at my answer in this thread.
Let me know if you are still having problems.
cheers!
Use AsynkTask for Custom ListView Like this:-
InboxTask.execute(); will call asynktask
class InboxTask extends AsyncTask<Uri, Integer, ArrayList<InboxField>>
{
#Override
protected void onPreExecute()
{
pd=ProgressDialog.show(HomePage.this, "", "Please wail...",true,false);
super.onPreExecute();
}
#Override
protected ArrayList<InboxField> doInBackground(Uri... params)
{
return ArrayList<InboxField>
}
#Override
protected void onPostExecute(ArrayList<InboxField> result)
{
inboxAdapter=new InboxAdapter(HomePage.this,result);
list.setAdapter(inboxAdapter);
adapter.notifyDataSetChanged();
list.destroyDrawingCache();
pd.dismiss();
}
put adepter.notigyDataSetChanged in onPostExecute Method
each time my activty receives a message (from some TCP listening thread), it does
mLstAdpChatScreen.add(line);
updateUI();
private void updateUI()
{
runOnUiThread(new Runnable()
{
public void run()
{
mLstAdpChatScreen.notifyDataSetChanged();
mLstAdpChatScreen.notifyDataSetInvalidated();
mLstVwChatScreen.requestLayout();
mLstVwChatScreen.invalidate();
}
});
}
While this approach works on most of my listviews and they do get updated, it does not for a certain listview. I must be missing something :-?
Thank you
That should work for most of the cases however I got the same problem when trying to update listview from database. I called adapter.notifyDataSetChanged(); too, but listview didn't update at all although I could confirmed the data was changed, and the ArrayList's size increased.
In the end, I implemented a method inside my custom Adapter extending from BaseAdapter just to call notifyDataSetChanged there, like
public class MyAdapter extends BaseAdapter{
public void updateData(){
this.notifyDataSetChanged();
}
}
Then in Activity, I just call adapter.updateData(). And this worked for me. Very weird.
I'm parsing a large XML document and showing its contents in a ListView. Initially I was parsing the entire thing and then showing it all at once, but this takes pretty long to load (~30 seconds on some devices).
Now, as soon as my SAX handler gets an object I add it to an array and notify my list adapter that the data changed. This brings up a new problem because I'm rapidly refreshing the ListView the user is unable to scroll or select an item.
Is there a more efficient way to handle this?
Thanks.
Two things come to mind.
Make sure you are loading the data in a background thread
Don't overwhelm the UI loading one object at a time. Do them in batches of 10 or whatever number makes sense for you app.
I recommend you to use a progressdialog in a class that extends AsyncTask.
class MyAsyncLoad extends AsyncTask{
ProgressDialog myprogsdial;
#Override
protected void onPreExecute(){
myprogsdial = ProgressDialog.show(MyActivity.this, null, "Loading...", true);
}
#Override
protected Void doInBackground(Void... params) {
//here you are doing parsing stuff and load data into your datastructure, or whatever you use to store data from XML
return null;
}
#Override
protected void onPostExecute(Void result){
myprogsdial.dismiss();
//here you update the cursor,something like
adapter = new CursorAdapter(context, cursor...)
setListAdapter(adapter);
adapter.notifyDataSetChanged();
}
}