Failed to show specific data from firebase in a RecyclerView - java

I managed to retrieve the data from the firebase and use a recylcerview and a holder to display them. I only need to retrieve data from certain nodes from the firebase. Nodes that are not taken by id but by an inside name:
The problem is that it only displays my data for certain nodes, but it displays them in the order in which they are found in the database. This leaves empty rows in the recyclerview. (for example if the second node is the one you are looking for, put an empty card then the one with data):
The function by which I take the data from the firebase depending on the name is:
private void LoadFeedbackMuzee(String denumire) {
options = new FirebaseRecyclerOptions.Builder<Parere>().setQuery(refM, Parere.class).build();
adapterFM = new FirebaseRecyclerAdapter<Parere, MyViewHolderRecenziiM>(options) {
#Override
protected void onBindViewHolder(#NonNull MyViewHolderRecenziiM holder, int position, #NonNull Parere model) {
if (model.getDenumire().equals(denumire)) {
String userId = model.getIdUser();
int photo = model.getLevel();
holder.parere.setText(model.getIntrb1());
userRef.child(userId).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
if (snapshot.hasChild("fullname")) {
String nume = snapshot.child("fullname").getValue().toString();
holder.username.setText(nume);
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
if (photo == 1) {
holder.image.setImageResource(R.drawable.terrible);
} else if (photo == 2) {
holder.image.setImageResource(R.drawable.bad);
} else if (photo == 3) {
holder.image.setImageResource(R.drawable.okay);
} else if (photo == 4) {
holder.image.setImageResource(R.drawable.good);
} else if (photo == 5) {
holder.image.setImageResource(R.drawable.great);
}
}
}
#NonNull
#Override
public MyViewHolderRecenziiM onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.parere_layout, parent, false);
return new MyViewHolderRecenziiM(view);
}
};
adapterFM.startListening();
recyclerViewR.setAdapter(adapterFM);
}

This is happening because you are doing these things in the onBindViewHolder. So in this case what happens is, if your condition matches then the data is binded otherwise the item in the recycler is left empty means the item layout is just inflated and if you know the working of the adapter then you must be knowing that the size of the list is used to inflate the number of items (parere_layout) in recycler view. So the adapter inflates the layout but when it comes to binding (in onBindViewHolder) the data like userName and photo then if your defined condition matches then it binds otherwise the fields are left empty. Hope now you understand the working of the adapter.
So the solution is:
(1) that either you make itemView Visible for only your condition and make its visibility gone for unwanted condition. Sample code below
holder.itemView.setVisibility(View.GONE);// to make it's visibility gone
holder.itemView.setVisibility(View.VISIBLE);// to make it visible when condition matches.
(2) Recommended Solution: The second solution that I can see is that, if you filter your data before passing it to the adapter then your problem will be solved. By filtering, I mean that after fetching data from the database you just make another list in which you add only that data what you want to be shown, so in this way, your adapter code will be more cleaner and also your problem will be solved simultaneously.
Feel free to ask if something is unclear. And kindly mark this as the correct answer if it helps you so that in the future this answer can also help any other needy.

