I have been loading data from a server and presenting it in a listview or gridview but every time i pause the activity or minimize it, the data is reloaded when i go back to the said activity.
I know it is possible to save strings, integers or booleans at onSaveInstanceState but is there a way to save other data objects such as ArrayLists of objects, bearing in mind that some of the fragments are nested inside other fragments, which makes using setRetainInstance(true) unviable?
The short answer is yes, you can save anything into onSaveInstanceState as long as you make sure it implements Parcelable or Serializable.
The longer, but possibly better solution for your situation is to use the LoaderManager callbacks to load your stuff from the internet. This decouples your data from the activity life-cycle, freeing you from a lot of the pains of all that runtime changes entail.
Heres a link for the official android guide to handle this
http://developer.android.com/guide/topics/resources/runtime-changes.html
basically you save your data ( ArrayList of Objects) in the fragment and when the Activity is restored you retrieve the data from the fragment.
The Fragment is not destroyed in between Activity change states or during runtime changes.
This is how your Fragment should look like:
public class RetainedFragment extends Fragment {
// data object we want to retain
private ArrayList<YourObject> data;
// this method is only called once for this fragment
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// retain this fragment
setRetainInstance(true);
}
public void setData(ArrayList<YourObject> data) {
this.data = data;
}
public ArrayList<YourObject> getData() {
return data;
}
}
And your onDestroy() method:
#Override
public void onDestroy() {
super.onDestroy();
// store the data in the fragment
dataFragment.setData(collectMyLoadedData());
}
And your onCreate() method should be the same the only difference is how you load your data on this line:
dataFragment.setData(loadMyData());
For your nested fragment problem check this link http://inthecheesefactory.com/blog/fragment-state-saving-best-practices/en
If you want to save the data in fragment call the method setRetainInstance( )in your fragments,this way your fragment won't be destroyed during runtime changes,
you can save the custom objects by parceling it and writing to bundle in the onSaveInstanceStatecallback method of your activity and retrieve it in onCreate or OnRestart callbacks.
I hope this blog post may help you understand the things in detail
Related
I have one method(let's call it getData) in my fragment(let's call it List) which I call in onCreateView.
This method load some data from server and put it to some views.
In my fragment I have one button which open another fragment(Let's call it Detail), and when I go back to List onCreateView calling again and data start load again.
I tried to put getData into onCreate... and in fact it must works, and mustn't call getData again...
But getData works with views whick initialize only in onCreateView and another methods after onCreateView and all that methods always recalling when I return to List from Detail.
How do I make don't recall getData when I return to List from Detail.
I'm so sorry for my bad English and grammar mistakes.
Thanks.
first of All override onStop method by Ctrl+O
it will look like this
public class yourFragment extends Fragment{
boolean isReturned = false;
#Override
public void onStop() {
super.onStop();
isReturned = true;
}
public void getData(){
if(!isReturned){
//fetch data
}
}
I've recently developed a new pattern for storing fragment state through a retained fragment. Instead of saving things in onSaveInstanceState as such:
public class MyActivityOrFragment {
public void onSaveInstanceState(Bundle bundle) {
bundle.putInt("example", 123);
}
// plus restore in onCreate
}
I keep a state fragment like this:
public class MyActivityOrFragment {
public static class State extends Fragment {
int example = 123;
public void onCreate(...) {
...
setRetainInstance(true);
}
}
State state;
public void onCreate(Bundle ssi) {
if (ssi == null) {
state = new State();
getFragmentManager().beginTransaction().add(state, "state").commit();
} else {
state = (State) getFragmentManager().findByTag("state");
}
}
}
With a lot of state to keep this reduces so much boilerplate since I can just keep it in the state fragment and know it'll be retained automatically. However, is there any disadvantage to using the retained fragment instead of saving to the bundle as usually recommended? Is this too good to be true?
Per Android documentation.
With setRetainInstance() you can retain your fragment while the application is running.
Control whether a fragment instance is retained across Activity
re-creation (such as from a configuration change).
http://developer.android.com/reference/android/app/Fragment.html
But, it will not retain it if your Activity is destroyed. That's what onSaveInstanceState and onRestoreInstanceState are for.
As your activity begins to stop, the system calls
onSaveInstanceState() so your activity can save state information with
a collection of key-value pairs.
When your activity is recreated after it was previously destroyed, you
can recover your saved state from the Bundle that the system passes
your activity. Both the onCreate() and onRestoreInstanceState()
callback methods receive the same Bundle that contains the instance
state information.
http://developer.android.com/training/basics/activity-lifecycle/recreating.html
It is bad way use fragment then onSaveInstanceState(), becouse approach with fragment consume more memory.
I have a main fragment with a viewpager inside it. This viewpager has 2 pages (list fragments). When I start the activty, the main fragment is shown and I also show the first paged fragment. This paged fragment displays data from a db using AsyncTask.
In the main fragment I have:
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
onPageSelected(0);
}
#Override
public void onPageSelected(int position) {
Fragment fragment = (Fragment) pagerAdapter.instantiateItem(viewPager, position);
if (fragment instanceof IPagedFragment) {
((IPagedFragment) fragment).onShown(getActivity());
}
}
And the interface is:
public interface IPagedFragment {
void onShown(FragmentActivity activity);
}
The first issue I have is that I have to pass the activity as a parameter because when onShown gets called, the activity is still null.
Furthermore, the paged fragments use progressbar logic similar to the LoginActivity sample. I also get the following exception:
IllegalStateException: Fragment PagedFragment1{4201f758} not attached to Activity
at android.support.v4.app.Fragment.getResources(Fragment.java:620)
So what is the correct stage to start retrieving data from db once the paged fragment is fully available to the UI?
Issues like yours is the reason some developers are starting to question if fragments are really that good or useful.
Also "the correct" is debatable as you can do it in a variety of places and different developers will give you different answers, But let me try to supply you some useful info.
The attach/detach callbacks:
public void onAttach(Activity activity);
public void onDetach();
between those two methods any call to getActivity() will return the non-null activity the fragments is connected to. You can override them and use a private boolean isAttached to keep track of that call.
Also useful is the:
public void onActivityCreated (Bundle savedInstanceState)
this method is called AFTER the Activity.onCreate method. That is very important if you rely on some initialisation that happened there.
Also it's important to remember that on the moment the fragment transaction happens, the Fragment.onCreate happens after the Activity.onCreate and during rotation it happens before it.
As a general rule of thumb I use the Fragment.onStart() / Fragment.onStop() for getting/listening to data. On those calls, all the UI have been created, the fragment is attached to the activity and those callbacks don't get called if there's a dialog/popup (pause/resume does)
From the documentation:
public void onActivityCreated (Bundle savedInstanceState)
[...] tells the fragment when it is fully associated with the new activity instance.
source: http://developer.android.com/reference/android/app/Fragment.html#onActivityCreated(android.os.Bundle)
To get the reference of your activity, create a local object of fragmentActivity and get your activity reference as shown below.
private FragmentActivity fragmentActivity;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
fragmentActivity=activity;
}
i need to save a custom object that i use in a fragment so it will not be lost when the screen rotates (when the app calls onDestroy and then recalls onCreate)
now the normal way to do so is to implement Parcelable interface and save it to the bundle as a Parcelable object.
that is a very tedious way of doing things.
is there a way to just pass the object along as "putObject" method?
You can save your data in fragment, retained during a configuration change like in example.
Extend the Fragment class and declare references to your stateful
objects.
public class RetainedFragment extends Fragment {
// data object we want to retain
private MyDataObject data;
// this method is only called once for this fragment
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// retain this fragment
setRetainInstance(true);
}
.. getter and setter
}
Then use FragmentManager to add the fragment to the activity.
public class MyActivity extends Activity {
private RetainedFragment dataFragment;
#Override
public void onCreate(Bundle savedInstanceState) {
..
// find the retained fragment on activity restarts
FragmentManager fm = getFragmentManager();
dataFragment = (RetainedFragment) fm.findFragmentByTag(“data”);
// create the fragment and data the first time
if (dataFragment == null) {
// add the fragment
dataFragment = new DataFragment();
fm.beginTransaction().add(dataFragment, “data”).commit();
} else {
// available dataFragment.getData()
..
// save data in onDestroy dataFragment.setData(yourData);
The best way is to implement Parcelable (Faster).
Easier (not efficient) way is to implement Serializable and add the object into the bundle as serializable.
well searching i found no official way of doing so, so here are two "hacks" i found around the problem:
1)create a class that extends Application class, in it add an arrayList of objects.
inside onSaveInstanceState call:
getApplication().getObjectArray().add(YourObject);
save the Object index inside the bundle using putInt.
extract it inside the method onReturnestoreInstanceState.
2)my less favorite one:
android automatically saves the states of its views
therefor a way to save an object will be to create a view set its visibility to none so it wont show on the screen and then add each object we want to the view using the methods:
view.setTag(key,Object); or view.setTag(Object);
now inside onReturnestoreInstanceState get the view and extract the tags.
unfortunately i couldn't find a more simple way of saving an object
hope this one helps you out (in my app i ended up using the first method)
Sorry if this been asked before but I couldn't find an answer to my specific case. Also sorry that I'm new and a little stupid.
Problem:
I'm showing a dialog from a fragment and passing along a context in my constructor method because I need a context in my dialog to register for broadcastrecievers etc.
DialogFragment fragmentDialog = MyDialog.myConstructor(getActivity());
fragmentDialog.show(getFragmentManager(), "dialog");
Then in MyDialog class I store the context in a instance variable.
The problem arises when rotating the device and I get a nullPointerException when I try to use the context again in the dialog.
Can this be solved in some easy way?
If the device is rotated the Activity will be destroyed and recreated. So the Context you passed to your Fragment points on the Activity which was destroyed.
You could use setRetainInstance(true) in your Fragment. This way your Fragment will survive the recreation of the Activity.
To solve the NPE you have to pass the Context to the Fragment, if the Activity is recreated. Then the Context belongs to the new Activity.
In fact, without this update every line of code which points on the Activity like getActivity() or getFragmentManager() will lead in a NPE.
You get the NullPointerException because activites are destroyed and recreated when rotating the screen.
The SO post below gives more info...
https://stackoverflow.com/a/1673374/
Please be careful with the order of events if you rotate a FragmentActivity, because this can also be a source of NullPointerExceptions.
This is not documentated:
When the FragmentActivity is created the first time,
public class MyActivity extends FragmentActivity implements
MyFragment.OnFragmentInteractionListener {
private int var1;
private int var2;
#Override
protected void onCreate(Bundle savedInstanceState) {
//before
var1 = 3;
super.onCreate(Bundle savedInstanceState)
//after
var2 = 5;
}
//Interface Methods
public int getVar1() { return var1; }
public int getVar2() { return var2; }
}
both of the [before] and [after] code will be run before the fragments are attached and created. So, if you get the vars in the onCreate() call of the Fragment you get both vars. But when you rotate your device, the Activity is recreated from the savedInstanceState in the super call. Now, the fragments are reattached and created anew in this call! That means, this time the Methods of the Listener Interface are called before your [after] code. So, if you pass the Context of the activity to the fragment and get Information through the Interface like it is shown in: https://developer.android.com/training/basics/fragments/communicating.html
you get a NullPointerException for var2 because the interface methods are called from the fragments onCreate() onAttach() ... functions before the [after] code in the Activity's onCreate() is executed! So, take care that you set your Information the InterfaceFunctions are accessing before the super call.
Depending on what you're doing in your initialization you could consider creating a new class that extends Application and moving your initialization code into an overwridden onCreate method within that class.
public class MyApplicationClass extends Application {
#Override
public void onCreate() {
super.onCreate();
// TODO Put your application initialization code here.
}
}
And you are not stupid, even experts need help from time to time.