Storing data own adapter after rotating - android

I have application with listview(one line contains textview, imageview, ratingbar) and my own adapter. When I rotate the screen after sorting listview (I choose this option in menu), it backs to form before sorting.
I tried override onSaveInstanceState:
#Override
public void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
outState.putParcelable("listView", listView.onSaveInstanceState());
}
and fragment onCreate:
listView.setAdapter(adapter);
if(savedInstanceState!=null)
{
mListInstanceState=savedInstanceState.getParcelable("listView");
listView.onRestoreInstanceState(mListInstanceState);
}
But it doesn't work. Should I override onRestoreInstanceState too or use something else?

The problem is when orientation change your Activity is recreated and when onCreate() is called you are setting the adapter with the default list items.
If you don’t maintain different layout for landscape/portrait, you can simply avoid the Activity recreate by just adding configChanges in manifest for your Activity
android:configChanges="orientation|screenSize" // add this line
This will avoid recreating the Activity and same layout will be used to fit on screen width. Make sure your listView layout width is set to match_parent.
If you still want your Activity to recreate, then you need to remember the last selected filter when onSaveInstanceState is called on orientation change
#Override
public void onSaveInstanceState(Bundle outState)= {
outState.putString(“selectedFilter","some name");
super.onSaveInstanceState(outState);
}
And then when onCreate in called after rotate, you can get the selectedFilter name
String filterName = null;
if(savedInstanceState != null){
filterName = savedInstanceState.getString("lastFilter");
}
Finally , set the listView with items based on the filter name.
if(filterName != null && filterName.equalsIgnoreCase("some name")){
// filtered list items
} else {
// default list items
}

Related

RecyclerView's textview store/restore state in android fragment not working

I am developing one android application my problem structure is like this.
One activity contains one fragment and this fragment contains one RecyclerView. RecyclerView item contains two textviews which displays simple text like this :
TextTitle TextValue --> item1
TextTitle TextValue --> item2
TextTitle TextValue --> item3
If User taps on text value, its value gets changed (Value is binary so it will be only two). If user change first two item's value by tapping and I want to restore those two values when I will come back to this fragment with a help of restore/save state.
I followed many articles and gather information that if I have to use proper unique id in item's view layout for both textviews then recyclerview state will be stored automatically, only just you need to save whole recyclerview state. Its not working in my case. What am I doing wrong? Do I need to fill adapter again?
any help would be appreciated.
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(INSTANCE_STATE_CONTENT_RECYCLER_VIEW,recyclerView.getLayoutManager().onSaveInstanceState());
}
// again restore it restore it
if (recyclerViewState != null) {
recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);
recyclerViewState = null;
}
#Override
public void onViewBound(#Nullable Bundle savedInstanceState) {
//super.onViewBound(savedInstanceState);
if (savedInstanceState != null) {
recyclerViewState = savedInstanceState.getParcelable(INSTANCE_STATE_CONTENT_RECYCLER_VIEW);
}
// continue with my work.
}

How to save scroll position of RecyclerView in Android?