I THINK you have to do something to your views if data doesn't exist at a node but is still being looped through. So you would do something like this where you would make the views GONE if the denumire doesn't .equal(model.getDenumire) and VISIBLE if it does. And do the same if the snapshot.hasChild(fullname) doesn't have the name so like:
if (model.getDenumire().equals(denmuire) {
GETDATA()
if (snapshot.hasChild(fullname) {
holder.image.setVisibility(View.VISIBLE);
holder.name..setVisibility(View.VISIBLE);
holder.name.setName(Name)
if (photo == 1) {
holder.image.setImageResource(R.drawable.terrible);
} else if (photo == 2) {
holder.image.setImageResource(R.drawable.bad);
} else if (photo == 3) {
holder.image.setImageResource(R.drawable.okay);
} else if (photo == 4) {
holder.image.setImageResource(R.drawable.good);
} else if (photo == 5) {
holder.image.setImageResource(R.drawable.great);
}
}else{
holder.image.setVisibility(View.GONE);
holder.name.setVisibility(View.GONE);
}
} else{
holder.image.setVisibility(View.GONE);
holder.name.setVisibility(View.GONE);
etc...
}

Related

How to change background colour to specific viewholder items in a RecycleView?

I am trying to change background color in specific item(s) in a RecycleView.
Because I need to set text too, I have the following code that works fine:
protected void populateViewHolder(RankingViewHolder viewHolder, final Ranking model, int position)
{
final Context mContext = getActivity().getApplicationContext();
viewHolder.txt_name.setText(model.getUserName());
viewHolder.txt_score.setText(String.valueOf(model.getScore()));
viewHolder.txt_class.setText(model.getUser_class());
Picasso.with(mContext).load(model.getAvatarUrl()).error(R.drawable.ic_people_black_24dp).into(viewHolder.personPhoto);
int totalRanking = adapter.getItemCount();
int realRank = totalRanking - viewHolder.getAdapterPosition();
viewHolder.ranknumber.setText("# "+String.valueOf(realRank));
}
This works as I want and realRanktakes the correct values, and the viewHolder.ranknumber.setText("# "+String.valueOf(realRank));
Sets the right text with no problem.
Now I am trying (as I got a number/text result correct, to make an if statement like this:
if(adapter.getItemCount() -viewHolder.getAdapterPosition() == 0)
{
viewHolder.itemView.setBackgroundColor(Color.GREEN);
}
if(adapter.getItemCount() -viewHolder.getAdapterPosition() == 1)
{
viewHolder.itemView.setBackgroundColor(Color.YELLOW);
}
if(adapter.getItemCount() -viewHolder.getAdapterPosition() == 2)
{
viewHolder.itemView.setBackgroundColor(Color.BLUE);
}
(I tried with String.valueOf(realRank)equality, with realRankequality too)
In all cases I have the same result. The color changes as its should at positions 1,2,3 BUT it changes at positions 7,8,9 and 14,15,16 and 21,22,23 etc.
What am I missing here?
public class RankingViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener
{
private ItemClickListener itemClickListener;
public TextView txt_name, txt_score, txt_class, ranknumber;
public ImageView personPhoto;
public RankingViewHolder(View itemView)
{
super(itemView);
txt_name = itemView.findViewById(R.id.txt_name);
txt_score = itemView.findViewById(R.id.txt_score);
personPhoto = itemView.findViewById(R.id.person_photo);
txt_class = itemView.findViewById(R.id.txt_class);
ranknumber = itemView.findViewById(R.id.ranknumber);
itemView.setOnClickListener(this);
}
public void setItemClickListener(ItemClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
}
#Override
public void onClick(View view) {
itemClickListener.onClick(view , getAdapterPosition(),false);
}
}
The adapter:
adapter = new FirebaseRecyclerAdapter<Ranking, RankingViewHolder>(
Ranking.class,
R.layout.layout_ranking,
RankingViewHolder.class,
rankingTbl.orderByChild("score").limitToFirst(100)
)
This line of code int realRank = totalRanking - viewHolder.getAdapterPosition();gives a number (1,2,3,4,5,6 etc.) Why i cannot use this number to check equality?
Notice
Keeping this code for NOT working solution, just for future reference:
if(position == 0){
viewHolder.itemView.setBackgroundColor(Color.GREEN);
}
else if(position == 1){
viewHolder.itemView.setBackgroundColor(Color.YELLOW);
}
else if(position == 2){
viewHolder.itemView.setBackgroundColor(Color.BLUE);
}
else{
viewHolder.itemView.setBackgroundColor(Color.WHITE);
}
This changes the color BUT not for only 3 first items. As you scroll down, changes the color for every 3 first viewable items like before, meaning 1,2,3, 7,8,9, etc.
Update:
I dont use a custom adapter, i use FirebaseRecyclerAdapter.
Thanks to #Muhammad Haroon comment i checked that has getItemViewType. So now i m trying with it like
position =adapter.getItemViewType( 0);
if(position == 0){
viewHolder.itemView.setBackgroundColor(Color.GREEN);
}
Not working for now, but i think its the correct direction...
Update 2
With position its not possible as RecycleView recycles the views so i have the same result. The working code is
if (linearLayoutManager.findFirstVisibleItemPosition() > 0) {
viewHolder.itemView.setBackgroundResource(R.drawable.blackframe);
}
else{
viewHolder.itemView.setBackgroundResource(R.drawable.goldframe);
}
Works fine except that after scrolling loosing the change of background.
So as we want and need the perfection, any idea for keeping even after scroll?
hi try add this in your Adapater it may solve your problem.
#Override
public int getItemViewType(int position) {
return position;
}
Please give this a try
override in your custom adapter
#Override
public long getItemId(int position) {
return position;
}
and in in your adapter object:
myAdapter.setHasStableIds(true);
In populateViewHolder add these line of code
if(position == 0){
viewHolder.itemView.setBackgroundColor(Color.GREEN);
}
else if(position == 1){
viewHolder.itemView.setBackgroundColor(Color.YELLOW);
}
else if(position == 2){
viewHolder.itemView.setBackgroundColor(Color.BLUE);
}
else{
viewHolder.itemView.setBackgroundColor(Color.WHITE);
}
position is a parameter in populateViewHolder.

Android Firebase RecyclerView Adapter Remove Views

I am facing a Firebase RecyclerView problem where I cannot remove unwanted CardViews from my RecyclerViews. In my code I check the city's name and the guide's chosen city to match them. It populates guide's details only if the guide's city matches the picked city, but it also shows empty cardview with default layout.
guideDataRef = FirebaseDatabase.getInstance().getReference().child("Guides");
public void recycler() {
super.onStart();
try {
//Guide RecyclerView
Query guideQuery = guideDataRef.orderByKey();
guideQuery.keepSynced(true);
FirebaseRecyclerOptions guideOptions =
new FirebaseRecyclerOptions.Builder<UserModelClass>().setQuery(guideQuery, UserModelClass.class).build();
guideAdapter = new FirebaseRecyclerAdapter<UserModelClass, guideViewHolder>(guideOptions) {
#Override
protected void onBindViewHolder(#NonNull guideViewHolder holder, final int position, #NonNull final UserModelClass model) {
String pickedcity = model.getPickedCity();
String postname = (String) cityName.getText();
if(pickedcity.equals(postname)) {
final String guide_key= getRef(position).getKey();
holder.setGuideName(model.getName());
holder.setGuideSurname(model.getSurName());
holder.setGuideImage(getApplicationContext(), model.getPhotoURL());
// holder.mView.setVisibility(View.VISIBLE);
//Guide Click listener
holder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent guideHireIntent = new Intent(getApplication(), GuideHireActivity.class);
guideHireIntent.putExtra("guide_id", guide_key);
finish();
startActivity(guideHireIntent);
}
});
}
}
#NonNull
#Override
public guideViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout_guides, parent, false);
return new guideViewHolder(view);
}
#Override
public void onError(DatabaseError e){
Toast.makeText(getApplicationContext(), "Error by stopping ", Toast.LENGTH_SHORT).show();
}
#Override
public int getItemCount() {
return super.getItemCount();
}
#Override
public void onDataChanged() {
super.onDataChanged();
notifyDataSetChanged();
}
};
guideAdapter.notifyDataSetChanged();
guideRecyclerView.setAdapter(guideAdapter);
guideAdapter.startListening();
} catch (DatabaseException e) {
Toast.makeText(this, "Error", Toast.LENGTH_SHORT).show();
}
}
enter image description here
enter image description here
I can change the adapter visibility to gone if it does not match with the requirements but the problem is that after making it's visibility gone it is still there holding the place (but invisible - there's still an empty space). How can I avoid populating an item from the recycler view completely, instead of making it invisible if the requirements do not match?
You're not showing what guideDataRef is in your code, so I'm assuming that it's just aDatabaseReference object for everything beneath a \Guides node.
If you're doing that, you're going to get a call for onBindViewHolder for every child at that particular location. This means that you're going to be asked to make a view for every child. You cannot choose whether or not a view will appear for that item.
It looks like you're assuming that your if statement in onBindViewHolder method will skip over those items. But what's actually happening is that you're simply allowing an empty view to occupy that spot in the list.
Instead, you should come up with a query that generates only the items of interest to your list. This means you'll have to tell Firebase to filter for children that meet your criteria.
You can also read the entire contents of the location, manually filter out the items you don't want, and build a list of items you do want. You can then build an custom adapter with that list, and it can then become the input to a ListView or even better to a RecyclerView.

