I have a RecyclerView list of CardViews created when a user enters a bunch of data with some EditTexts. A click on a single CardView from the list loads a DetailsActivity that shows only that specific CardView to the user. Another click on that CardView loads an EditActivity that allows the user to edit the original data they entered.
When the user saves any edited data, the EditActivity closes and the user is returned to the specific CardView. But the CardView is not updated with the edited data. The RecyclerView list of CardViews does update as expected because if I backspace out of the DetailsActivity and return to the MainActivity, then the CardView that was edited shows up correctly. How do I refresh the view for the single CardView in the DetailsActivity?
MainActivity (the RecyclerView list)
...
#Override
public void onItemClick(int position, final View view) {
// Create a new intent to send data from this MainActivity to the DetailsActivity
Intent intent = new Intent(this,CardViewDetails.class);
// Send the position of the CardView item that was clicked on in the intent.
intent.putExtra("position",position);
startActivity(intent);
}
DetailsActivity (for the single CardView)
...
// Get the position of the clicked on RecyclerView list CardView from
// the MainActivity's intent bundle.
Bundle extras = getIntent().getExtras();
if (extras != null) {
// get the CardView item using the int position from the
// MainActivity's onItemClick() and the putExtra in the intent.
position = extras.getInt("position", 0); // 0 is default value
}
cardView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// pass the position variable to the start method of
// EditActivity so get the correct data from the db can
// be loaded onto the EditText lines, etc. of the
// EditActivity and that can then be used to pass the
// data back to the MainActivity.
EditActivity.start(CardViewDetails.this,listItems.get(position));
}
});
EditActivity (for editing the original CardView data)
...
// Launches the activity to edit an Item (CardView) that was clicked on from the
// RecyclerView list in the MainActivity file. The intent brings the item's
// position in the RecyclerView Adapter so the correct Item is edited.
public static void start(Context context, ListItem item) {
Intent intent = new Intent(context, ActActivity.class);
// From the OnItemClick method in the MainActivity the RecyclerView item
// position from the Adapter is passed into a putExtra bundle that the
// intent carries to this Activity. The data is then copied in the onCreate()
// below using getIntent().getParcelableExtra(). So this is to update an
// existing CardView item.
intent.putExtra(ActActivity.class.getSimpleName(),item);
context.startActivity(intent);
}
public void onClickSaveEdits(View v) {
// Update the user EditText input to the database.
sqLiteDB.update(item);
// Close the EditActivity.
finish();
**what am I missing here to update/refresh the view for the just edited CardView that is shown in the DetailsActivity?**
}
Call notifyDataSetChanged() on your adapter whenever you make a change to the data you're using for the RecyclerView.
For example, your onClickSaveEdits(View v) method would look as follows:
public void onClickSaveEdits(View v) {
// Update the user EditText input to the database.
sqLiteDB.update(item);
adapterObjectHere.notifyDataSetChanged();
// Close the EditActivity.
finish();
}
Related
I'm developing an android Wallpaper app consists of 2 activity, main activity displays the images from the internet in ListView and the second activity displays the preview of that image.
my problem is when I press the back button in preview activity to go back to the main activity, the main activity displays the images from the beginning and I would like to display the images from the last I clicked on.
The following code in onCreate() method in Main Activity:
// Find a reference to the {#link ListView} in the layout
ListView gameListView = findViewById(R.id.list);
mEmptyStateTextView = findViewById(R.id.empty_view);
gameListView.setEmptyView(mEmptyStateTextView);
// Create a new adapter that takes an empty list of games as input
mAdapter = new GamesAdapter(this, new ArrayList<Games>());
// Set the adapter on the {#link ListView}
// so the list can be populated in the user interface
gameListView.setAdapter(mAdapter);
// Set an item click listener on the ListView, which sends an intent to a web browser
// to open a website with more information about the selected game.
gameListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
// Find the current game that was clicked on
Games currentGame = mAdapter.getItem(position);
// Convert the String URL into a URI object (to pass into the Intent constructor)
Uri gameUri = Uri.parse(currentGame.getUrl());
String name = currentGame.getName();
// Create a new intent to view the game URI
Intent i = new Intent(GamesActivity.this, PreviewActivity.class);
i.setData(gameUri);
i.putExtra("name", name);
startActivity(i);
}
});
The following code in onCreate() method in Preview Activity:
final Uri i = getIntent().getData();
String profile = getIntent().getStringExtra("name");
photographer.setText(profile);
Picasso.with(this).load(i).into(img);
saveImage.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
downloadFile(i);
}
});
I think what your are looking for is the method onSaveInstanceState(Bundle outState) here you can save your values in the outState and get them in onCreate() at the savedInstanceState
here is a very good example how to use it.
I think you want to preserve the scroll distance. For that what you need to do is you can preserve using Bundle object. But you can also clear current activity then you will get the same results.
Add this outside the onCreate() method(this is java):
#Override
public void onBackPressed() {
this.finish(); //important
}
finish() clears activity from back stack.
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 am using Recyclerview adapter to populate Recyclerview. After populating Recyclerview from SQLite, If user want to open an recyclerview item need to click on that item and adapter open the related activity. Here is an image which can help you understand easily.
When an activity is open user can delete that post from SQLite by clicking delete button after deleting data recyclerview should dynamically update data.
You can also use StartActivityForResult and use the result of the second activity for delete item in first one.
I mean:
FirstActivity starts SecondActivity waiting for result
SecondActivity sends the result back to FirstActivity. Only if you delete
the item.
Now FirstActivity remove and refresh the list.
In FirstActivity:
Intent i = new Intent(this, SecondActivity.class);
startActivityForResult(i, 1);
In SecondActivity, when you push delete button:
Intent returnIntent = new Intent();
returnIntent.putExtra("delete", true);
returnIntent.putExtra("position", position);
setResult(Activity.RESULT_OK, returnIntent);
finish();
And finally, FirstActivity handle the result:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1) {
if(resultCode == Activity.RESULT_OK){
if (data.getBooleanExtra("delete") {
// get position and delete item from list and refresh
int position = data.getIntegerExtra("position");
}
}
if (resultCode == Activity.RESULT_CANCELED) {
//Write your code if there's no result
}
}
}//onActivityResult
https://stackoverflow.com/a/10407371/1820599
Edited:
Getting the context of your activity inside the adapter constructor:
FirstActivity listener;
public myAdapter(Context context, List<String> items) {
super(context, R.layout.row_edition, items);
this.listener = ((FirstActivity) context);
this.items = items;
}
Then, inside the adapter, when you push on item, call the activity to start the seconde one:
listener.startSecondActivity(int position, parameters you need to use);
and finally, in your FirstActivity
startSecondActivity(int position, parameters you need to use) {
// whatever you have to do
Intent i = new Intent(this, SecondActivity.class);
// push position inside intent and whatever you need
startActivityForResult(i, 1);
}
The flow is:
Push item
Use FirstActivityListener to call SecondActivity
In SecondActivity delete and senr result back
In FirstActivity remove item from adapter, using an auxiliar method
inside que adapter
if you display list of companies in recyclerview once you click to show detailes of company and you delete the company once back you should found the item disappear this what my code do
protected void onResume()
{
super.onResume();
Log.i("TAG", "resume");
if(yourlist.size() > 0)
{
yourlist.clear();
yourlist.addAll(where your data come from
ex:databaseHelper.GetOrganization());
youradapter.notifyDataSetChanged();
}
}
You must have to implement a listener in your activity, that tells your recycler view that the items has changed. I suppose that you have implemented your own onItemClickListener for recycler view, so you have position and can easily remove item from recycler view data set. For more info, please, post your code.
This listener goes in your class that populate Recycler view.
public interface DeletedListener {
void deleted(int position);
}
Makes your activity implement this listener, and there send what position have to remove.
public void setListener(DeletedListener listener) {
this.listener = listener;
}
DeletedListener listener;
From your activity call the setListener method, and from adapter, call deleted method.
Explaining the structure: In MainActivity I have a drawer menu and a fragment to display contents. From the drawer I can choose a category and a list of items in that category is read from database and is displayed in a listView in the content fragment.
When an item from the list view is clicked, a DetailActivity will start.
Problem: In the DetailActivity there is a button to remove the item from database. When this Item is pressed the DetailActivity closes. And the previous content fragment in the MainActivity is displayed. (like pressing back button) But the problem is that the item I deleted is still shown in the listView. I have go to the relevant category so that the listView refreshes and that item isn't there anymore.
What I expect: What I want is that when I delete the item in the DetailActivity and return to the previous activity, the listView is automatically updated.
What I've done: I know I can use onActivityResult like this:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// TODO Auto-generated method stub
super.onActivityResult(requestCode, resultCode, data);
// Refresh Bookmarks page when a bookmark is deleted
if (requestCode == LESSON_DETAIL_ACTIVITY && resultCode == -1) {
datasource.open();
lessons = datasource.findMyLessons();
refreshDisplay(context, view, category, i); // Problem Here!
}
}
As you see my refreshDisplay method takes 4 arguments. Originally these arguments are sent to the DetailActivity. (when a list item is clicked in the fragment inside MainActivity)
When I press "delete item" button in the DetailActivity and it closes. I don't know how to retrieve those argument so that I can refresh the list.
Here I post the code of my refreshDisplay method and how I call it just in case its needed.
I call refreshDisplay method inside a fragment (in the MainActivity) and the refreshDisplay itself is in the MainActivity.
public static class contentFragment extends Fragment {
public static final String ARG_CATEGORY_NUMBER = "category_number";
public contentFragment() {
// Empty constructor required for fragment subclasses
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_content, container, false);
int i = getArguments().getInt(ARG_CATEGORY_NUMBER);
String category = getResources().getStringArray(R.array.category)[i];
((MainActivity) this.getActivity()).refreshDisplay(this.getActivity(),rootView, category, i);
getActivity().setTitle(category);
return rootView;
}
}
my refreshDisplay method is:
public void refreshDisplay(Context context, View view, String category, int i) {
List<Lesson> lessonByCategory = datasource.findByCategory(category, i);
final ListView lv = (ListView) view.findViewById(R.id.listView);
final ArrayAdapter<Lesson> adapter = new LessonListAdapter(context, lessonByCategory);
lv.setAdapter(adapter);
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick (AdapterView<?> parent, View view, int position, long id) {
Log.i(LOGTAG, "onListItemClick called");
ArrayAdapter<Lesson> m_adapter = adapter;
// get the Lesson object for the clicked row
Lesson lesson = m_adapter.getItem(position);
// use this if your `refreshDisplay()` method is in your activity
Intent intent = new Intent(MainActivity.this, LessonDetailActivity.class);
isStared = datasource.isStared(lesson);
intent.putExtra(".model.Lesson", lesson);
intent.putExtra("isStared", isStared);
startActivityForResult(intent, LESSON_DETAIL_ACTIVITY);
}
});
}
Can anyone please help me?
Edit 1: In my code, in the onActivityResult I have called refreshDisplay(context, view, category, i); But please notice those arguments I have passed are not defined. Without the correct arguments refreshDisplay doesn't work!
Edit 2: I have a drawer navigation that contains a list of categories. When I click on one of the categories, In my content fragment the onCreateView calls refreshDisplay ** and puts context & View arguments plus category & i which are the name and position of the category chosen. Now refreshDisplay takes those arguments and and creates the list of items of that category and shows it in the content Fragment. refreshDisplay has a list adapter and listener that when clicked opens DetailActivity. From DetailActivity I can delete that item and DetailActivity is closed. I'm back to the list. The list still shows that deleted item. I want to update the list.
Just try my hints on you code .... I hope it might be solve your problem
call this method after delete item from you adapter. It will refresh the adapter content.
notifyDataSetChanged()
You can pass the Context to following method as first param..
refreshDisplay(/*this.getActivity()/ context,rootView, category, i);
Solution:-
declare your list listCategory static in your Activity class(global variable)
declare your listView as instance variable of your activity class.
In DetailActivity delete the listCategory item(You can access it cause it is static)
like this:-
MainActivity.listCategory.remove(<the position of that detail>);
declare your adapter also as instance variable of your activity class.
no need of your refreshDisplay method just use adapter.notifyDataSetChanged() in onActivityResult() method
Depending on the clicked button out of 3 button, different data gets populated in listView.
I've used this
onListItemClick snippet
//ltable refers to list
String item = ltable.getItemAtPosition(position).toString();
Intent i = new Intent(getApplicationContext(),NextClass.class);
i.putExtra("name", item);
startActivity(i);
Now on any button click, corresponding data gets populated in listView. Then on listItelClick, it navigates to NextClass.class and hence new activity gets launched.
What if I want app to navigate to next view if and only if listView is populated when Gainers or Losers button is pressed????
If listView is populated on Index button click, it should not navigate.
i.e. Clicked button should be captured.
I tried to use flag, but only final variables are permitted within buttonClickListener, so it doesn't work.
How can I implement this??
ANY HELP WILL BE LIFE-SAVER !!!
Take a class level variable
boolean shouldNavigate = false;
and in onClick() of Index Button. set shouldNavigate to false:
public void onClick(View v)
{
// Update adapter for Index..
shouldNavigate = false;
}
But in onClick() of other than Index Button. set shouldNavigate to true:
public void onClick(View v)
{
// Update adapter for Gainer or Losers..
shouldNavigate = true;
}
and inside your onItemClick() check for the flag and navigate accordingly
public void onItemClick(AdapterView parent, View view, int position, long id) {
if(shouldNavigate)
{
// you can navigagte...
}
else
{
// do other task
}
}
When you are in the 'Index' mode, remove the onItemClickListener. Add it back for the other two buttons.