In my main activity I have two fragments. One is song list and second is favorite. This both fragment contain ListView and custom adapter. There is a bottom sheet which contain a button . when that button will be clicked that song will be added to favorite(second fragment). I am able to add the song into fragment favorite but it do not show at a time when we add it. we have to restart the application to get it into the favorite. The button to add favorite is operated from main activity . Is there any way that i can notify my adapter that state have been changed.(songs added to favorite use database)
Is there any way i can refresh fragment when it is opened???
This is code of main activity in which favorite is button that adds song name in database when it is clicked.
favourite.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(x==0) {
favourite.setImageResource(R.drawable.ic_favorite);x=1;
boolean isinserted =
mydb.insertdata(collections.get(a).getsong().toString());
if (isinserted == true) {
Toast.makeText(MainActivity.this, "Added as favorite", Toast.LENGTH_LONG).show();
x=1;
}
else
{
Toast.makeText(MainActivity.this, "data not inserted", Toast.LENGTH_LONG).show();
}
}
else{favourite.setImageResource(R.drawable.ic_favorite_border);
int deletedrow = mydb.deleteData(collections.get(a).getsong());
if(deletedrow>0){Toast.makeText(MainActivity.this, "removed from favorite", Toast.LENGTH_LONG).show();
finish();
startActivity(getIntent());
x=0;
}
else {Toast.makeText(MainActivity.this, " not removed from favorite", Toast.LENGTH_LONG).show();
}
}
}
});
you should call notifyDataSetChanged() from your adapter like this:
yourAdapter.notifyDataSetChanged();
update the list with the newly added item and call
adapter.notifyDataSetChanged();
Use this into item click to add into list :
public Both[] boths;
public ArrayList<Both> both = new ArrayList<Both>();
onItemClick:
String itemValue = (String) listview.getItemAtPosition(i);
if (boths == null) {
boths = new Both[]{ \\Both is your Object Class name
new Both(itemValue)
};
}
both.addAll(Arrays.asList(boths));
adapters.notifyDataSetChanged(); \\adapters is your Adapter Name
adapters.setAdapter(both);
As I know, you have two fragments. Here is a solution as follow:
Step 1:
You can define two method to update your adapter in your two fragment!
SongFragment:
public void updateDataList(){
if(songAdapter != null){
songAdapter.notifyDataSetChanged();
}
}
FavoriteFragment:
public void updateDataList(){
if(favoriteAdapter != null){
favoriteAdapter.notifyDataSetChanged();
}
}
Step 2:
When your want to update your views, and your MainActivity must hold the SongFragment And FavoriteFragment Instance.
you can call the method updateDataList in MainActivity. eg:
songFragment.updateDataList();
favoriteFragment.updateDataList();
You also need to update your fragment if you are retrieving data from database,you need to update your cursor or list, you can check this on fragment in this method or you can make listener where you can listen your added fav event.
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
}else{
// fragment is no longer visible
}
}
I solved it if anybody wants it.
I have used broadcast when button gets clicked. Then that broadcast will be recieved in fragment. In reciever i have cleared the list and then again added the list . Now adapter is notified if state have been changed
Related
I have a RecyclerView and adapter. Now in that adapter, I'm inflating one row. In that row, there are one delete button and one progressbar. So what I'm doing is when user clicks on delete button, I make invisible that delete button, and make visible small progress bar in place of delete button from Adapter class. And also I'm sending position via listener to that attached activity, from that I'm calling AsyncTask.
Now the problem is:
When I got to know via AsyncTask that item is deleted, I again want to make visible delete button and to make invisible progressbar. But this time - from Activity (not from adapter), because I want to do something in activity when I get to know that item is deleted. So I can't implement AsyncTask in adapter.
code:
Adapter
delete.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (listener != null) {
delete.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
listener.onClicked(getAdapterPosition(), eventList.get(getAdapterPosition()).getEventId());
}
}
});
Activity (in activity I want to visible/invisible adapter row button and p.bar:
#Override
public void onDeleteDataReceived(Boolean status, int position) {
stopShimmerLayout();
if (status) {
try {
eventsList.remove(position);
mAdapter.notifyItemRemoved(position);
showToast(context, "Deleted", Toast.LENGTH_SHORT);
} catch (Exception e) {
e.printStackTrace();
}
} else {
showToast(context, "Failed", Toast.LENGTH_SHORT);
}
}
See the video for better understanding: https://drive.google.com/open?id=13ZAtnyfGbi2X4JjUTmJsIDy-gt5y51Gr
To fix your problem you can take the below approach.
1) Inside your Eventpojo/model class, declare a boolean isSelectedwhich will be initially false. Now whenever user clicks the row, do `
delete.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
eventList.get(position).isSelected=true;
delete.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
}
So by doing this, we are keeping in memory which object is selected and which is not, but once you recycle your views bindViewHolder will be invoked aagain and UI elements setter will be called again so put a check inside onBindViewHolder()
if(eventList.get(position).isSelected){
//show progress bar
}else{
// show delete icon
}
To remove the item, just do the following changes in your adapter-
public void removeItem(int position){
eventList.remove(position)
notifyItemRemoved(position)
}
I've seen some RecyclerView examples that simply modify an item within the click listener. My problem is that I start an activity from a click on an item, and the activity can change or delete the clicked item from the list. So the view needs to be updated after the activity is finished.
This code mostly from another developer passes the serialized item and position of the item to the activity as extra data. The position was intended to use to update the view later.
However, I found these comments:
"Do not treat position as fixed; only use immediately and call holder.getAdapterPosition() to look it up later. RecyclerView will NOT call onBindViewHolder again when the position of the item changes in the data set unless the item itself is invalidated or the new position cannot be determined. For this reason, you should only use the position parameter while acquiring the related data item inside this method, and should NOT keep a copy of it. If you need the position of an item later (eg. in a click listener), use getAdapterPosition() which will have the updated adapter position."
In the Adapter:
#Override
public void onBindViewHolder (#NonNull ItemAdapter.MyViewHolder holder, final int position)
{
final Item item = items.get(position);
holder.itemTitle.setText(item.getTitle());
holder.lay_item.setOnClickListener (new View.OnClickListener () {
#Override
public void onClick(View v) {
Intent intent = new Intent(context, ItemDetailActivity.class);
intent.putExtra("Item", item);
intent.putExtra("position", position);
context.startActivity (intent);
// TODO is this the place to refresh the view?
//holder.getAdapterPosition();
}
});
}
and for the activity:
Item currentItem;
protected void onCreate (Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_item_detail);
currentItem = (Item)getIntent().getSerializableExtra("Item"));
position = getIntent().getExtras().getInt("position");
...
}
I realize that the object held by the view is serialized into the extra data, so currentObject in the activity is not the same object.
I don't know why they did it like that. If that is the norm, please tell me how the list view is updated for changes to the object in the activity. If that is not the norm, how should it be done?
Therein lies the basis of the question, stated in the title:
How do I get a list in an android RecyclerView to update after an activity that modifies what it was showing?
Within the Activity, there is this click listener for the "save" button to update the database:
btn_save.setOnClickListener (new View.OnClickListener () {
#Override
public void onClick(View v) {
// ... stuff that updates the item attributes from the view elements...
// Save it. It is this object that should then be in the list in the RecyclerView.
try
{
MyApp.getDatabase().updateItem(item);
// TODO: This might work if currentItem was actually the one in the list
//adapter.notifyDataSetChanged();
}
catch (PersistenceException ex)
{
// notify user of failure
Toast.makeText (EditItemActivity.this, getResources().getString(R.string.item_update_fail), Toast.LENGTH_LONG).show ();
finish();
return;
}
Toast.makeText(EditItemActivity.this, getResources().getString(R.string.item_update_success), Toast.LENGTH_LONG).show ();
finish ();
}
});
The adapter was created in the fragment like this (where "rv_items" is the RecyclerView):
adapter = new ItemAdapter(itemList, getActivity());
rv_items.setAdapter(adapter);
The Item class is declared as:
class Item implements Serializable
In general, any time you're talking about "modifying a RecyclerView", that's a hint that you're looking at things the wrong way. You should always think about modifying the data, and then realize that the RecyclerView is just one way to display that data. Of course, you'll need to call methods like notifyDataSetChanged() whenever you modify the data so that the RecyclerView can know to update its display to pick up the changes, but you should still always think about the data first.
That being said, the root of the problem here is that you need some way to uniquely identify your item in your list of data items. Generally, I'd lean towards using some sort of unique ID here (instead of position). Then, when your second activity finishes and returns its result, you can use the ID to update your data list and dispatch the changes to the RecyclerView.
With all that, you'd have something like this:
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == MY_REQUEST) {
if (resultCode == Activity.RESULT_OK && data != null) {
String id = data.getStringExtra("id");
for (int i = 0; i < items.size(); ++i) {
if (items.get(i).getId().equals(id)) {
// the item at position i was updated.
// insert your code here...
// at the end, notify the adapter
adapter.notifyItemChanged(i);
}
}
}
}
}
I have a one ImageView (With border heart icon) in each row of my
recyclerview. I use this icon for add to favorite list . when I press this
image view it's change to other icon (complete heart icon) . every thing is ok
, But when i go to other
Activity it return to the default icon (border heart icon) . I use the flag for doing this work .
This is my RecyclerView Adapter (image onClick event):
//============== IMG ADD TO FAVORITE CLICK LISTENER ======================
holder.imgAddFav.setOnClickListener(new View.OnClickListener() {
boolean flag = false;
#Override
public void onClick(View v) {
QuestionDatabaseAdapter databaseAdapter = new QuestionDatabaseAdapter(v.getContext());
if (!flag) {
ModelQuestion question = new ModelQuestion();
question.setQuestionTitle(questionha.get(position).getQuestionTitle());
question.setQuestionDesc(questionha.get(position).getQuestionDesc());
question.setQuestionDate(questionha.get(position).getQuestionDate());
question.setQuestionAuthorName(questionha.get(position).getQuestionAuthorName());
question.setQuestionAuthorPic(questionha.get(position).getQuestionAuthorPic());
question.setQuestionDownLink(questionha.get(position).getQuestionDownLink());
databaseAdapter.saveQuestion(question);
Toast.makeText(v.getContext(), "Added !", Toast.LENGTH_SHORT).show();
holder.imgAddFav.setImageResource(R.drawable.ic_favorite_red_700_24dp);
flag = true;
} else {
Toast.makeText(v.getContext(), "Removed !", Toast.LENGTH_SHORT).show();
holder.imgAddFav.setImageResource(R.drawable.ic_favorite_border_red_a700_24dp);
flag = false;
}
}
});
}
And this is my icons .
To maintain the state of your each item, in your model class create a boolean for ex:"isClicked", by default make it false, when he clicks make it true in adapter onclick, u need to display item based on this "isClicked" property.
if(isClicked){
//show filled heart
}else{
//show empty heart
}
It will return to default icon at holder.imageView.setImageResource();
To over come this store the items added to favourite list and then set the image properly.
//this is a simple code to check whether its in favourite
//itemMap is a static HashMap<Integer,Boolean>
// make a statement like isFavorite=itemMap !=null && itemMap.get(new Integer(position) to be used in if else statement
//use the hashmap for current use. If hasgmap is null retrieve it from a database.
//you should probably do that at the constructor of the adapter
if(isInFavourites){
holder.imageView.setImageResource(R.drawable.favouriteImage);
}else{
holder.imageView.setImageResource(R.drawable.addFavImage);
}
Its always good practice to store the favourite item at onClick in a hash map and in a database using a new thread to prevent a possible lag.
Explanation as requested
You have a onBIndViewHolder() method in your recycle view adapter and it contains the method holder.imgAddFav.setImageResource() which you use to add the favourite border image.
The adapter calls that function whenever the recycle view items are created and the bordered image is set as a result.
So at imgAddFav onClick you save the newly added item to favorites in a static hashMap that you've declared as an instance variable.
//consider the hashMap as favMap;
HashMap<Integer,Boolean> favMap;
//at the constructor of the recylcer view you've to check whether you have items that you previously added to the favorite list
public YourAdapter(/*parameters*/){
if(favMap==null){
favMap=new HashMap<>();
}
//now fill the map with stored data
//You can use sharedpreferences or a datase
//for an example think that postio 2 is in favorites;
favMap.put(new Integer(2),true);
}
Now back to the getItemHolder(). Here before setting the imgAddFav image check from the hashmap whether the particular position is in favourites.
//set a boolean to check whether this item is in favorites
boolean isInFavorites;
if(favMap.contains(new Integer(position)) && favMap.get(new Integer(position){
//the item is in the favourite list
//set the full colored image
holder.imgAddFav.setImageResource(R.id.coloredHeart);
isInFavorites=true;
}else{
holder.imgAddFav.setImageResource(R.id.borderedHeart);
isInFavorites=false;
}
Now at onclick not only change the image but store the result as well
holder.imgAddFav.setOnClickListener(new View.OnClickListener{
#Overrride
public void onClick(View v){
if(isInFavorites){
favMap.put(new Integer(position),false);
//save this same thing in a database or sharedPreference.Do it in a new thread.
//if you want to save to a database
new Thread(new Runnable{
#Override
public void run(){
//insert into database
}
}).start();
//or else if you want save to sharedPreference you can use edit.apply() method
//now change the image or simply call notify datasetChanged
notifyDatasetChanged();
}
}
});
I have a Button in a ListViewItem in a ListView within a Fragment. I have code that successfully notifies the host Activity of the button being tapped. While the code works, I want to make sure that this is the best design pattern to use here.
Here is a summary of the code:
The Activity (MainActivity) passes a reference of itself (this) to the Fragment in a variable called mainActivityReference.
The Fragment passes this reference to the ArrayAdapter object in a variable also called mainActivityReference.
In the ArrayAdapter getView method, I set the onClickListener and call a method within the mainActivityReference with the index position of the item as a parameter as follows:
viewHolder.soundButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d(TAG, "Sound button tapped at position " + position);
mainActivityReference.chooseSoundForIndex(position);
}
});
Does this seem kosher? Or should I use something like a LocalBroadcastManager?
The basic strategy of passing an "owner" object to an adapter is quite kosher, and very common. Often I will make a listener interface, and have the parent activity or fragment implement the interface. Since it is your activity that is responding to the click, you wouldn't even need to pass the reference to the fragment and then the adapter. You could just get the View's context, and check if it implements the interface. Like this:
public interface SoundChooser {
void chooseSoundForIndex(int position);
}
viewHolder.soundButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d(TAG, "Sound button tapped at position " + position);
Context context = v.getContext();
if (context instanceof SoundChooser) {
((SoundChooser)context).chooseSoundForIndex(position);
} else {
Log.w(TAG, "Activity should implement SoundChooser:" + context.getClass().getName());
}
}
});
I am trying to delete an item in my listview through a context menu. I have the context menu working and I can easily insert new items to the list view but I can not remove them. I have two options edit and delete.
public boolean onContextItemSelected (MenuItem item)
{
super.onContextItemSelected(item);
if(item.getTitle() == "Edit")
{
Toast.makeText(this, "Edit", Toast.LENGTH_LONG).show();
}
if(item.getTitle() == "Delete")
{
Toast.makeText(this, "Delete", Toast.LENGTH_LONG).show();
myActivities.remove(item.getGroupId());
populateListView();
}
return true;
}
for some reason, the item being deleted is always the first item!
myActivities is my dynamic array and all I do in populateListView is populate it. I know populateListView works since if I add an item to myActivities then use the function, the new item will be added. Delete is not working though!
Note: I have also tried
myActivities.remove(item.getItemId());
Let me tell you the general approach for deleting item from Listview.
You have a list of data, which you pass to the adapter and through adapter display in Listview to the user.
So when you want to delete a particular item, first get the position of the item, delete it from the datalist. As the underlying data has been changed call notifyDataSetChanged() on the adapter which will make the Listview to refresh the child views.
Try following:
if((item.getTitle()).equals("Delete")) // You are comparing Strings, don't use ==
{
Toast.makeText(this, "Delete", Toast.LENGTH_LONG).show();
removeItemFromList(itemPosition);
}
// method to remove list item
protected void removeItemFromList(int deletePosition) {
your_datalist.remove(deletePosition);
adapter.notifyDataSetChanged();
}
Hope it helps.