How to show progress dialog until the data loads in FirestoreRecyclerAdapter - android

I am using FirestoreRecyclerAdapter to load the data from Firestore into RecyclerView. I would like to show progress dialog until the data loads completely. How do I do that? Thanks in advance.
Here is my code so far.
void loadPosts() {
FirestoreRecyclerOptions<Post> options = new FirestoreRecyclerOptions.Builder<Post>()
.setQuery(query, Post.class)
.build();
adapter = new FirestoreRecyclerAdapter<Post, DataViewHolder>(options) {
#Override
protected void onBindViewHolder(#NonNull DataViewHolder dataViewHolder, int i, #NonNull Post post) {
dataViewHolder.setData(post.getTitle(), post.getDescription());
}
#NonNull
#Override
public DataViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.single_post, parent, false);
return new DataViewHolder(view);
}
};
recyclerView.setAdapter(adapter);
adapter.startListening();
}
#Override
protected void onStop() {
super.onStop();
if (adapter != null) {
adapter.stopListening();
}
}
#Override
protected void onStart() {
super.onStart();
if (adapter != null) {
adapter.startListening();
}
}

Before .setQuery(query) in your FirebaseRecyclerAdapter builder you can attach a listener to that query to know when it has completed in order to build the RecyclerView
Example
progressDialog.show();
Query query = db.collection("Users").child(my_user_id).addListenerForSingleValue(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//Data has been fetched, hide progressDialog , check if data exists at the location with if(dataSnapshot.exists()) if you want to be sure theres something to fetch
progressDialog.hide();
}
#Override
public void onCancelled(DatabaseError databaseError) {
//The fetch failed, dismiss the dialog and show an error message
progressDialog.hide();
}
});
Check the methods of Query

While Gastón's answer works, there is a slightly simpler way, that doesn't require an extra listener. The FirestoreRecyclerAdapter has a method that signals when it has received a complete QuerySnapshot.
void loadPosts() {
FirestoreRecyclerOptions<Post> options = new FirestoreRecyclerOptions.Builder<Post>()
.setQuery(query, Post.class)
.build();
progressDialog.show();
adapter = new FirestoreRecyclerAdapter<Post, DataViewHolder>(options) {
#Override
public void onDataChanged() {
progressDialog.hide();
}
#Override
protected void onBindViewHolder(#NonNull DataViewHolder dataViewHolder, int i, #NonNull Post post) {
dataViewHolder.setData(post.getTitle(), post.getDescription());
}
#NonNull
#Override
public DataViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.single_post, parent, false);
return new DataViewHolder(view);
}
};
recyclerView.setAdapter(adapter);
adapter.startListening();
}
Also see the FirebaseUI documentation on FirestoreRecyclerAdapter data and error events.
Note that onDataChanged may be called multiple times, once initially and then each time when the data for query changes. So make sure that the code in there can handle being called multiple times. For example: if you destroy the progress dialog in onDataChanged instead of hiding it, you'll want to check for its existence too.

Related

How to implement ViewModel with FirebaseRecyclerAdapter

I am trying to separate my database from my UI controller using ViewModel and prevent constant data usage. I have searched for solution for days now without any headway.
I am using Firebase RecyclerAdapter in my app and the code is working well, but I don't know how to implement a LiveData Observer with the RecyclerAdapter.
any sample app or snippet will be of great help to me.
Here is a sample code from my Fragment
private void fetchValues() {
Query query1 = mDatabaseReference;
FirebaseRecyclerOptions<Course> options = new FirebaseRecyclerOptions.Builder<Course>()
.setQuery(query1, new SnapshotParser<Course>() {
#NonNull
#Override
public Course parseSnapshot(#NonNull DataSnapshot snapshot) {
return new Course(snapshot.child("course_title").getValue().toString(),
snapshot.child("course_code").getValue().toString(),
snapshot.child("course_lecturer").getValue().toString());
}
})
.build();
mFirebaseRecyclerAdapter = new FirebaseRecyclerAdapter<Course, CourseViewHolder>(options){
#NonNull
#Override
public CourseViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
mCourseItemBinding = CourseItemBinding.inflate(LayoutInflater.from(getActivity()), parent, false);
View view = mCourseItemBinding.getRoot();
return new CourseViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull CourseViewHolder holder, int position, Course course) {
holder.bind(course);
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Toast.makeText(getActivity(), String.valueOf(position), Toast.LENGTH_LONG).show();
}
});
}
};
mRecyclerView.setAdapter(mFirebaseRecyclerAdapter);
}

