Is there any way to update the appearance of one RecyclerView element without a full reload? - android

I'm making an app that has a main RecyclerView "feed" comprised of post elements.
You can click on a post which takes you to a detailed view.
I opted to put the detailed view in a "transparent" activity so that when the detailed view is clicked on, the current activity is paused and the new one starts. This means that the feed activity does not lose its state and the recyclerview maintains its position so that when the user clicks back the activity/fragment does not need to reload.
On both the feed's posts and the detailed view, I have thumb buttons that can be clicked. When the user enters the detailed view, I pass in the thumb button's state and it's initialized in onCreate(). When the user exits the detailed view, I need to update the state of the thumb button in the main feed (to keep them in sync) without refreshing the entire recyclerview. This requires me refreshing the dataset (and subsequently updating the appearance) in one item of the recyclerview without refreshing/reloading the entire recyclerview. Is this possible?
I tried doing this by updating the data set used by my adapter and calling notifyItemChanged(position) but this did not work. I was told this method calls onBindViewHolder (which would then call my bind method and update the view).
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// Here, I'm updating my mRecipes data set after returning from the detailed view
if (requestCode == 0) {
int adaptpos = Integer.parseInt(data.getStringExtra("adaptpos"));
String likes = data.getStringExtra("likes");
String favorites = data.getStringExtra("favorites");
mRecipes[adaptpos].setLikes(likes);
mRecipes[adaptpos].setFavorites(favorites);
mAdapter.notifyDataSetChanged();
mAdapter.notifyItemChanged(adaptpos);
}
}
Any help going about this would be greatly appreciated. If all else fails, I may have to just reload the entire fragment to reflect the change in state.

You can use DiffUtil to handle this scenario , its an easy way to handle updates to recycler view items and was recently added to the support library.
This medium post explains the implementation

Related

Is there way to acces every RecyclerViewHolder?

I have a recyclerview with data that changes during the lifecycle of an app, lets say that in my recycler view holders i have an edittext field that comes with populated data but I want user to have access to change that data, is there a way that when the button is pressed i can somehow access all of those textfields at once?
Hmm you could keep 2 parallel lists in your recycle view adapter? one that has the main data, the other one is updated whenever the user changes something on an edit text.
Once the user presses the update button, you can just copy the data from list2(with modified data) to list1 (original) and update the recycler view.
for example
inside onBindviewholder()
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
// find your edittext to set a listener for text change
// get the current item from list2 (list2 has same item originally as the original list)
keep updated the list2 data here.
}
on the main activity once the user presses the button you can do
adapter.mainList = new ArrayList<>(adapter.list2)
adapter.notifyDataSetChanged()

How to swap items from two recyclerview items that are in two fragments in one activity?

I have two fragments (a and b) inside an Activity according to the picture below.
I am able to delete from first one but how to add that item to favorites fragment RecyclerView?
Deleting actress name and adding to favorites
My Viewholder code for RecyclerView Fragment one class:
addToFavoriteButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mArrayList.remove(getAdapterPosition());
notifyDataSetChanged();
}
});
How to add this deleted item inside adapter of Favorites Recyclerview?
What I would do is maintain a list of actresses (either locally or on the server) with each one containing an isFavorite boolean attribute.
Then, while you have one global list, each recyclerview is only showing a subset:
On the left, you show all actresses where isFavorite is set to false.
On the right, you show all actresses where isFavorite is set to true.
How you update it could be done a few different ways, but here is what I recommend at a high level:
Have an onClick listener for each one that bubbles up to the activity, so the activity is aware any time an actresses's favorite state changes. Every time the state changes for an actress, tell your adapters in each fragment to update.
If you don't want to refresh the entire list every time, you could integrate a remove and add method like Mauker's Answer.
You can create a method inside your adapter that removes an item from the RecyclerView, and returns the given item.
Then, you can use this item reference to add it to the second RecyclerView.
Pseudocode example
public myItem removeAndGetItem(int position) {
myItem item = mArrayList.get(position);
mArrayList.remove(position);
notifyDataSetChanged();
return item;
}
Then you could call something like (also, pseudocode):
myItem item = adapter1.removeAndGetItem(position);
adapter2.add(item);
Adjust the examples to your code, and it should do the trick.
Edit
I misread the part about the RecyclerViews being on different Fragments.
So, you can still do what I said on the example above, you'll just have to pass that item to the second Fragment, using Fragment callbacks, or Broadcasting the item, or even through an EventBus.
Instead of using notifyDataSetChanged() which can be very costly, try to use notifyItemRemoved(int position) instead. As you can see on the docs:
If you are writing an adapter it will always be more efficient to use the more specific change events if you can. Rely on notifyDataSetChanged() as a last resort.

