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

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.

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);
}

querying multipe documents of acollection

image of databaseI have a collection in the firebase firestore inside which I have multiple documents. I want to retrieve a document based on the data field which is present in the document how can I do this in android.
I have done this
firebaseFirestore = FirebaseFirestore.getInstance();
CollectionReference collectionReference = firebaseFirestore.collection("Planes");
Query query = collectionReference.whereEqualTo("starting",startAirport).whereEqualTo("destination",destinationAirport);
FirestoreRecyclerOptions<Planes> response = new FirestoreRecyclerOptions.Builder<Planes>()
.setQuery(query, Planes.class)
.build();
firestoreRecyclerAdapter= new FirestoreRecyclerAdapter<Planes,PlaneViewHolder>(response) {
#NonNull
#Override
public PlaneViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.planes_item,parent,false);
return new PlaneViewHolder(view);
}
#Override
protected void onBindViewHolder(#NonNull PlaneViewHolder holder, int position, #NonNull Planes model) {
holder.book.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
DocumentSnapshot snapshot = getSnapshots().getSnapshot(holder.getAdapterPosition());
final String DocId= snapshot.getId();
Intent intent=new Intent(PlaneRequestActivity.this,UserDetails.class);
intent.putExtra("Document",DocId);
intent.putExtra("tripId",tripId);
startActivity(intent);
}
});
holder.setPlanes(model);
}
#Override
public void onError(#NonNull FirebaseFirestoreException e) {
super.onError(e);
Toast.makeText(PlaneRequestActivity.this,"Error:"+e.toString(),Toast.LENGTH_LONG).show();
}
};
plane.setAdapter(firestoreRecyclerAdapter);
}
it works for only the first document. It didn't find the second Document of the Collection

How to show progress dialog until the data loads in FirestoreRecyclerAdapter

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.

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);

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);

Categories

Resources