Adapter in FirebaseUI is getting null

I've used firebase Recycler Adapter to get data. But always getting error,
Attempt to invoke virtual method 'void
androidx.recyclerview.widget.RecyclerView.setAdapter(androidx.recyclerview.widget.RecyclerView$Adapter)'
on a null object reference
My fragment is
public class MyBooksFragment extends BaseFragment {
private ShimmerFrameLayout mShimmerViewContainer;
private RecyclerView recyclerView ;
private FirebaseRecyclerAdapter adapter;
private FirebaseUser user= FirebaseAuth.getInstance().getCurrentUser();
#Override
public BaseFragment provideFragment() {
return new MyBooksFragment();
}
#Override
public View provideFragmentView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_my_books,parent,false);
mShimmerViewContainer=view.findViewById(R.id.mybook_shimmer_view_container);
RecyclerView recyclerView = view.findViewById(R.id.recycler_view_books);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
fetchBooks(1);
FloatingActionButton fab = view.findViewById(R.id.addBookFab_);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(getActivity(), BookAdder.class);
startActivity(intent);
}
});
return view;
}
private FirebaseRecyclerOptions<Book> queryBuilderParser(){
Query bookQuery= FirebaseDatabase
.getInstance()
.getReference()
.child("Books")
.orderByChild("ownerID")
.equalTo(user.getUid());
FirebaseRecyclerOptions<Book> options =
new FirebaseRecyclerOptions.Builder<Book>()
.setQuery(bookQuery,Book.class)
.build();
return options;
}
protected void fetchBooks(int type)
{
Log.e("profile",type+" ");
FirebaseRecyclerOptions<Book> options;
Query bookQuery= FirebaseDatabase
.getInstance()
.getReference()
.child("Books")
.orderByChild("ownerID")
.equalTo(user.getUid());
options = new FirebaseRecyclerOptions.Builder<Book>()
.setQuery(bookQuery,Book.class)
.build();
Log.e("profile","inside fetchReviw "+options.getSnapshots());
adapter= new FirebaseRecyclerAdapter<Book, BookViewHolder>(options) {
#NonNull
#Override
public BookViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.book_row_item, parent, false);
return new BookViewHolder(view);
}
#Override
protected void onBindViewHolder(#NonNull BookViewHolder bookViewHolder, int i, #NonNull Book book) {
bookViewHolder.book_name.setText(book.getBookName());
bookViewHolder.book_author.setText(book.getAuthors());
bookViewHolder.book_category.setText(book.getCategory());
bookViewHolder.book_rating.setText(book.getRating()+"");
Glide.with(getActivity()).load(book.getThumbnail()).into(bookViewHolder.img_thumbnail);
}
#Override
public void onDataChanged() {
if(getItemCount()>0 && mShimmerViewContainer!=null){
mShimmerViewContainer.stopShimmerAnimation();
mShimmerViewContainer.setVisibility(View.GONE);
}
}
#Override
public void onError(DatabaseError e) {
// Called when there is an error getting data. You may want to update
// your UI to display an error message to the user.
// ...
Log.e("profile","database error");
}
};
recyclerView.setAdapter(adapter);
}
}
Here, I've extended a base fragment which extends Fragment.
You haven't put the right arguments into the adapter. The adapter takes 4 arguments in:
Object class
List item layout resource
Viewholder Class
Query/firebase reference
Here is how it should look, also the adapter does a lot of the hard word so you only need to use the populateViewHolder function:
FirebaseRecyclerViewAdapter<Object, ObjectViewHolder> adapter = new FirebaseRecyclerViewAdapter<Object, ObjectViewHolder>
(Object.class, android.R.layout.*list_item_layout*, ObjectViewHolder.class, objectQuery) {
public void populateViewHolder(ObjectViewHolder ObjectViewHolder, Object Object) {
//in your case the following lines:
bookViewHolder.book_name.setText(book.getBookName());
bookViewHolder.book_author.setText(book.getAuthors());
bookViewHolder.book_category.setText(book.getCategory());
bookViewHolder.book_rating.setText(book.getRating()+"");
Glide.with(getActivity()).load(book.getThumbnail()).into(bookViewHolder.img_thumbnail);
}
};
recycler.setAdapter(mAdapter);

