When I search for a user in my database, it populates the RecyclerView, but when I make another search, and it can't find the user, I want it to also remove the RecyclerView content, but the old users content stays in the RecyclerView until I search for an existing user, then it changes to the new users content.
#OnClick(R.id.btn_search_user)
public void onSearchBtnClicked(View view) {
Log.e(LOG_TAG, "Search button clicked");
if (!TextUtils.isEmpty(etSearchUser.getText())) {
userNameSearch = etSearchUser.getText().toString();
if (!isNetworkAvailable()) {
Toast.makeText(this, "No internet, searching offline", Toast.LENGTH_SHORT).show();
mOwner = Owner.getByUsername(userNameSearch);
if(mOwner != null) {
searchUserByUserName(userNameSearch);
} else {
// I want the RecyclerView to be empty at this point.
tvGitHubUser.setText("was not found offline");
}
} else {
loadRepository(userNameSearch);
Log.e(LOG_TAG, "Owner is empty, searching online");
}
}
}
initRecycleView()
mGithubRecyclerView = (RecyclerView) findViewById(R.id.github_repository_recyclerview);
RecyclerView.LayoutManager mGithubLayoutManager = new LinearLayoutManager(getApplicationContext());
mGithubRecyclerView.setLayoutManager(mGithubLayoutManager);
mGithubRecyclerView.setHasFixedSize(true);
Simply, call recyclerView.setAdapter(null);
If you can show me your adapter code, I can provide better method.
Related
Hi I am developing a chat application like whatsapp. I have to load the chat history from the api when user scrolls down like in whatsapp. I get the data and set it to the adapter. But the history is loading at the bottom of recycler view. I need to add it on top for every scroll. This is my code. Please help me. Thanks in advance.
if (Status.equals("1")) {
historyList = resp.getHistory();
Log.i("history size",String.valueOf(resp.getHistory().size()));
String historysize = String.valueOf(resp.getHistory().size());
Message message = new Message();
for (int i = 0; i < resp.getHistory().size(); i++) {
String fromusertoken = String.valueOf(resp.getHistory().get(i).getFromUserToken());
String txtmsg = resp.getHistory().get(i).getMessage();
String username = String.valueOf(resp.getHistory().get(i).getFromUserName());
if (fromusertoken.equals(user_token)) {
Message messages = new Message();
messages.setUser_token(fromusertoken);
messages.setUsername(username);
messages.setIsMine(true);
messages.setMessage(txtmsg);
messageAdapter.add(messages);
}
else {
Message messages = new Message();
messages.setUser_token(fromusertoken);
messages.setUsername(username);
messages.setIsMine(false);
messages.setMessage(txtmsg);
messageAdapter.add(messages);
}
}
messageAdapter.notifyDataSetChanged();
onItemsLoadComplete();
Toast.makeText(Single_chat.this, msg, Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(Single_chat.this, msg, Toast.LENGTH_SHORT).show();
}
}
} else {
Toast.makeText(Single_chat.this, "No Response", Toast.LENGTH_SHORT).show();
}
}
There is a way to set the item on top by editing the adapter class not the activity. We have to add the item list like messagelist.add(0,message). Now only i found. For your regular chat you have to add as normal like messagelist.add(message). This will add the item at bottom. Hope this will be useful for others.
public void add(Message message) {
messageList.add(message);
notifyItemInserted(messageList.size() - 1);
}
public void add(int i,Message message) {
messageList.add(0,message);
notifyItemInserted(messageList.size() - 1);
}
I changed the list in adapter as messagelist.add(0,message). It worked.
public void add(Message message) {
messageList.add(0,message);
notifyItemInserted(messageList.size() - 1);
}
you can insert the history message at the index 0
messageAdapter.add(0,messages);
every iteration it will replace the first message by the last inserted message.
chatHistoyList.add(adapter.getCurrentChatList());
adapter.setData(chatHistoryList);
adapter.notifyDataSetChanged();
//This method has to be in adapter
public List<Chat> getCurrentChatList() {
return currentChatList;
}
I am new in android development, after make a network request trying to update item view in the RecyclerView control without scrolling it. As far as I understand items gets refreshed during scroll via onBindViewHolder event.here is the code. Using notifyItemChanged method to update UI but it doesn't work until user scroll.
Note : favoriteMatches is not my data source. It is another list of objects which stores user favorites,
inside onBindViewHolder event I am cheking if item is favoriteMatches.contains(match) then render as fav item.
call.enqueue(new Callback<AddRemoveFavoriteRequest.Response>() {
#Override
public void onResponse(Call<AddRemoveFavoriteRequest.Response> call, Response<AddRemoveFavoriteRequest.Response> response) {
AddRemoveFavoriteRequest.Response body = response.body();
Utilities.dismissProgressDialog(getActivity(),progressBar);
if(body.error == null){
if(add){
favoriteMatches.add(matchId);
}
else {
favoriteMatches.remove(matchId);
}
adapter.notifyItemChanged(absolutePosition);
Preferences.getDefaultPreferences().edit()
.putStringSet(Preferences.PREF_FAVORITES,favoriteMatches)
.apply();
}else{
Utilities.showSnackBar(getActivity(),recyclerView,body.error);
}
}
#Override
public void onFailure(Call<AddRemoveFavoriteRequest.Response> call, Throwable t) {
t.printStackTrace();
Utilities.dismissProgressDialog(getActivity(),progressBar);
}
});
notifyItemChanged takes the position at which the item changed.
You are adding and removing elements, so that position is going to move around. Instead, you can individually notify at positions.
int changedPosition = 0;
if(add){
changedPosition = favoritesMatches.size();
favoriteMatches.add(matchId);
adapter.notifyItemInserted(changedPosition);
}
else {
changedPosition = favoriteMatches.indexOf(matchId); // might not work
favoriteMatches.remove(matchId);
adapter.notifyItemRemoved(changedPosition);
}
Or, instead update the entire list
adapter.notifyDataSetChanged();
Read more about notifying the adapter, but note
a RecyclerView adapter should not rely on notifyDataSetChanged() since the more granular actions should be used.
I have a REST Service that paginate my requests... I need to insert on my recyclerview 20 itens and keep the 20 itens before loaded.
This is my request...
VeiculoRequestHelper.veiculosPaginatedRequest(Request.Method.GET, url, null, new Response.Listener<VeiculoPaginated>() {
#Override
public void onResponse(VeiculoPaginated response) {
AnalyticsTracker.getInstance().sendEvent(AnalyticsEnum.Category.MY_VEHICLE, AnalyticsEnum.Action.CLICK, AnalyticsEnum.Label.SUCCESS);
isLast = response.isLast();
ArrayList<Veiculo> veiculoArrayList = new ArrayList<>();
veiculoArrayList.addAll(response.getContent());
veiculos = veiculoArrayList;
mAdapter.addItem(veiculos);
mRecyclerView.setAdapter(mAdapter);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
AnalyticsTracker.getInstance().sendEvent(AnalyticsEnum.Category.MY_VEHICLE, AnalyticsEnum.Action.CLICK, AnalyticsEnum.Label.ERROR);
Toast.makeText(getActivity(), "Erro ao realizar listagem de veĆculos.", Toast.LENGTH_LONG).show();
}
});
This is my adapter method to add new itens...
public void addItem(ArrayList<Veiculo> veiculosArray) {
if (veiculos != null) {
veiculos.clear();
veiculos.addAll(veiculosArray);
notifyItemInserted(veiculos.size() - 1);
} else {
veiculos = veiculosArray;
}
notifyDataSetChanged();
}
What i'm doing wrong? The itens is inserted but not keeping the old itens... Help please!
The old data will be cleared in addItem(ArrayList<Veiculo> veiculosArray):
if (veiculos != null) {
veiculos.clear(); // here they will be cleared
veiculos.addAll(veiculosArray);
notifyItemInserted(veiculos.size() - 1);
}
Remove this line to keep the old items.
in your onResponse:
ArrayList<Veiculo> veiculoArrayList = new ArrayList<>();
veiculoArrayList.addAll(response.getContent());
veiculos = veiculoArrayList;
mAdapter.addItem(veiculos);
mRecyclerView.setAdapter(mAdapter);
see what is happening, veiculoArrayList is being assigned to veiculos means new 20 items are now in it.
passing veiculos into mAdapter.addItem(veiculos);
and in your addItem method
veiculos.clear();
veiculos.addAll(veiculosArray);
means what ever was in veiculos will clear and new list veiculosArray would be added in it (means new 20 items). You just don't need to clear, add new items list and notifyDatasetChanged().
to keep focus on last item, you just need to set recyclerView.smoothScrollToPosition(position);
and obviously position would be veiculos.size() before adding new items into it.
notifyItemRangeInserted(lastListSize, newList.size());
I have a recyclerview which works as expected. I have a button in the layout that fills the list. The button is supposed to make a async call, and on result, I change the button's look. All this happens fine.
But when I click on the button and scroll down the list fast, the async call's result updates the new view's button(the view that is in place of the old one). How do I handle this? Can I have a handle on when a particular view gets reused?
Update :
Code piece of the adapter class that does the async call and the updation of ui.
#Override
public void onBindViewHolder(CommentsViewHolder holder, int position) {
try {
Comments comment = comments.get(position);
holder.bindView(comment,position);
}
catch(Exception ex){ex.printStackTrace();}
}
#Override
public int getItemCount() {
if(comments==null)
{return 0;}
return comments.size();
//return comments.length();
}
public class CommentsViewHolder extends RecyclerView.ViewHolder {
TextView score ;
TextView commentText;
TextView commentTime;
TextView avatarId;
ImageButton minusOne;
ImageButton plusOne;
ParseObject model;
public CommentsViewHolder(View itemView) {
super(itemView);
//itemView.setBackgroundColor(Color.DKGRAY);
minusOne =(ImageButton)itemView.findViewById(R.id.decScore);
plusOne =(ImageButton)itemView.findViewById(R.id.incScore);
commentText = (TextView)itemView.findViewById(R.id.comment);
score = (TextView)itemView.findViewById(R.id.commentScore);
commentTime =(TextView)itemView.findViewById(R.id.commentTime);
avatarId = (TextView)itemView.findViewById(R.id.ivUserAvatar);
}
public void bindView(Comments comment, int position) {
commentText.setText(comment.getCommentText());
score.setText(Integer.toString(comment.getScore()));
String timeText = DateUtils.getRelativeTimeSpanString( comment.getCreatedAt().getTime(), System.currentTimeMillis(), DateUtils.SECOND_IN_MILLIS).toString();
timeText = timeText.replace("hours","hrs");
timeText = timeText.replace("seconds","secs");
timeText = timeText.replace("minutes","mins");
commentTime.setText(timeText);
int commentHandler = comment.getCommenterHandle();
String commenterNumber = "";
if(commentHandler==0)
{
commenterNumber = "OP";
}
else{
commenterNumber = "#"+commentHandler;
}
avatarId.setText( commenterNumber);
model = comment;
String choice = "none";
minusOne.setEnabled(true);
plusOne.setEnabled(true);
minusOne.setVisibility(View.VISIBLE);
plusOne.setVisibility(View.VISIBLE);
for (ParseObject choiceIter : choices) {
if ((choiceIter.getParseObject("comment").getObjectId()).equals(comment.getObjectId())) {
choice = choiceIter.getString("userChoice");
break;
}
}
Log.i("debug",comment.getCommentText()+" "+comment.getScore()+" "+choice);
switch (choice) {
case "plusOne":
Log.i("darkplus","setting darkplus");
plusOne.setImageResource(R.drawable.ic_add_circle_black_18dp);
plusOne.setOnClickListener(reversePlusOneOnClickListener);
//minusOne.setOnClickListener(minusOneOnClickListener);
minusOne.setVisibility(View.GONE);
break;
case "minusOne":
Log.i("darkminus","setting darkminus");
minusOne.setImageResource(R.drawable.ic_remove_circle_black_18dp);
minusOne.setOnClickListener(reverseMinusOneOnClickListener);
//plusOne.setOnClickListener(plusOneOnClickListener);
plusOne.setVisibility(View.GONE);
break;
case "none":
Log.i("darkregular","setting regular");
minusOne.setImageResource(R.drawable.ic_remove_black_18dp);
plusOne.setImageResource(R.drawable.ic_add_black_18dp);
plusOne.setOnClickListener(plusOneOnClickListener);
minusOne.setOnClickListener(minusOneOnClickListener);
break;
}
}
View.OnClickListener reversePlusOneOnClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
if (!FourUtils.isConnected(v.getContext())) {
return;
}
minusOne.setEnabled(false);
plusOne.setEnabled(false);
model.increment("plusOne", -1);
model.increment("score", -1);
model.saveEventually(new SaveCallback() {
#Override
public void done(ParseException e) {
if (e == null) {
ParseQuery<ParseObject> query = ParseQuery.getQuery("CommentChoice");
query.whereEqualTo("user", ParseUser.getCurrentUser());
query.whereEqualTo("comment", model);
query.fromPin(Four.COMMENT_CHOICE);
query.getFirstInBackground(new GetCallback<ParseObject>() {
#Override
public void done(ParseObject parseObject, ParseException e) {
if (e == null) {
if (parseObject == null) {
parseObject = ParseObject.create("CommentChoice");
parseObject.put("comment", model);
parseObject.put("user", ParseUser.getCurrentUser());
}
parseObject.put("userChoice", "none");
parseObject.pinInBackground(Four.COMMENT_CHOICE, new SaveCallback() {
#Override
public void done(ParseException e) {
if (e == null) {
score.setText(Integer.toString(model.getInt("score")));
//votes.setText((model.getInt("minusOne") + model.getInt("plusOne")) + " votes");
minusOne.setVisibility(View.VISIBLE);
plusOne.setImageResource(R.drawable.ic_add_black_18dp);
plusOne.setOnClickListener(plusOneOnClickListener);
minusOne.setEnabled(true);
plusOne.setEnabled(true);
// minusOne.setOnClickListener(minusOneOnClickListener);
BusProvider.getInstance().post(new NewCommentChoicesAdded());
} else {
e.printStackTrace();
}
}
});
}
else{e.printStackTrace();}
}
});
} else {
e.printStackTrace();
Log.i("plus1 error", e.getMessage());
}
}
});
}
};
When the async code is done, you should update the data, not the views. After updating the data, tell the adapter that the data changed. The RecyclerView gets note of this and re-renders your view.
When working with recycling views (ListView or RecyclerView), you cannot know what item a view is representing. In your case, that view gets recycled before the async work is done and is assigned to a different item of your data.
So never modify the view. Always modify the data and notify the adapter. bindView should be the place where you treat these cases.
Chet Haase from Google discusses your exact issue in this DevBytes video.
In short, the framework need to be notified that one of the Views is in "transient" state. Once notified, the framework will not recycle this View until its "transient" flag cleared.
In your case, before you execute the async action, call setHasTransientState(true) on the child View that should change when the async action completes. This View will not be recycled until you explicitly call setHasTransientState(false) on it.
Offtopic:
It looks like you might be manipulating UI elements from background threads. Don't do that! If you can have a reference to Activity then use its runOnUiThread(Runnable action) API instead. If getting a reference to Activity is difficult, you can obtain UI thread's Handler and use its post(Runnable action) API.
Without code to look at, this is going to be difficult (if not impossible) for people to provide an exact answer. However, based on this description it sounds as though your async network loading (using an AsyncTask or custom Loader?) is not specifically tied to an element being tracked by your adapter. You'll need to have some way of tying the two together since the child View objects shown by the RecyclerView are re-used to be more efficient. This also means that if a View is being reused and there is an active async operation tied to it, that async operation will need to be canceled. Otherwise you'll see what you see now: the wrong child View being updated with content from an older async call.
I have this query located in my ParseQueryBuilder object:
public ParseQuery<Event> eventsTypes() {
ParseQuery<Event> query = Event.getQuery();
query.setCachePolicy(ParseQuery.CachePolicy.CACHE_ELSE_NETWORK);
query.setMaxCacheAge(TimeUnit.DAYS.toMillis(1));
query.whereEqualTo(Event.owner, parse.getParseUser());
query.orderByDescending(Event.timesUsed);
return query;
}
I use it to populate a ParseQueryAdapter
and at some point I would like to add an Event and immediately show it:
#OnClick(R.id.add)
public void add(Button button) {
final Event new_type = new Event();
new_type.setOwner(parse.getParseUser());
new_type.setName("atest");
new_type.saveEventually(new SaveCallback() {
#Override
public void done(ParseException e) {
if (e == null) {
// on successfull save, clear cache
parseQueryBuilder.eventsTypes().clearCachedResult();
// and show newly added object
mAdapter.loadObjects();
Toast.makeText(getActivity(), new_type.getName(), Toast.LENGTH_SHORT).show();
}
}
});
}
I expected clearing the cache would result in a new network query, revealing the newly added item but no matter what I try, it seems it will only show the initially cached result.
Even if I try to restart my app, it shows the result from the first cache.