Why does tha last row of this listview always act strange?

I want to display a ListView where each row represents the answer to a question. When the user presses the button CHECK I want the rows of the ListView to change color according to the result (if the answer was wrong, correct, not selected and so on...).
In my onCreateView() I'm creating the following CursorAdapter:
Cursor crs = db.queryQuestionAnswers(questionID); // a cursor with all answers relative to the question
adapter = new CursorAdapter(getActivity(), crs, 0) {
#Override
public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
return ((LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE))
.inflate(R.layout.listfragment_test_row, listview, false); // Row of the listview
}
// Instance items of each row
#Override
public void bindView(View view, Context context, Cursor crs) {
Answer answer = test.getQuestion(current_question).getAnswer(crs.getPosition());
TextView answerTv = (TextView) view.findViewById(R.id.ans_test_button);
answerTv.setText(answer.getText());
view.setOnClickListener(new AnswerOnClickListener(answer));
if (show_solution) { // User has clicked "CHECK"
displayResult(view, answer); // Color answers according to selection and correctness
} else
view.setBackgroundColor(Color.TRANSPARENT); // The question has just been displayed
}
#Override
public long getItemId(int position) {
Cursor cursorAdapter = adapter.getCursor();
cursorAdapter.moveToPosition(position);
return cursorAdapter.getLong(cursorAdapter.getColumnIndex(DatabaseStrings.FIELD_ID));
}
};
Whenever the user presses the CHECK button so (bringing show_solution to true), the app should check the following fields and act on the relative view accordingly:
private void displayResult(View view, Answer answer) {
view.setEnabled(false);
if (answer.isSolution() && answer.isChoice()) // User selected a correct answer
view.setBackgroundColor(ContextCompat.getColor(getActivity(), R.color.colorLightGreen));
else if (answer.isChoice()) // User selected a wrong answer
view.setBackgroundColor(ContextCompat.getColor(getActivity(), R.color.colorMediumRed));
else if (answer.isSolution()) // User didn't select a solution
view.setBackgroundColor(ContextCompat.getColor(getActivity(), R.color.colorLightBlue));
else { // Answer was nor selected nor solution
// [...] Hide view with an animation [..]
}
}
}
But in all cases the last answer gets colored (apparently with a random color) and then vanishes (as it was nor selected nor solution).
It looks like for only the last row the program enters in all if statements.
The only case when it works as it was supposed to, is when the user selects all answers: correct ones are colored green and wrong one red... while no answer vanishes. Not even the last one.
I managed to fix the problem relative to the color adding the following line inside the last else statement.
view.setBackgroundColor(Color.TRANSPARENT);
But the last line keep vanishing. What's wrong?
EDIT:
Here's the AnswerOnClickListener implementation:
// Listener for answer selection
public class AnswerOnClickListener implements View.OnClickListener {
Answer answer;
private AnswerOnClickListener(Answer answer) { this.answer = answer; }
#Override
public void onClick(View v) {
answer.changeChoice();
if (answer.isChoice()) { // Answer has been selected by the user
v.setBackgroundColor(ContextCompat.getColor(getActivity(), R.color.colorLightYellow));
} else { // Answer has been deselected by the user
v.setBackgroundColor(Color.TRANSPARENT);
}
}
}