FirebaseRecyclerAdapter duplicating the objects in recycle view

My Structure:
I am developing a chat app by using firebase realtime database. I am using FirebaseRecyclerAdapter to display messages. But the thing is on scrolling, messages get duplicated and out of order.
Here is my implementation:
public class FirebaseChatActivity extends AppCompatActivity {
private String messageSenderId;
private String messageReceiverId;
private FirebaseRecyclerAdapter<ChatMessage, MessageViewHolder> firebaseRecyclerAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_firebase_chat);
RecyclerView recyclerView = findViewById(R.id.rv_firebase_chat_activity);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
messageSenderId = "eoSU5m7PyucyC9h30JfHhV6S8Av2";
messageReceiverId = "ZOqofCid0XN5ovIAj1mXhRYxdnO2";
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
Query query = rootRef.child("messages").child(messageSenderId).child(messageReceiverId);
FirebaseRecyclerOptions<ChatMessage> firebaseRecyclerOptions = new FirebaseRecyclerOptions.Builder<ChatMessage>()
.setQuery(query, ChatMessage.class)
.build();
firebaseRecyclerAdapter = new FirebaseRecyclerAdapter<ChatMessage, MessageViewHolder>(firebaseRecyclerOptions) {
#Override
protected void onBindViewHolder(#NonNull MessageViewHolder messageViewHolder, int position, #NonNull ChatMessage message) {
if (message.getMessageType().equals("text")) {
messageViewHolder.showMessage.setText(message.getMessageText());
} else if (message.getMessageType().equals("image")) {
try {
Glide.with(getApplicationContext())
.load(message.getPhotoUrl())
.placeholder(R.drawable.no_image2)
.error(R.drawable.image_1)
.into(messageViewHolder.photoImageView);
} catch (Exception e) {
Log.d("MessageAdapterLog", e.toString());
}
}
}
#Override
public MessageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
android.view.View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_item_right, parent, false);
return new MessageViewHolder(view);
}
};
recyclerView.setAdapter(firebaseRecyclerAdapter);
}
private class MessageViewHolder extends RecyclerView.ViewHolder {
public TextView showMessage;
public ImageView photoImageView;
public MessageViewHolder(#NonNull android.view.View itemView) {
super(itemView);
showMessage = itemView.findViewById(R.id.show_message);
photoImageView = itemView.findViewById(R.id.photo_image_view);
}
}
#Override
protected void onStart() {
super.onStart();
firebaseRecyclerAdapter.startListening();
}
#Override
protected void onStop() {
super.onStop();
if (firebaseRecyclerAdapter!= null) {
firebaseRecyclerAdapter.stopListening();
}
}
}
I have tried many different solutions e.g set as mentioned here as well as .addChildEventListener (completely different implementation) but unable to solve the issue.
Full Activity code as well as database structure is attached. Kindly let me know what I am doing wrong.
Thanks
The problem is that everytime you scroll, to make a new item visible again it will run the code inside your onBindViewHolder to inflate that view and rebind it.
if (message.getMessageType().equals("text")) {
messageViewHolder.showMessage.setText(message.getMessageText());
} else if (message.getMessageType().equals("image")) {
try {
Glide.with(getApplicationContext())
.load(message.getPhotoUrl())
.placeholder(R.drawable.no_image2)
.error(R.drawable.image_1)
.into(messageViewHolder.photoImageView);
} catch (Exception e) {
Log.d("MessageAdapterLog", e.toString());
}
}
}
to solve this problem you will need to override two methods inside your adapter
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}