I have Recycler view which lays inside of SwipeRefreshLayout. Also, have ability to open each item in another activity.
After returning back to Recycler I need scroll to chosen item, or to previous Y.
How to do that?
Yes, I googled, found articles in StackOverFlow about saving instance of layout manager, like this one: RecyclerView store / restore state between activities.
But, it doesn't help me.
UPDATE
Right now I have this kind of resolving problem, but, of course, it also doesn't work.
private int scrollPosition;
...//onViewCreated - it is fragment
recyclerView.setHasFixedSize(true);
LinearLayoutManager llm = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(llm);
data = new ArrayList<>();
adapter.setData(getActivity(), data);
recyclerView.setAdapter(adapter);
...
#Override
public void onResume() {
super.onResume();
recyclerView.setScrollY(scrollPosition);
}
#Override
public void onPause() {
super.onPause();
scrollPosition = recyclerView.getScrollY();
}
Yes, I have tried scrollTo(int, int) - doen't work.
Now I tried just scroll, for example, to Y = 100, but it doesn't scrolling at all.
Save the current state of recycle view position #onPause:
positionIndex= llManager.findFirstVisibleItemPosition();
View startView = rv.getChildAt(0);
topView = (startView == null) ? 0 : (startView.getTop() - rv.getPaddingTop());
Restore the scroll position #onResume:
if (positionIndex!= -1) {
llManager.scrollToPositionWithOffset(positionIndex, topView);
}
or another way can be #onPause:
long currentVisiblePosition = 0;
currentVisiblePosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
restore #onResume:
((LinearLayoutManager) rv.getLayoutManager()).scrollToPosition(currentVisiblePosition);
currentVisiblePosition = 0;
A lot of these answers seem to be over complicating it.
The LayoutManager supports onRestoreInstanceState out of the box so there is no need to save scroll positions etc. The built in method already saves pixel perfect positions.
example fragment code (null checking etc removed for clarity):
private Parcelable listState;
private RecyclerView list;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
listState=savedInstanceState.getParcelable("ListState");
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable("ListState", list.getLayoutManager().onSaveInstanceState());
}
then just call
list.getLayoutManager().onRestoreInstanceState(listState);
once your data has been reattached to your RecyclerView
Beginning from version 1.2.0-alpha02 of androidx recyclerView library, it is now automatically managed. Just add it with:
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"
And use:
adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
The StateRestorationPolicy enum has 3 options:
ALLOW — the default state, that restores the RecyclerView state immediately, in the next layout pass
PREVENT_WHEN_EMPTY — restores the RecyclerView state only when the adapter is not empty (adapter.getItemCount() > 0). If your data is loaded async, the RecyclerView waits until data is loaded and only then the state is restored. If you have default items, like headers or load progress indicators as part of your Adapter, then you should use the PREVENT option, unless the default items are added using MergeAdapter. MergeAdapter waits for all of its adapters to be ready and only then it restores the state.
PREVENT — all state restoration is deferred until you set ALLOW or PREVENT_WHEN_EMPTY.
Note that at the time of this answer, recyclerView library is still in alpha03, but alpha phase is not suitable for production purposes.
User your recycler view linearlayoutmanager for getting scroll position
int position = 0;
if (linearLayoutManager != null) {
scrollPosition = inearLayoutManager.findFirstVisibleItemPosition();
}
and when restoring use following code
if (linearLayoutManager != null) {
cardRecyclerView.scrollToPosition(mScrollPosition);
}
Hope this helps you
to save position to Preferences, add this to your onStop()
int currentVisiblePosition = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
getPreferences(MODE_PRIVATE).edit().putInt("listPosition", currentVisiblePosition).apply();
then restore position like this
if (getItemCount() == 0) {
int savedListPosition = getPreferences(MODE_PRIVATE).getInt("listPosition", 0);
recyclerView.getLayoutManager().scrollToPosition(savedListPosition); }
this last code should be added inside an event of the Adapter (not sure witch event but in my case was onEvent() - com.google.firebase.firestore.EventListener)
For some reason there are a lot of quite misleading tips/suggestions on how to save and restore scroll position in your_scrolling_container upon orientation changes.
Taking current scroll position and saving it in Activity’s onSaveInstanceState
Extending a certain scrollable View to do same there
Preventing Activity from being destroyed on rotation
And yeah, they are working fine, but…
But in fact, everything is much simpler, because Android is already doing it for you!
If you take a closer look at
RecyclerView/ListView/ScrollView/NestedScrollView sources, you’ll see that each of them is saving its scroll position in onSaveInstanceState. And during the first layout pass they are trying to scroll to this position in onLayout method.
There are only 2 things you need to do, to make sure it’s gonna work fine:
Set an id for your scrollable view, which is probably already done. Otherwise Android won’t be able to save View state automatically.
Provide a data before the first layout pass, to have the same scroll boundaries you had before rotation. That’s the step where developers usually have some issues.
The easiest and transition compatible way I found is:
#Override
public void onPause() {
super.onPause();
recyclerView.setLayoutFrozen(true);
}
#Override
public void onResume() {
super.onResume();
recyclerView.setLayoutFrozen(false);
}
in onSaveInstanceState() method of fragment you can save the scroll position of RecycleView
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
LinearLayoutManager layoutManager = (LinearLayoutManager)
recyclerView.getLayoutManager();
outState.putInt("scrolled_position",
layoutManager.findFirstCompletelyVisibleItemPosition());
}
then you can retrieve saved scroll position in onViewStateRestored() method
#Override
public void onViewStateRestored(#Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if (savedInstanceState != null) {
int scrollPosition = savedInstanceState.getInt("scrolled_position");
recyclerView.scrollToPosition(scrollPosition);
}
}
You can use scrollToPosition or smoothScrollToPosition to scroll to any item position in RecyclerView.
If you want to scroll to item position in adapter, then you would have to use adapter's scrollToPosition or smoothScrollToPosition.