Android Listview in Baseadapter how can I save changes to the list

I have been reading different posts on here about baseadapters and trying to learn so that I can fix my issue but I haven't been able to resolve it. On my BaseAdapter I have a String called post that is used in a column in the listview. If the post is longer than 13 characters then it is shortened automatically when the user Clicks on the shortened post then it displays it's full length,however the issue is that once you scroll down the listview and come back up to that same post it's still shortened even though the user clicked before to show the full post. I think this is an issue of the Listview or Baseadapter recycling or cache mechanism is there anyway I can fix this? This image will clear things up .. This post is more than 13 characters so it shows the shortened version
if a user wants to read it in full then they will click on the Read More which will then show all of the content which looks like this
and when the user scrolls down or up that same long post will return to this without the user clicking it again, which I want to avoid
I know that the Listview recycles but how can I update it? This is my code below
public class LocalFeed_CustomView extends BaseAdapter {
JSONObject names;
Context ctx;
Activity m;
// More is the default value
String result="More";
#Override
public int getCount() {
try {
JSONArray jaLocalstreams = names.getJSONArray("localstreams");
return jaLocalstreams.length();
} catch (Exception e) {
Toast.makeText(ctx, "Error: Please try again", Toast.LENGTH_LONG).show();
return names.length();
}
}
#Override
public Object getItem(int position) {
return position;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row=convertView;
MyViewHolder holder=null;
try {
if (row == null) {
LayoutInflater li = (LayoutInflater) m.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = li.inflate(R.layout.customadapter, null);
holder = new MyViewHolder(row);
row.setTag(holder);
} else {
holder = (MyViewHolder) row.getTag();
}
final MyViewHolder finalHolder1=holder;
// Json data has been read
JSONArray jaLocalstreams = names.getJSONArray("localstreams");
final JSONObject jsonObject = jaLocalstreams.getJSONObject(position);
// if post length is more than 14 then shorten it
if (jsonObject.getString("post").length() > 14) {
holder.post.setText(jsonObject.getString("post").substring(0, 13) + "...Read More");
holder.post.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
try {
// if Result is More then show full post
if (result.equals("More")) {
finalHolder1.post.setText(jsonObject.getString("post") + "... Read Less");
result = "Less";
}
else
{
//Result is Less so shorten it again
finalHolder1.post.setText(jsonObject.getString("post").substring(0, 13) + "... Read More");
result = "More";
}
} catch (JSONException e) {
e.printStackTrace();
}
}
});
} else{
// This Post is already less than 14 characters so no Onclick here
holder.post.setText(jsonObject.getString("post"));
}
return row;
} catch (Exception e) {
e.printStackTrace();
}
return row;
}
class MyViewHolder{
TextView post;
MyViewHolder(View v)
{
post = (TextView)v.findViewById(R.id.post);
}
}
}
The adapter represents the model of the list at any given moment in time.
What this means to you is that if a user clicks a TextView to expand it with the idea that the view is going to stay expanded, then that expanded TextView is state information that will have to be captured in the adapter.
Adapters should always be thought of in two phases:
Event (like onClick()) will update state in the adapter and call notifyDataSetChanged().
getView() uses the current state to create the view.
So let's say in the adapter constructor we create an array of flags
boolean expanded[] = new boolean[size];
where size is the length of your list.
Then you can do this:
// use the current state to create the view
String text;
if (expanded[position]) {
text = jsonObject.getString("post") + "... Read Less";
} else {
text = jsonObject.getString("post").substring(0, 13) + "...Read More";
}
holder.post.setText(text);
holder.post.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// update the current state and request list refresh
expanded[position] = ! expanded[position]; // toggle
notifyDataSetChanged();
}
});
This code doesn't do exactly what yours does, I just wanted to give you the basic idea.