Recycler View showing only very last item added to realtime database

I have a firebase strucuture as
The recycler view is only displaying the last added or say latest added node to the database instead of displaying each and every node
the database reference i am using is
r2 = FirebaseDatabase.getInstance().getReference().child("Uploads").child("Wheat").child(S).child(D).child(T).child(FirebaseAuth.getInstance().getUid());
and the code for recycler view is as
public class MyPost extends AppCompatActivity {
private RecyclerView recyclerView;
private DatabaseReference r, r2, r3;
private String S, D, T;
FirebaseRecyclerAdapter<RecyclerCropView, ViewHolder2> Fbra1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_post);
r = FirebaseDatabase.getInstance().getReference().child("User_Data").child(MainDashboard.type).child(FirebaseAuth.getInstance().getUid());
recyclerView = (RecyclerView) findViewById(R.id.R_view2);
r.addListenerForSingleValueEvent(rr);
}
private ValueEventListener rr = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
S = dataSnapshot.child("State").getValue().toString();
D = dataSnapshot.child("City").getValue().toString();
T = dataSnapshot.child("Tehsil").getValue().toString();
// Toast.makeText(MyPost.this,S+D+T.toString(),Toast.LENGTH_LONG).show();
r2 = FirebaseDatabase.getInstance().getReference().child("Uploads").child("Wheat").child(S).child(D).child(T).child(FirebaseAuth.getInstance().getUid());
// Toast.makeText(MyPost.this,r2.toString(),Toast.LENGTH_LONG).show();
r2.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
r3=r2.child(dataSnapshot.getKey());
Toast.makeText(MyPost.this, r3.toString(), Toast.LENGTH_LONG).show();
FirebaseRecyclerOptions<RecyclerCropView> options = new FirebaseRecyclerOptions.Builder<RecyclerCropView>().setQuery(r3, RecyclerCropView.class).build();
Fbra1 = new FirebaseRecyclerAdapter<RecyclerCropView, ViewHolder2>(options) {
#Override
protected void onBindViewHolder(#NonNull ViewHolder2 holder, int position, #NonNull RecyclerCropView model) {
holder.setProductImage(model.getProduct_Image());
holder.setProduct(model.getProduct());
holder.setMax(model.getMaximumPrice());
holder.setQuantityUnit(model.getQuantityUnit());
holder.setQuantity(model.getQuantity());
}
#NonNull
#Override
public ViewHolder2 onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_post, parent, false);
return new ViewHolder2(v);
}
};
Fbra1.notifyDataSetChanged();
recyclerView.hasFixedSize();
recyclerView.setLayoutManager(new LinearLayoutManager(MyPost.this));
Fbra1.startListening();
recyclerView.setAdapter(Fbra1);
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
};
#Override
protected void onRestart() {
super.onRestart();
finish();
}
}
Recycler view is only showing very last item that was added to database.
Note:-
The item.xml file has width set to wrap content.
if i remove the childEventListener the recyclerView shows multiple items with
null on the place of TextView(These are those textView on which data retrived was supposed to be set)
Here is the problem: r2.addChildEventListener
Classes implementing this interface can be used to receive events about changes in the child locations of a given DatabaseReference ref. Attach the listener to a location using addChildEventListener(ChildEventListener) and the appropriate method will be triggered when changes occur.
Firebase Docs
What you should use is a r2.addListenerForSingleValueEvent
This returns the children of the specific node. But now since you are using FirebaseUI library, you will not need to listen as it is a listener itself.
So your code should listen to the root of the posts. This r3=r2.child(dataSnapshot.getKey()); is unnecessary because it only gets one entry
Change to this
FirebaseRecyclerOptions<RecyclerCropView> options = new FirebaseRecyclerOptions.Builder<RecyclerCropView>().setQuery(r2, RecyclerCropView.class).build();
Fbra1 = new FirebaseRecyclerAdapter<RecyclerCropView, ViewHolder2>(options) {
#Override
protected void onBindViewHolder(#NonNull ViewHolder2 holder, int position, #NonNull RecyclerCropView model) {
holder.setProductImage(model.getProduct_Image());
holder.setProduct(model.getProduct());
holder.setMax(model.getMaximumPrice());
holder.setQuantityUnit(model.getQuantityUnit());
holder.setQuantity(model.getQuantity());
}
#NonNull
#Override
public ViewHolder2 onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_post, parent, false);
return new ViewHolder2(v);
}
};
Fbra1.notifyDataSetChanged();
recyclerView.hasFixedSize();
recyclerView.setLayoutManager(new LinearLayoutManager(MyPost.this));
Fbra1.startListening();
recyclerView.setAdapter(Fbra1);