Restore Android GridView after screen orientation change (without reloading it)

I'm new to Android (this is my first application) and I made a GridView in an Activity which I'd like it to be responsive according to screen position (portrait / landscape). Of course I made some custom layout values of GridView rows width for portrait as well as for landscape, and it works perfectly.
Except that, on every screen orientation change, the GridView is reloading again. And that's not good for user experience. I only like the GridView (and definitely its rows) to adjust to new screen width without doing all the onCreate() instructions (retrieving GridView's data from internet, assigning retrieved data in a HashMap object, setting the GridView's Adapter, then display the GridView's rows).
I went through many forum threads and some Google documentation that talk about restoring simple values, but never found an answer for restoring a GridView without having it to reload again.
I read about onSaveInstanceState() and onRestoreInstanceState(), but I didn't know how to use them in my case (GridView case).
Could any one provide a simple code sample for this? Or at least some headlines to follow?
Here's some of what I tried in my code, in the Activity class :
private Parcelable mListInstanceState;
private Parcelable mAdapterInstanceState;
private GridView gridView = null;
private MyAdapter myAdapter = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
gridView = (GridView) findViewById(R.id.gridview);
// Check whether we're recreating a previously destroyed instance
if(savedInstanceState != null) {
// Restore value of members from saved state
mListInstanceState = savedInstanceState.getParcelable("gridview");
mAdapterInstanceState = savedInstanceState.getParcelable("adapter");
}
else {
// Retrieve data from internet, fill HashMap object,
// set Adapter and display GridView only for the first time the activity is created
attemptToDisplayGirdViewItems();
}
}
#Override
protected void onSaveInstanceState(Bundle state) {
// Save activity state
super.onSaveInstanceState(state);
state.putParcelable("gridview", gridView.onSaveInstanceState());
// state.putParcelable("adapter", myAdapter);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
// Retrieve and restore activity state
// Restore state members from saved instance
mListInstanceState = savedInstanceState.getParcelable("gridview");
mAdapterInstanceState = savedInstanceState.getParcelable("adapter");
super.onRestoreInstanceState(savedInstanceState);
}
You don't need to save the views. If they have ID, they'll be saved automatically (such as scroll position of a GridView). What you need to save is the adapter's data set.
Your adapter presumably holds a list of items. Unless these items are primitives or String or implement Serializable, they need to implement Parcelable for this to work.
See here how to do that: How can I make my custom objects Parcelable?
Your adapter needs a getItems method which returns the data set - an ArrayList of your parcelable items. It also needs to have a setItems(...) method or a constructor taking a list of items as parameter.
Then your activity will look like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
gridView = (GridView) findViewById(R.id.gridview);
myAdapter = new MyAdapter(); // Create empty adapter.
if(savedInstanceState != null) {
ArrayList<MyItem> items = savedInstanceState.getParcelableArrayList("myAdapter");
myAdapter.setItems(items); // Load saved data if any.
}
gridView.setAdapter(myAdapter);
}
#Override
protected void onSaveInstanceState(Bundle state) {
super.onSaveInstanceState(state);
state.putParcelableArrayList("myAdapter", myAdapter.getItems());
}

