Keep ListView scroll position and data when popping fragment stack - android

Here's my situation, I have 1 Activity, and 2 Fragments. When the Activity is started it loads a Fragment, let's call it list, into the view (in a FrameLayout filling the screen). List simply lists blogs posts which are retreived via an async HTTP call to out web api. When a blog is clicked in list is runs this method in the Activity
public void loadBlog(FragmentBlog.Blog theBlog) {
blog = theBlog;
FragmentBlogDetails d = new FragmentBlogDetails();
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_BLOG, theBlog);
d.setArguments(bundle);
fragmentManager.beginTransaction().replace(R.id.container, d).addToBackStack("Detail").commit();
}
Then the blog loads up and is displayed fine. Now, once I press the back key, my list Fragment calls onCreateView again and I have to run the async call again to populate my list, which reset the scroll to the top too. How can I keep my data for needing to be fetched again, and keep the view so the scroll stays where it is?
Here's my onCreateView method in the list Fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d("FragmentBlog", "onCreateView");
View view = inflater.inflate(R.layout.fragment_blog, container, false);
list = (ListView) view.findViewById(R.id.the_list);
list.setOnItemClickListener(this);
if (baa != null) {
list.setAdapter(baa);
}
text = (TextView) view.findViewById(R.id.my_text_view);
pb = (ProgressBar) view.findViewById(R.id.progressBar);
asyncForBlogs();
return view;
}
If you need any other bits of code let me know and I'll post them.

You need to save index of last item user clicked. Define a index variable in your fragment and when users clicked on an item store item index in it. Then save data in onSaveInstanceState Call back. You can save other data like this. But make sure the data you put inside bundle is not so big. Do not put any images or videos here!
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt("index",index);
super.onSaveInstanceState(outState);
}
after saving your data you can access it in onCreate method. like this
public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState != null) {
index = savedInstanceState.getInt("index");
listView.smoothScrollToPosition(index);
}
}
smoothScrollToPosition scroll your list view to the last place you were been.

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.
}

Android spinner acting weird on rotate

I have a fragement with various Spinners in it. To have these spinners display an initial (non selectable) value I'm using a custom arrayAdapter (SpinnerHintAdapter). The only important thing this class does is override the getCount() so the last item of the selection-array isn't displayed, this is where the initial value is stored.
This all works fine untill you rotate the device, then the spinners are set on the last normal value of the list for some reason, even though the Fragment class still thinks it's set on the initial value.
Anyone any clue why this happens and / or how to solve this problem?
Code samples:
fragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_free_top_up, container, false);
Spinner pet = (Spinner) rootView.findViewById(R.id.pet);
SpinnerHintAdapter<CharSequence> petAdapter = SpinnerHintAdapter.createFromResource(getActivity(),
R.array.pet_array, android.R.layout.simple_spinner_item);
petAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
pet.setAdapter(petAdapter);
pet.setSelection(pet.getCount());
return rootView;
}
SpinnerHintAdapter:
#Override
public int getCount() {
int count = super.getCount();
// The last item will be the hint.
return count > 0 ? count - 1 : count;
}
example string-array
<string-array name="pet_array">
<item>Yes</item>
<item>No</item>
<item>(initial value)</item>
</string-array>
The activity is re-created when you rotate the device. You need to override onSavedInstanceState function, save your data in a Bundle and then use that data in onCreate again.
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//save your data
}
and then use that in your onCreate to restore your spinner.
Haven't found an answer to this specific problem but used another method to display an initial value which can be found here: https://stackoverflow.com/a/12221309/3453217
Any clarification as to what went wrong with my first attempt is still welcome.

ListView position is always retained when changing orientation in a Fragment