How to refresh data in FirebaseAdapter using FirebaseUI when query is changed?

I want to allow users to sort and filter data. I have this implementation of Firebase Adapter and everything is working great. When I want to filter data using e.g. price, data in RecyclerView is not changing. Can you please help and tell what am doing wrong?
This is the query - depends on what user selected from Spinner:
Query query;
if (!priceLower.equals("Any")) { // option from Spinner
query = FirebaseDatabase.getInstance()
.getReference()
.child("Students")
.orderByChild("price")
.startAt(priceLower)
.limitToLast(50);
} else if (!priceHigher.equals("Any")) {
query = FirebaseDatabase.getInstance()
.getReference()
.child("Students")
.orderByChild("price")
.endAt(priceHigher)
.limitToLast(50);
} else {
query = FirebaseDatabase.getInstance()
.getReference()
.child("Students")
.orderByChild("price")
.limitToLast(50);
}
getData(query);
This is the method to fetch data from Firebase:
public void getData(Query query) {
FirebaseRecyclerOptions<Students> firebaseOptions =
new FirebaseRecyclerOptions.Builder<Students>()
.setQuery(query, Students.class)
.build();
FirebaseAdapter = new FirebaseRecyclerAdapter<Students, StudentsHolder>(firebaseOptions) {
#NonNull
#Override
public StudentsHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_students, parent, false);
return new StudentsHolder(view);
}
#Override
protected void onBindViewHolder(#NonNull StudentsHolder holder, int position, #NonNull final Students student) {
holder.setName(student.getName());
holder.setPrice(student.getPrice());
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// ...
}
});
}
#Override
public void onDataChanged() {
progressDialog.setVisibility(View.GONE);
mRecyclerView.setAdapter(FirebaseAdapter);
}
};
}
To solve this, you should add at the end of your getData() method, the following line of code:
FirebaseAdapter.startListening();
This means that everytime you change the query, you create and set a new adapter and you also start listening for changes.
#Override
public void onDataChanged() {
progressDialog.setVisibility(View.GONE);
mRecyclerView.setAdapter(FirebaseAdapter);
}
This method is called only when data on firebase database is changed. But according to your requirement, this data remains the same and only your applied filter or sorting method is changing. Therefore this method will never be called and firebaseadapter will reflect original query only.
Try it this way:
public void getData(Query query) {
FirebaseRecyclerOptions<Students> firebaseOptions =
new FirebaseRecyclerOptions.Builder<Students>()
.setQuery(query, Students.class)
.build();
FirebaseAdapter = new FirebaseRecyclerAdapter<Students, StudentsHolder>(firebaseOptions) {
#NonNull
#Override
public StudentsHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_students, parent, false);
return new StudentsHolder(view);
}
#Override
protected void onBindViewHolder(#NonNull StudentsHolder holder, int position, #NonNull final Students student) {
holder.setName(student.getName());
holder.setPrice(student.getPrice());
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// ...
}
});
}
#Override
public void onDataChanged() {
}
};
progressDialog.setVisibility(View.GONE);
mRecyclerView.setAdapter(FirebaseAdapter);
}
getData() method should be called whenever your filter is changed.

Categories

Resources