How to maintain Android GridView position in all cases?

I'm new to Android and I'm trying to do the following task on my school project:
I have a grid view of movies which the user can scroll endlessly.
When the app starts I fetch the first 20 movies and each time the user scrolls to the bottom of the grid I execute an AsyncTask to fetch 20 more movies and add them to the Adapter.
When the user clicks on a movie he goes to a new child activity to see the movie details.
I'm having troubles maintaining the GridView's scroll position in the following cases:
When the user goes to the details activity and returns to the main activity of the movies.
When the user changes the device orientation.
And when dealing with theses 2 cases I also need to take in consideration that maybe the user scrolled a lot and had 100 movies in the adapter and when he goes back the activity start from the start with only the first 20 movies, so I would be able to scroll to his last position.
Can someone please tell me how can I give the best user experience in my project by not losing the user's scroll position at any case?
I don't know if this is the best practice, but in my case it is.
I decided to set my adapter as a global static variable, in this way I maintain the amount of data loaded via the API, and I don't need to perform a request for every time the user moves between activities.
For maintaining the scroll position I used the onItemClickListener when moving to the details activity and the savedInstanceState when changing orientation.
Here is my code for that:
//Static variables
private static MoviesAdapter mMoviesAdapter;
private static int mGridViewPosition = 0;
//Call this method when user clicks the back button
public static void ClearStaticData(){
mMoviesAdapter.clear();
mMoviesAdapter = null;
}
#Override
public void onSaveInstanceState(Bundle outState) {
int index = mGridView.getFirstVisiblePosition();
outState.putInt(GRID_VIEW_POSITION, index);
super.onSaveInstanceState(outState);
}
#Override
public View onCreateView(...) {
if (mMoviesAdapter == null) {
mMoviesAdapter = new MoviesAdapter(...);
} else {
RestoreGridPosition();
}
}
private void RestoreGridPosition(){
if(mGridViewPosition > 0 && mMoviesAdapter.getCount() >= mGridViewPosition)
mGridView.setSelection(mGridViewPosition);
}
Since I fill my adapter via API call, I think this is probably the best solution to save the data and not to perform requests every time.
Try not finishing mainActivity once a gridItem is clicked so when user navigates back to mainActivity (from detailsActivity) he will have all the data that was there before.
You can handle this situation with activity's lifecycle callbacks:
You can get currently visible GridView item's position like this:
int mCurrentPosition = gridview.getFirstVisiblePosition();
When an orientation change is occurring the activity is recreated and going through the following stages:
onSaveInstanceState
onRestoreInstanceState
You can then save the position before orientation change is happening and get it back when its being restored.
Save Your Activity State
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the user's current scroll state
savedInstanceState.putInt(STATE_POSITION, mCurrentPosition);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
Restore Your Activity State
public void onRestoreInstanceState(Bundle savedInstanceState) {
// Always call the superclass so it can restore the view hierarchy
super.onRestoreInstanceState(savedInstanceState);
// Restore state members from saved instance
mCurrentPosition = savedInstanceState.getInt(STATE_POSITION);
}
Here once you have the previous position you can move to the desired position in the gridView:
gridview.smoothScrollToPosition(int mCurrentPosition)
This is taken from android docs: Recreating an Activity
Scrolling gridView to position GridView scrolling stackoverflow

How to update view state in Android?

I need to refresh text in TextView (TextView with id) on configuration change. Android saves its state on configuration state because it has id. But I want to update text (because language may be changed).
I solved a similar problem in this way... add a static boolean to the class containing the view you want to refresh, and change this variable to true when opening settings... then, update your onResume method in this way:
#Override
public void onResume(){
super.onResume();
if (changed == true) {
this.recreate();
changed=false;
}
}
this will force recreation on the view.

Categories

Resources