I have a recyclerview and it includes several rows.
each row has an imageview(heart shape) for like and dislike and what I need is changing the image of imageview each time by my click.
when I start this activity and my list sets, it works well and the image changes by clicking on imageview, but the problem is when I start another activity and get back to this activity, onclick doesn't work.
It seems my adapter doesn't notify data!
This is my code after clicking on imageview:
public static void afterLikeChanged(Post post) {
if (!post.getPostIsLikedByYou()) {
post.setPostIsLikedByYou(true);
} else {
post.setPostIsLikedByYou(false);
}
mAdapter.notifyDataSetChanged();
}
and on the other side in adapter class:
boolean isliked = post.getPostIsLikedByYou();
if (!isliked) {
holder.imglike.setImageResource(R.drawable.btn_like);
} else {
holder.imglike.setImageResource(R.drawable.btn_liked);
}
any idea guys?!
make interface in adapter for like and dislike click event like below code ..
Note : take boolean value for like or like and make getter setter.
onItemClickListner onItemClickListner;
public interface onItemClickListner {
void onClick(User str);//pass your object types.
}
public void setOnItemClickListner(RecyclerViewAdpater.onItemClickListner onItemClickListner) {
this.onItemClickListner = onItemClickListner;
}
#Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
// below code handle click event on recycler view item.
User data=userlist.get(position);
if (!data.isliked()) {
holder.imglike.setImageResource(R.drawable.btn_like);
} else {
holder.imglike.setImageResource(R.drawable.btn_liked);
}
holder.imageHeart.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
onItemClickListner.onClick(data);
}
});
}
then after adapter set into recylcerview call below code..
recyclerViewAdpater.setOnItemClickListner(new RecyclerViewAdpater.onItemClickListner() {
#Override
public void onClick(User str) {
if (str.isLike()){
str.setLike(false);
}
else{
str.setLike(true);
}
recyclerViewAdpater.notifyDataSetChanged();
}
});
You need to update your post list before notify the adapter
Something like this:
public static void afterLikeChanged(int position, Post post) {
if (!post.getPostIsLikedByYou()) {
post.setPostIsLikedByYou(true);
} else {
post.setPostIsLikedByYou(false);
}
// postList is a ArrayList that you passed to your adapter
postList.set(position, post);
mAdapter.notifyDataSetChanged();
}
You can not just call like that. Please try below steps:
Assume your current Activity is A. you will switch to activity named B
Using startActivityForResult(B.class, 0x0) instead of startActivity
Implement this in activity A:
boolean getPostIsLikedByYou = false;// This is the variable you get new value from Activity B
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// Check which request we're responding to
if (requestCode == 0x0) {
// Make sure the request was successful
if (resultCode == RESULT_OK) {
getPostIsLikedByYou = data.getBoolean("getPostIsLikedByYou");
}
}
}
From Activity B, do this when go back:
Intent resultIntent = new Intent();
enter code here
resultIntent.putBoolean("getPostIsLikedByYou", "your_value_here");
setResult(Activity.RESULT_OK, resultIntent);
Hope this help.
It seems like your code is a bit seperated. Something like this will encapsulate the behaviour. All you want is to change the icon and preserve the state of the posts.
It's important to keep track of what you've saved.
Your adapter class. It's missing a lot of code but something like this.
List<Post> posts; // init this in constructor
public void update(List<Post> posts) {
// do some nullchecking, maybe clone the posts. It's sent as a reference now.
this.posts = posts;
notifyDataSetChanged();
}
#Override
public void onBindViewHolder(final MyViewHolder viewHolder, int position) {
final Post post = posts.get(position);
if (post != null) {
if (post.isLiked()) {
holder.imglike.setImageResource(R.drawable.btn_liked);
} else {
holder.imglike.setImageResource(R.drawable.btn_like); // change name of this
}
}
}
public void afterLikeChanged(Post post) {
post.setIsLiked(!post.isLiked());
notifyDataChanged();
}
class MyViewHolder extends RecyclerView.ViewHolder {
// super, etc...
// this is a lazy way to use it, not safe.
// You should create a listener that sends back the getAdapterPosition()
itemView.setOnClickListener(v -> afterLikeChanged(posts.get(getAdapterPosition())));
}
Now make sure that you're hooking up the adapter to the recyclerview correctly. If it works the first time and not the second time, it seems like you are hooking things up in the wrong places. Use onPause and onResume instead.
Finally, I found the solution!
The problem was I had this line in onResume:
mAdapter = new RecyclerViewAdapterQuestions(this, arrayList);
َAfter removing this line the problem was resolved!
It seems in this section my adapter was resetting with new data, not with my previous array!
Thank you for answering me:)
Related
I'm trying to make a simple chat application for my own learning - no firebase involved (the messages won't be stored between sessions). I've implemented a RecyclerView to show all the messages. The problem is that every time I add a new message, the RecyclerView Adapter will iterate through all previous messages before populating the latest one. Whilst this isn't causing any major bugs, it does seem very inefficient. The relevant functions in my adapter class are shown below:
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
MessageItem newMsgItem = messages.get(position);
holder.txtMsgContent.setText(newMsgItem.getMsgContent());
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) holder.msgParentView.getLayoutParams();
if (newMsgItem.isSent()) {
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
holder.msgParentView.setLayoutParams(layoutParams);
holder.msgParentView.setCardBackgroundColor(0xFF03DAC5);
} else {
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
holder.msgParentView.setLayoutParams(layoutParams);
holder.msgParentView.setCardBackgroundColor(0xFF67706F);
}
}
// boolean sent: false = received, true = sent
public void addMessage (boolean sent, String msgContent) {
messages.add(new MessageItem(sent, msgContent));
notifyDataSetChanged();
}
I could implement a condition-check like below, but that isn't a satisfying solution as it only masks the problem - i.e. the program is still iterating unnecessarily through all previous messages:
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
if (position == messages.size() - 1) {
//... do function
}
}
Is there a way to make the program only call onBindViewHolder for the newest item that's been added? I also saw this forum, but as I'm a beginner I couldn't tell if they were having the same issue as me.
RecyclerView populating each item every time i add a new item
notifyDataSetChanged() always reloads the whole view. Use notifyItemInserted() instead.
public void addMessage (boolean sent, String msgContent) {
messages.add(new MessageItem(sent, msgContent));
notifyItemInserted(messages.size()-1);
}
Don't use notifyDataSetChanged() method, you can use notifyItemInserted() method, this will not refresh every time.
public void addMessage (boolean sent, String msgContent) {
messages.add(new MessageItem(sent, msgContent));
notifyItemInserted(messages.size()-1);}
I'm writing a todo app to learn more about android architecture.
I implemented RecyclerView with adapter , which receives data from ViewModel. Now i trying to implement swipe-to-delete with "undo" button in Snackbar.
All works fine, until I'm trying to delete 2 items from RecyclerView at the same time. Only one item is deleted, second appears again. Problem only exists while snackbar not dismissed. I use
Snackbar.Callback.DISMISS_EVENT_ACTION
to handle when user push cancel on snackbar
class FolderFragment
...
adapter = new FolderListAdapter(getContext(), folderViewModel);
folderViewModel.getFolders().observe(this, adapter::setFolders);
...
public void onSwipe() {
Snackbar.make(getView(),
R.string.folder_removed_message, Snackbar.LENGTH_SHORT)
.setAction(R.string.undo, v ->
adapter.undoDelete())
.addCallback(new Snackbar.Callback() {
#Override
public void onDismissed(Snackbar transientBottomBar, int event) {
if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {
folderViewModel.delete(adapter.getDeletedFolder());
}
}
})
.show();
public class FolderListAdapter extends RecyclerView.Adapter<FolderListAdapter.FolderViewHolder>
...
void setFolders(List<Folder> folders) {
this.folders = folders;
notifyDataSetChanged();
}
public void onItemDismiss(int position) {
mDeletedPosition = position;
mDeletedFolder = folders.get(position);
folders.remove(position);
notifyItemRemoved(position);
}
public void undoDelete() {
folders.add(mDeletedPosition, mDeletedFolder);
notifyItemInserted(mDeletedPosition);
}
...
public class FolderViewModel extends AndroidViewModel
...
public void delete(Folder folder) {
folderRepository.delete(folder);
}
...
See my RecyclerView adapter behavior on link below
behavior
trying to comment lines in adapter which deletes item from list in adapter -not work
logging setList() in adapter - viewmodel not updates LiveData because it work in background, but i dont know how to solve this
project on github
Usually on a RecyclerView I show an empty view when there are no items on the RecyclerView and since I control all updates to the RecyclerView via the notify methods then that is pretty simple but with PagedListAdapter updates just seem to happen on the background, so how can I hide or show my empty view?
For example, if I call deleteItem() on my Room DB, the PagedListAdapter will be updated on its own without me calling notifyItemDeleted but if it was the last item on the list, how does my code know to show the empty view? I could query the DB each time an action happens for the count but that seems wasteful. Is there a better way?
As mentioned in the comment, you can test emptiness of the list in the same LiveData observer you use for .submitList().
Java Example:
I am assuming you are following something similar to this snippet found in the PagedListAdapter document. I am simply adding emptiness check to that sample code.
class MyActivity extends AppCompatActivity {
#Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
RecyclerView recyclerView = findViewById(R.id.user_list);
UserAdapter<User> adapter = new UserAdapter();
viewModel.usersList.observe(this, pagedList -> {
// Check if the list is empty
updateView(pagedList.size());
adapter.submitList(pagedList));
pagedList.addWeakCallback(null, new PagedList.Callback() {
#Override
public void onChanged(int position, int count) {
updateView(pagedList.size())
// updateView(adapter.getItemCount())
}
...
}
}
recyclerView.setAdapter(adapter);
}
private void updateView(int itemCount) {
if (itemCount > 0) {
// The list is not empty. Show the recycler view.
recyclerView.setVisibility(View.VISIBLE);
emptyView.setVisibility(View.GONE);
} else {
// The list is empty. Show the empty list view
recyclerView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
}
}
}
Kotlin Example:
The above Java example is actually just a Java translation of Kotlin example I found in this Android Paging codelab.
It's not the best solution, but you can give it a try
myViewModel.getMyPagedList().observe(MainActivity.this, items -> {
myPagedListAdapter.submitList(items);
Handler handler = new Handler();
Runnable runnable = new Runnable() {
#Override
public void run() {
if (myPagedListAdapter.getItemCount() == 0) {
runOnUiThread(() -> {
emptyTextView.setText("Empty");
emptyTextView.setVisibility(View.VISIBLE);
});
// The Runnable will be re-executed (repeated) as long as if condition is true
handler.postDelayed(this, 100);
} else
emptyTextView.setVisibility(View.GONE);
}
};
// trigger first time
handler.postDelayed(runnable, 1000);
});
This code in onclick of the Button in Activity:
if(v.getId()==recomendationSelectAllFriends.getId()){
recomendationAdapter.selectAll(resultEntities.size());
}
This method in Adapter:
public void selectAll(int size){
// what should be written here?
}
public void selectAll(int size){
// what should be written here?
for(DataEntity e: data) { //your list of data in the array
e.isChecked(true)
}
notifyDataSetChanged();
}
where e.isChecked field is bounded to the checkbox of the card.
Add a boolean variable in your model class and name it checked, write getters and setters for that variable.
Now,
In your onBindViewHolder write
if(resultEntities.get(position).isChecked(){
holder.yourCheckBoxName.setChecked(true);
}else{
holder.yourCheckBoxName.setChecked(false);
}
In your function
public void selectAll(){
for(ResultEntity e: resultEntities) {
e.setChecked(true)
}
notifyDataSetChanged();
}
I have a list that gets loaded from the server. Below is the task that does this:
class LoadActivities extends AsyncTask <String, String, String> {
protected String doInBackground(String ... args) {
final RestAdapter restAdapter = new RestAdapter.Builder().setServer("http://10.0.2.2:8080").build();
final MyService apiManager = restAdapter.create(MyService.class);
final Activity activity = apiManager.getActivity("some user", act_id);
//tasks in activity
for (Tasks t : activity.getTasks()) {
String r_id = t.getId()+"";
String name = t.getName();
HashMap<String, String> map = new HashMap<String, String>();
map.put("activity_id", act_id);
map.put("t_id", t_id);
map.put("t_name", name);
tasksList.add(map);
}
return null;
}
protected void onPostExecute(String file_url) {
runOnUiThread(new Runnable() {
public void run() {
ListAdapter adapter = new SimpleAdapter(
TaskActivity.this, tasksList,
R.layout.list_item_rec, new String[] { "act_id", "t_id", "t_name"}, new int[] {
R.id.act_id, R.id.task_id,R.id.task_name });
setListAdapter(adapter);
}
});
}
}
All of this is working fine. However, on another screen I am adding an item on the server and after that I come back to this screen to show the list again. At the time of coming back I want to refresh the list so that it reflects the newly added item.
Questions
Should I refresh the entire list? I have tried doing this by calling the above class again. like so:
public boolean onOptionsItemSelected(MenuItem menuItem) {
if (menuItem.getTitle().toString().equalsIgnoreCase("save")) {
new CreateTask(this,activityName.getText().toString(), actId).execute();
Intent returnIntent = new Intent();
setResult(RESULT_OK, returnIntent);
finish();
return true;
}
return true;
}
...back on this screen
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1) {
if (resultCode == RESULT_OK) {
Log.d("This is result", result);
new LoadActivities().execute();
}
}
}
problem with this is that It is repopulating the list. Meaning I have duplicates of every activity. How can I resolve this?
OR Is there a way so that I won't have to reload the entire list but rather just add an item(s) to the existing list?
First,in the method "onPostExecute", you don't need to call "runOnUiThread", because the "onPostExecute" was run in UI thread.
Second, if you want to refresh the ListView in front of the page, you can use "onActivityResult" in the front page, but if your server data was updated, just get data from server again and update your data set(list), then call adapter.notifyDataSetChanged().
Wish to help you!
You should us and ArrayAdapter and let it handle the list.
Create and set the ArrayAdapter right away, then add items to it as necessary. You'll have to override getView in the adapter, but for a simple view that won't be complex code.
The general structure will look like:
onCreate(...) {
// It's okay if the adapter is empty when you attach it to the ListView
setListAdapter(new ArrayAdapter<ListItemType>(...));
}
onPostExecute(...) {
// Once you've retrieved the list of items from the server, add them to
// the adapter
ArrayAdapter adapter = (ArrayAdapter) getListAdapter();
adapter.add([items retrieved from server]);
}
onActivityResult(..., Intent data) {
// Add the newly added item, either pass it back directly, or get the new
// list from the server and compare to see which item needs adding.
// For simplicity, we'll assume it was passed back by the activity
ListItemType newlyAddedItem = (ListItemType) data.getParcelableExtra("key");
ArrayAdapter adapter = (ArrayAdapter) getListAdapter();
adapter.add(newlyAddedItem);
}