What's the correct way of displaying ViewPager after associated ListView's item click?

I'm a beginner in Android, so I apologize for the mistakes and I'd appreciate any constructive criticism.
I'm writing a basic application with a ListView of images, and when the user clicks on an item in the list, I want to display that image in a ViewPager, where the user can swipe back and forth to browse the whole list of images. Afterwards when the user presses the back button, I want to switch back to the ListView.
I manage the business logic in the MainActivity, which uses MainActivityFragment for the ListView and ImageHolderFragment for ViewPager.
The simplified code so far is as follows:
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListItems = new ArrayList<>();
mListItemAdapter = new ListItemAdapter(this, R.layout.list_item, R.id.list_item_name, mListItems);
mListView = (ListView) findViewById(R.id.list_view_content);
mListView.setAdapter(mListItemAdapter);
mDeletedListItems = new ArrayList<>();
mViewPager = (ViewPager) getLayoutInflater().inflate(R.layout.image_display, null, true);
mImageAdapter = new ImageAdapter(getSupportFragmentManager(), mListItems);
mViewPager.setAdapter(mImageAdapter);
mViewPager.setOffscreenPageLimit(3);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mViewPager.setCurrentItem(position);
setContentView(mViewPager); // TODO: this is very wrong!
}
});
loadImages();
noContentText = (TextView) findViewById(R.id.no_content_text);
if (mListItems.isEmpty()) {
noContentText.setText(R.string.no_images);
} else {
mImageAdapter.notifyDataSetChanged();
}
}
Although this does work to some extent, meaning that it manages to display the ViewPager when an item in the list is clicked, there are two things about it ringing the alarm bells:
I've read that calling setContentView() for the second time in the same class is pretty much a sin. Nobody explained me why.
The back button doesn't work in this case. When it's pressed, the application is terminated instead of going back to the list view. I believe this is connected to the first point.
I would appreciate any help, explanations if my idea is completely wrong, and if my case is hopeless, I'd like to see a successful combination of ListView and ViewPager with transitions between each other.
Your activity already has R.layout.activity_main set as content view, which rightly displays the list view - that's what the responsibility of this activity is as you defined it. If we want to change what's shown on the screen, we should use a different instance of a building block (activity or fragment) to display the view pager images.
To say the least, imagine if you wanted to change the view to a third piece of functionality or UI, or a fourth... it would be a nightmare to maintain, extend and test as you're not separating functionality into manageable units. Fields that are needed in one view are mixed with those needed in another, your class file would grow larger and larger as each view brings its click listeners, callbacks, etc., you'd also have to override the back button so it does what you want - it's just not how the Android framework was designed to help you. And what if you wanted to re-use UI components in different contexts whilst tapping in to the framework's activity lifecycle callbacks? That's why fragments were introduced.
In your case, the list view could continue to run in your MainActivity and in your click listener, onItemClick you could start a new activity that will hold a viewPager:
Intent i = new Intent(MainActivity.this, MyLargePhotoActivityPager.class);
i.putExtra(KEY_POSITION, position);
// pass the data too
startActivityForResult(i, REQUEST_CODE);
Notice how you could pass the position to this activity as an int extra, in order for that second activity to nicely set the viewPager to the position that the user clicked on. I'll let you discover how to build the second activity and put the ViewPager there. You also get back button functionality assuming your launch modes are set accordingly, if needed. One thing to note is that when you do come back to the list View, you'd probably want to scroll to the position from the view pager, which is why you could supply that back as a result via a request code. The returned position can be supplied back to the list view.
Alternatively, you could use the same activity but have two fragments (see the link further above) and have an equivalent outcome. In fact, one of your fragments could store the list view, and the second fragment could be a fullscreen DialogFragment that stores a viewPager, like a photo gallery (some details here).
Hope this helps.
I've read that calling setContentView() for the second time in the
same class is pretty much a sin. Nobody explained me why.
Well, you kind of get an idea as to why.
When you use setContentView() to display another 'screen' you do no have a proper back stack.
You also keep references to Views (like mListView) that are not visible anymore and are therefore kind of 'useless' after you setContentView() for the second time.
Also keep in mind orientation changes or your app going to the background - you'll have to keep track of the state that your Activity was in which is way more complicated than it has to be if you have one Activity that does two different things.
You won't be arrested for doing things like you do right now, but it's just harder to debug and keep bug free.
I'd suggest using two different Activities for the two different things that you want to do, or use one Activity and two Fragments, swapping them back and forth.
If you insist on having it all in one Activity you need to override onBackPressed() (called when the user presses the back button) and restore the first state of your Activity (setContentView() again, pretty much starting all over).

