Here's my database:
Database:{
X:{
JK-KDUKSIDKSIIJDSL1:{
text:"hello",
name:"Donald J. Drunk"
}
JK-KDadDFDDIJDSL1:{
name:"Killery Hlinton"
}
}
}
And I want to filter my FirebaseRecyclerAdapter such that only add to my RecyclerView data that contains the key text. How is this possible? How can I add it? Current code:
mAdapter = new FirebaseRecyclerAdapter<Campaign, CampaignHolder>(Campaign.class, R.layout.recyclerview_template, CampaignHolder.class, ref) {
#Override
public void populateViewHolder(final CampaignHolder viewHolder, final Campaign campaign, final int position) {
findViewById(R.id.progress_bar).setVisibility(View.INVISIBLE);
MainActivity.this.holder = viewHolder;
viewHolder.setTitle(campaign.title);
//... Other "set" methods
}
}
You could try overriding the onBindViewHolder method in your FirebaseRecyclerAdapter. Something like this:
mAdapter = new FirebaseRecyclerAdapter<Campaign, CampaignHolder>(Campaign.class, R.layout.recyclerview_template, CampaignHolder.class, ref) {
#Override
public void onBindViewHolder(CampaignHolder viewHolder, int position) {
Campaign model = getItem(position);
if(model.getText() != null){
populateViewHolder(viewHolder, model, position);
}
}
#Override
public void populateViewHolder(final CampaignHolder viewHolder, final Campaign campaign, final int position) {
findViewById(R.id.progress_bar).setVisibility(View.INVISIBLE);
MainActivity.this.holder = viewHolder;
viewHolder.setTitle(campaign.title);
//... Other "set" methods
}
};
With this approach you would still pull all data at the query location but entries without a text attribute just wouldn't be displayed. If that is unsatisfactory and you want to minimize the amount of data you download per query then the only other option I see is closer to what is mentioned in the comments above - meaning you would need to create a separate location that only stores those entries which have a text value.
Related
I am really stuck here, I need to get the Post class value that is held in users/user_id/latest_post only some users have this value. so I thought:
query.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
if (dataSnapshot.hasChild("latest_post")) {
Post latest = dataSnapshot.child("latest_post").getValue(Post.class);
latest_posts = latest;
post = latest;
}
}
should work, it does in getting me the values and ignoring the ones that don't contain a latest_posts child. But my recycler adapter and view-holder doesn't seem to use this and when I try setting up the recycler view is says the value it's trying to get is null.
FirebaseRecyclerOptions<Post> firebaseRecyclerOptions = new FirebaseRecyclerOptions.Builder<Post>()
.setQuery(query, Post.class)
.build();
firebaseRecyclerAdapter = new FirebaseRecyclerAdapter<Post, PostHolder>(firebaseRecyclerOptions) {
#Override
protected void onBindViewHolder(#NonNull PostHolder postHolder, int position, #NonNull Post post) {
postHolder.setPost(post);
}
#Override
public PostHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.post_content, parent, false);
return new PostHolder(view);
}
};
Here is the rest of the firebase adapter ^ and then here is the setPost function:
void setPost(Post post) {
String key = post.getPost_id();
Integer reads = post.getReads();
String titleString = post.getTitle();
String bodyString = post.getBody();
String usernameString = post.getAuthor();
String category = post.getCategory();
System.out.println("setPost " + usernameString);
...
}
Top of file I am holding:
Post post;
which is updated with the values in the childEventListener. Any ideas where I am messing up? Thank you.
UPDATE:
I have made these changes using my User class which contains the Post class (latest_post):
void setupRecycler(Query query) {
query = FirebaseDatabase.getInstance()
.getReference()
.child("users");
FirebaseRecyclerOptions<User> firebaseRecyclerOptions = new FirebaseRecyclerOptions.Builder<User>()
.setQuery(query, User.class)
.build();
firebaseRecyclerAdapter = new FirebaseRecyclerAdapter<User, PostHolder>(firebaseRecyclerOptions) {
#Override
protected void onBindViewHolder(#NonNull PostHolder postHolder, int position, #NonNull User user) {
if (user.getLatest_post().getAuthor() != null) {
postHolder.setPost(user);
}
}
#Override
public PostHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.post_content, parent, false);
return new PostHolder(view);
}
};
}
Now I am trying to filter when the setPost(user) is called so it only lets through if the latest_posts value is not null. But I must be misunderstanding something, because it gives me the first two names but still attempts to use the vaues which dont have latest_post which results in crashing due to null.
UPDATE 2:
Getting closer, this works:
query = FirebaseDatabase.getInstance()
.getReference()
.child("users").orderByChild("latest_post");
I'm guessing because it orders them by the users that have latest_post, but it crashes if I scroll to the bottom because Then all the rest are still there, so it attempts to set a string value to a textView when it is null. How do I exclude these pesky things?
It was because you can't filter a query to exclude posts that do not exist, and you do not have access to the list that firebaseRecyclerAdapter uses. The only solution to accurately and safely filter the data the way I needed was to use a custom recyclerView Adapter, and a list of my class, and filter and order the list as needed after the query.
I am using FirestoreRecyclerAdaper with the normal form of retrieving and showing data.
my data becomes bigger and I want to make pagination in it, but I didn't find any tutorial to this point. I read documentations but nothing show how to do that because their is only one adapter and 2 queries and FirestorRecyclerAdaper accept only one query.
so is their a solution without changing my code?
final Query query = firebaseFirestore
.collection("Ads")
.orderBy("creationDate", Query.Direction.DESCENDING).limit(5);
query.get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
DocumentSnapshot lastVisible = queryDocumentSnapshots
.getDocuments().get(queryDocumentSnapshots.size() - 1);
Query next = firebaseFirestore.collection("Ads")
.orderBy("creationDate")
.startAfter(lastVisible)
.limit(10);
}
});
options = new FirestoreRecyclerOptions
.Builder<MyAdCard>()
.setQuery(query, MyAdCard.class)
.build();
dapter = new FirestoreRecyclerAdapter<MyAdCard, AllCardViewHolder>(options) {
#Override
protected void onBindViewHolder(#NonNull final AllCardViewHolder holder, final int position, #NonNull MyAdCard model) {
holder.publicAd_discription.setText(model.getMyAddiscriptiontxt());
}
#NonNull
#Override
public AllCardViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.cardview_categoryads, parent, false);
return new AllCardViewHolder(view);
}
};
categoryAdsRV.setAdapter(dapter);
private class AllCardViewHolder extends RecyclerView.ViewHolder {
public AllCardViewHolder(View itemView) {
super(itemView);
}
TextView publicAd_discription = itemView.findViewById(R.id.publicAd_Discription);
}
#Override
protected void onStart() {
super.onStart();
dapter.startListening();
}
#Override
protected void onStop() {
super.onStop();
if ( dapter != null ) dapter.stopListening();
}
There is nothing you can do here without changing your code significantly.
There is a FirestorePagingAdapter from Firebase-UI that may help you with the changes you'll need to make.
There is also a sample code I wrote with one example of using Android Architecture Components Paging along with both Firestore and Realtime Database.
How to get The current Item position Of FirebaseRecyclerAdpter
madapter = new FirebaseRecyclerAdapter<user, contact.UserViewHolder>(user.class, R.layout.activity_dialogs_list, contact.UserViewHolder.class, muserref) {
#Override
protected void populateViewHolder(final UserViewHolder viewHolder, user model, final int position) {
final String ais = model.getName();
viewHolder.setName(model.getName());
final String b = model.getQuery();
(final UserViewHolder viewHolder, user model, ---->final int position<------)
how to use that int position to find the current position.
I'm not sure what is your concrete problem, but I suggest You t use
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html
It is more flexible if you extend this class, and create your own adapter.
In this case you will have a dataset, which can be easily modified.
Also, the accessing of data is simpler.
Actually that position in the populateViewHolder is the current position of the view.
Easiest way is to create an integer value position in the ViewHolder class and assign the value to it like this
madapter = new FirebaseRecyclerAdapter<user, contact.UserViewHolder>(user.class, R.layout.activity_dialogs_list, contact.UserViewHolder.class, muserref) {
#Override
protected void populateViewHolder(final UserViewHolder viewHolder, user model, final int position) {
final String ais = model.getName();
viewHolder.setName(model.getName());
viewHolder.setPosition(position);
final String b = model.getQuery();
And you can use the position by viewHolder.getPosition();
I am getting movie data from the internet like its name, poster etc, but for the genres of the movie, I need to fetch it again from the net. So here's my solution for this problem.
public class MoviesViewAllAdapter extends RecyclerView.Adapter<MoviesViewAllAdapter.MoviesViewHolder> {
private Context mContext;
private List<MovieBrief> mMovies;
public MoviesViewAllAdapter(Context context, List<MovieBrief> movies) {
mContext = context;
mMovies = movies;
}
#Override
public MoviesViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new MoviesViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_movie_large,parent,false));
}
#Override
public void onBindViewHolder(MoviesViewHolder holder, int position) {
holder.movieGenreTextView.setText("");
setGenres(holder, mMovies.get(position).getId());
}
#Override
public int getItemCount() {
return mMovies.size();
}
public class MoviesViewHolder extends RecyclerView.ViewHolder {
public TextView movieGenreTextView;
public MoviesViewHolder(View itemView) {
super(itemView);
movieGenreTextView = (TextView) itemView.findViewById(R.id.text_view_genre_movie_card);
}
}
private void setGenres(final MoviesViewHolder holder, Integer movieId) {
ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);
Call<Movie> call = apiService.getMovieDetails(movieId,mContext.getResources().getString(R.string.MOVIE_DB_API_KEY));
call.enqueue(new Callback<Movie>() {
#Override
public void onResponse(Call<Movie> call, Response<Movie> response) {
if(response.code() != 200) return;
List<Genre> genresList = response.body().getGenres();
String genres = "";
for (int i=0;i<genresList.size();i++) {
if(i == genresList.size()-1) {
genres = genres.concat(genresList.get(i).getGenreName());
}
else {
genres = genres.concat(genresList.get(i).getGenreName()+", ");
}
}
holder.movieGenreTextView.setText(genres);
}
#Override
public void onFailure(Call<Movie> call, Throwable t) {
}
});
}
}
But here problem is that when performing fling and going up the recyclerview the genres which are showing is not related to the movie, the genres which are loading are random.
May be because I am loading data on onBindViewHolder and when holder disappears from screen it loads into random holder. Is it so ?
I believe your problem is that you are getting the entire movie list every time you create a view in your list. I am not sure how the server is returning that data but my guess is that every time you call the server there is no guarantee that the data is in the same order. You are getting a random order every time but trying to extract a fixed position of it and that is why the genres are not related.
The problematic line is onBindViewHolder which is called every time a view is created and this function is calling setGenres which is getting a new movie list which is in random order.
You can do two things to fix this issue:
Search for the movie first, find its index and then use that to get the genre. But this is still a very bad design since for a list of N movies, you are calling the server N times.
Get the list first, store it as an ArrayList in your Adapter. Now iterate through it without having to call the server each time
public class MoviesViewAllAdapter extends RecyclerView.Adapter<MoviesViewAllAdapter.MoviesViewHolder> {
.
.
.
List<Movie> list = new ArrayList<>;
public void setList(List movies){
//get data from server before creating the adapter. call this on your adapter and store the data here
this.list = movies;
}
private void setGenres(final MoviesViewHolder holder, Integer movieId){
//iterate the field list instead of calling the server
}
.
.
.
}
The problem with the wrong genres being displayed is caused by the recycling of the view/holder preformed by the RecyclerView . When you scroll instances of MoviesViewHolder and the view are reused so when you trigger the loading of a Movie details a ViewHolder is associated to a MovieRef but by the time the call to get the movie details ends the holder is now assigned to a different movie.
The best thing to do in my opinion is to load the Movie details and cache them in a map for example HashMap<Integer, Movie> mMoviesDetails; when the call to the API ends you can store the Movie object in there.
public void onResponse(Call<Movie> call, Response<Movie> response) {
if(response.code() != 200) return;
mMoviesDetails.put(movieId, response.body());
notifyDataSetChanged();
}
Then in your adapter you can change the onBindViewHolder to something like the below :
#Override
public void onBindViewHolder(MoviesViewHolder holder, int position) {
holder.movieGenreTextView.setText("");
Movie movie = mMoviesDetails.get(mMovies.get(position).getId());
if(movie != null){
/// set data to holder
}else {
//load data from network
loadGenres(mMovies.get(position).getId());
}
}
This is just sample code based on your current implementation, generally speaking I would not load this data inside an Adapter but delegate this type of tasks to a dedicated API class that can store the data in a database like Realm for example instead of using a map.
I wish to delete an object from Firebase on Swipe left. Everything works fine now with the swipe and it it removed from the view, but it stays in the database.
I've added the following to my onCreate:
ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {
Toast.makeText(ListBox.this, "Item removed", Toast.LENGTH_SHORT).show();
//Remove swiped item from list and notify the RecyclerView
}
};
and this is how i populate my ViewHolder.
#Override
protected void populateViewHolder(final BoxViewHolder viewHolder, final Box model, int position) {
viewHolder.setTitle(model.getTitle());
final String boxUniqueKey = model.getBoxkey();
final DatabaseReference postRef = getRef(position);
final String postKey = postRef.getKey();
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Launch BoxItem view
final Intent intent = new Intent(ListBox.this, AddBoxItem.class);
String boxkey = model.getBoxkey();
String boxName = model.getTitle();
startActivity(intent);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);
itemTouchHelper.attachToRecyclerView(allBoxes);
}
});
}
but how (and where) do I get the position of the item, and how can I send the remove query to the Firebase Database?
I have coded something very similar to what you are trying to achieve. This is one way you could achieve it.
First, extend the ItemTouchHelper.SimpleCallback class to make your own custom class.
public class SwipeToDeleteCallback extends ItemTouchHelper.SimpleCallback {
private RecyclerAdapter adapter; // this will be your recycler adapter
private DatabaseReference root = FirebaseDatabase.getInstance().getReference();
/**
*
Make sure you pass in your RecyclerAdapter to this class
*/
public CallBack(int dragDirs, int swipeDirs, RecyclerAdapter adapter) {
super(dragDirs, swipeDirs);
this.adapter = adapter;
}
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition(); // this is how you can get the position
Object object = adapter.getObject(position); // You will have your own class ofcourse.
// then you can delete the object
root.child("Object").child(object.getId()).setValue(null);// setting the value to null will just delete it from the database.
}
Calling viewHolder.getAdapterPosition() returns the position of the view in the adapter. You can use this position to get the Object from the ArrayList contained in your recycler adapter.
In my adapter, I have created a getObject method. This just returns the object from the ArrayList that my adapter has. Once I have the object, I can call the associated Firebase Realtime Database method and delete the object. In my Object class, I have stored the unique key within the object so I can easily delete it. I get the unique id by calling getId(). I pass this to the associated Firebase Realtime Database method and set the value to null which deletes it.
After doing this you can add it to your recycler view like this.
ItemTouchHelper.SimpleCallback swipeToDeleteCallback = new
SwipeToDeleteCallback(0, ItemTouchHelper.RIGHT, choreRecyclerAdapter, getContext()); // Making the SimpleCallback
ItemTouchHelper touchHelper = new ItemTouchHelper(swipeToDeleteCallback);
touchHelper.attachToRecyclerView(recyclerView); // then attach it to your recycler view
First, you make a simple callback and make sure you instantiate the custom class that you extended. Be sure to pass your recycler adapter.
Notice I only support right swipe by passing ItemTouchHelper.Right. You can support left or pass in both left and right.
Then create an ItemTouchHelper object and pass it your simple callback.
Lastly, you attach your touch helper to your recycler view and that's all.