how to avoid getView to be called endlessly when invalidating adapter's data?

I want to implement lazy items loading from the server using Android ListView.
For that propose i have added the next code in my Adapter:
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
if (position != 0 && position % 7 == 0)
// ask next page
{
mOffersListActivity.getOffersFromServer(ListType.FULL, 10, 10);
}
This calls the hosting Activity which loads the next bulk from the server:
#SuppressWarnings("unchecked")
private void afterServerOfferResponse(final Gson gson,
String result) {
boolean hadError = false;
Type collectionType = new TypeToken<ArrayList<Offer>>() {
}.getType();
if (mOffersList == null) {
mOffersList = new ArrayList<Offer>();
}
mOffersList.addAll((Collection<? extends Offer>) gson
.fromJson(result, collectionType));
if (mOffersList == null || mOffersList.size() == 0) {
errorMsg.setText("no offers found");
hadError = true;
} else {
if (mAdapter == null) {
mAdapter = new ImageAdapter(
OffersListActivity.this, mOffersList,
listType);
mListView.setAdapter(mAdapter);
}
// mAdapter.notifyDataSetChanged();
mListView.invalidate();
}
The problem is that the getView for position 7, 14 and so on is called endlessly
how can I avoid this?
I have thought to put this is the adpater instead of the %7 code:
if (mOffersList.size() - 3 == position)
// ask next page
{
mOffersListActivity.getOffersFromServer(ListType.FULL, 10, 10);
}
Any other ideas?
Every time you call getOffersFromServer it looks like to reload your adapter, so it will reload th eview as well...
But this approach does no seems correct to me, you will have to maintain an connection open all the time, which is very bad, it is preferable to download all or at least a good chunk of data before showing anything, you will avoid a lot of network overhead and battery waste over constant connection e network calls.

Categories