How to repopulate an arrayadapter when database is modified in a different activity?

I am working on an array adapter which uses a master detail flow layout in order to show list of items that are stored within the apps database.
From this activity/fragment, I call a different activity to add a new record to the database. When the new record is added, this activity is finished and the user is returned to the master detail flow layout.
I then want this view to be shown with the updated database that was created in the new activity. I'm not 100% sure what would be the best way to implement this. I was thinking of in the onResume method, I call a function that repopulates the array adapter with the new data but this seems like the way wrong way as it would always repopulate even if no data was changed in the database.
You can start the activity for adding items using startActivityForResult()
and implement the callback onActivityResult() in the list activity.
You can repopulate the list items in the onActivityResult() in case there was a successfully added element to the database.
See this http://developer.android.com/training/basics/intents/result.html
For example in listActivity
private void addItem(){
...
startActivityForResult(intent, ADD_REQUEST);
...
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == ADD_REQUEST && resultCode == RESULT_OK) {
// here you can repopulate the list
}
}
and the addItem activity
private void addingItem(){
...
// when the item saved successfully you can set the results to ok
setResult(RESULT_OK);
...
}
How about if you save a value to shared prefrances that would act as a flag :
let us say the value is x : 1 if it is changed and 0 if not.
By default it should be zero off course. whenever the user accesses the new activity and the contents of the database are changes the value should be changed back to 1. (If not it would stay zero).
Then onResume you check this value if it is zero then you do not re populate the list. However, if it is 1, you change it back to zero, update the list from the database, and call notifydatasetchanged() on the adapter.
Good Luck
You could always use startActivityForResult() and inform the calling activity whether or not data was changed.

Changing reenter animation to another item of a list

I have a RecyclerView with images and when I press an image the app opens another activity that contains a ViewPager with the same images but in the position of the one I selected.
I've done the transition in Lollipop to share this image between activities using supportPostponeEnterTransition and supportStartPostponedEnterTransition in the called activity to wait until the viewPager is loaded with images to start the transition.
When I enter in the called activity and when I press back the transitions are ok.
The problem I'm facing is if I move to another image in the ViewPager of the called activity, when I press back it animates the image that it was selected at the beginning, not the currently selected one.
I've been able to change the animated image to the one selected in the called activity with this:
#Override
public void onBackPressed() {
View view = ((ImageDetailFragment) adapter.getFragment(viewPager,
viewPager.getCurrentItem())).getTransitionView();
ViewCompat.setTransitionName(view, Constants.TRANSITION_IMAGE);
super.onBackPressed();
}
But it is returning to the same position of the original image in the list of the calling activity.
How can I do it to make the image return to its position in the list of the calling activity?
The first thing to do is to make sure that the views work properly without any Activity transition. That is, when your return from Activity with the ViewPager, the RecyclerView Activity should be showing the View that the ViewPage was showing. When you call the ViewPager activity, use startActivityForResult and use the result to scroll the RecyclerView to the correct position.
Once that is working, the Activity Transition can be made to work. I know that you've given each View in your RecyclerView a different transitionName, right? When you bind the View, call setTransitionName and give it a repeatable name. Typically this is the image URL or cursor row ID or at worst some munged index like "image_" + index.
The next thing you need to do is set the SharedElementCallback for both the calling Activity (exit) and called activity (enter). In each, you're going to need to override the onMapSharedElements callback to remap the shared element.
#Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
// assuming just one shared element, excuse the ugly code
sharedElements.put(names.get(0), mSharedElement);
}
Here, mSharedElement has been set in the onActivityReenter in the calling (RecyclerView) activity and in onCreate and prior to finishAfterTransition (onBackPressed?) in the called (ViewPager) activity.
The onActivityReenter gives new functionality specifically for this case. You can look at the results there before the called Activity completes.

Categories

Resources