Now, I'm sure this will sound weird to many, but I have a problem with my ListView always retaining its position when rotating my device. It's not that this is a particularly bad behavior (it's what my goal is eventually, to have it remember the ListView position when a user rotates their device), it's more so that there doesn't seem to be a reason why it's retaining the position. I never told it to do that! Even when I try to force a change of my custom adapter on the ListView, it updates the information inside (the list items) but still retains the position of the element.
After further tests, if I try to make a delayed Runnable (about 300ms delay), I can successfully change the ArrayAdapter and the position resets to the top as expected.
Why is it forcing the ListView to change position during the creation of the Fragment (during onCreateView, onActivityCreated, etc)?
Here's some code:
public class MainFragment extends Fragment {
ListView list;
ArrayList<String> skinslist;
ArrayList<File> files;
ArrayList<Integer> heights;
File directory;
String[] listitems;
LinearLayout skin_list_error;
SkinListAdapter adapter;
SwipeRefreshLayout swipeLayout;
View view;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
listitems = getResources().getStringArray(R.array.TestStrings);
list = (ListView) view.findViewById(R.id.skin_list);
skin_list_error = (LinearLayout) view.findViewById(R.id.skin_list_error);
//Initialize list items
if (isExternalStorageWritable() && isExternalStorageReadable()) {
directory = new File(Environment.getExternalStorageDirectory());
//File directory = new File(getActivity().getFilesDir(), "Mine");
directory.mkdir();
CreateNoMediaFile.CreateNoMedia(directory);
File[] filedirectory = directory.listFiles();
Arrays.sort(filedirectory, new Comparator<File>(){
public int compare(File f1, File f2)
{
return Long.valueOf(f2.lastModified()).compareTo(f1.lastModified());
} });
skinslist = new ArrayList<String>();
files = new ArrayList<File>();
heights = new ArrayList<Integer>();
for (File f : filedirectory) {
String filename = f.getName();
if (filename.endsWith(".png")) {
skinslist.add(f.getName().substring(0, filename.length() - 4));
files.add(f);
}
}
listitems = skinslist.toArray(new String[skinslist.size()]);
adapter = new SkinListAdapter(this.getActivity(), listitems, files, heights);
list.setAdapter(adapter);
} else {
Toast.makeText(getActivity(), "Error: Couldn't access phone storage", Toast.LENGTH_LONG).show();
list.setVisibility(ListView.GONE);
skin_list_error.setVisibility(LinearLayout.VISIBLE);
}
//Finish initializing list items
swipeLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipe_container);
swipeLayout.setOnRefreshListener(new OnRefreshListener() {
#Override
public void onRefresh() {
refreshSkins(false);
}
});
swipeLayout.setColorScheme(R.color.refresh_initial_color,
R.color.refresh_initial_color,
R.color.refresh_initial_color,
R.color.refresh_initial_color);
}
Here's where the view is inflated:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
view = inflater.inflate(R.layout.skinlist, container, false);
return view;
}
FragmentManager saves and loads fragment's view state in its moveToState() method. (Search for saveFragmentViewState(f) and f.restoreViewState()).
saveFragmentViewState() eventually calls View.onSaveInstanceState() for all views in hierarchy, so it saves ListView scroll positions, EditText content and many more.
Update: corresponding documentation (it's about Activity, but Fragment lifecycle is directed by Activity lifecycle):
However, even if you do nothing and do not implement onSaveInstanceState(), some of the activity state is restored by the Activity class's default implementation of onSaveInstanceState(). Specifically, the default implementation calls the corresponding onSaveInstanceState() method for every View in the layout, which allows each view to provide information about itself that should be saved. Almost every widget in the Android framework implements this method as appropriate, such that any visible changes to the UI are automatically saved and restored when your activity is recreated. For example, the EditText widget saves any text entered by the user and the CheckBox widget saves whether it's checked or not. The only work required by you is to provide a unique ID (with the android:id attribute) for each widget you want to save its state. If a widget does not have an ID, then the system cannot save its state.

Updating an Android Fragment's ListView from the background

I've been searching for the past few days, but have had no success, so I'm posting this question in the hopes someone can answer it.
Setup (Using android-support-library-v4, revision 12):
I have 1 Main Activity with a ViewPager.
I have 1 Fragment Class, called DataFragmentClass which consists of a ListView.
This fragment class loads data off the internet asynchronously and displays it in the listview. However, this fragment class can load two types of data, called DataTypeA and DataTypeB.
What I do is create two instances of this fragment in my main activity like so:
Fragment[] mFragmentList = new Fragment[2];
mFragmentList[0] = DataFragmentClass.newInstance(Modes.DataTypeA);
mFragmentList[1] = DataFragmentClass.newInstance(Modes.DataTypeB);
I then pass this array to my ViewPager, which works fine.
After being created, the Fragments will begin to async load the data:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.genericlistview, container, false);
DataProvider mDataProvider = new DataProvider(this.getActivity());
mDataProvider.addListener(mDataResponseAdapter);
return view;
}
The first data load everything is fine - DataTypeA will load in Fragment1 and DataTypeB will load in Fragment2.
However, any subsequent loads result in the data 'spilling over'. All the data (Type A and B) is now loaded in Fragment2.
I've narrowed the problem down to the ListView updates.
What I do is:
Runnable updateListView = new Runnable(){
#Override
public void run() {
adapter.add(myNewData);
adapter.notifyDataSetChanged();
}
};
DataFragmentClass.this.getActivity().runOnUiThread(updateListView);
I think something goes wrong at these points:
When posting the runnable on the UI thread.
When the remote server returns the data.
Any suggestions at all would be really helpful. Thanks!

Handling orientation change with loaders and array adapters

I am using loaders to load data into a gridview . First let me show you some code:-
The Fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle icic){
View v=inflater.inflate(R.layout.thegrid, container, false);
final GridView gv = (GridView)v.findViewById(
R.id.thegridview);
gv.setAdapter(upAd);//upAd is the array adapter initialized in oncreate of the fragment
gv.setOnItemClickListener(this);
Bundle args=new Bundle();
args.putString("page", String.valueOf(page));
getLoaderManager().initLoader(1, args, this);
return v;
}
#Override
public Loader<ArrayList<HashMap<String, String>>> onCreateLoader(int loaderId,Bundle thebundle) {
if(loaderId==1){
String page=thebundle.getString("page");
return new MyLoader(getActivity().getApplicationContext(),page);
}
return null;
}
#Override
public void onLoadFinished(Loader<ArrayList<HashMap<String, String>>> theLoader,ArrayList<HashMap<String, String>> data) {
upAd.addAll(data);
}
Now when the fragment is displayed , it shows a grid of 10 elements downloaded.Now when the screen orientation is changed, the loader behaves well and does not download the data again but it adds the same data again to the grid.. so i end up having a grid of 20 elements the same 10 repeated twice. Well i can call :-
upAd.clear()
in onLoadFinished but i want to avoid that.Could someone tell me what would I be doing wrong here.
You have 20 items because ArrayList holding data in upAd already has 10 items from past. Then you are calling upAd.addAll(data);. That means you are adding another 10 identical items to that array list. So the solution is to clear adapter.
use this line in your Activity declaration in menifiest file.
android:configChanges="orientation|keyboardHidden"

Categories

Resources