Android - fragments - android

I'm starting using fragments in android but I have doubts: 1.- is necessary using this part of code (newInstance, setArguments)?, if so, why?
public static class DetailsFragment extends Fragment {
/**
* Create a new instance of DetailsFragment, initialized to
* show the text at 'index'.
*/
public static DetailsFragment newInstance(int index) {
DetailsFragment f = new DetailsFragment();
// Supply index input as an argument.
Bundle args = new Bundle();
args.putInt("index", index);
f.setArguments(args);
return f;
}
public int getShownIndex() {
return getArguments().getInt("index", 0);
}
http://developer.android.com/reference/android/os/Bundle.html
2.-if this code would have an OnCreate method, when will this be called?

1) Why is newInstance needed?
newInstance methods are needed because the framework encourages you to only ever have one constructor, taking no arguments. Having more than one constructor is somewhat frowned upon.
It needs this constructor to be public, so that it can recreate you Fragments when, for example, you undergo a configuration change. The same Bundle that you passed to setArguments on the original will be given to the copy too (as well as a savedInstanceState Bundle from onSaveInstanceState).
Doing this for all fragments, including ones that have no arguments leads to consistent usage of fragments and ease of maintenance. Namely, do make sure to always pass an arguments Bundle, even if it's empty, so that you don't have to check getArguments() for null.
2) onCreate in the Fragment lifecycle.
The Fragment lifecycle is officially called "nested" but when adding new fragments I like to think of it as "playing catch up". For example, if your Activity has started (has had its onStart called), your Fragment will get a rapid succession of onAttach, onCreate, onActivityCreated, onCreateView, onStart in essence catching up to the Activity's lifecycle. You can enable debugging with FragmentManager.enableDebugLogging(true); to track the lifecycle changes - look for moveto (forward direction) and movefrom (rewinding the cycle - destroying/detaching the fragment).
That said, onCreate is kinda easy to predict - since you're probably first creating the fragment after onCreate of the Activity, it would be called almost immediately when the transaction executes.
However, if you undergo a configuration change, the Fragments are destroyed with the old Activity and then created before the new Activity (it happens in that super.onCreate(savedInstanceState) call that you have in your Activity) - onCreate of all the fragments will be called before the Activity's onCreate returns. Once it returns, the Fragments' onActivityCreated will be called and from then on the fragments and activity will move in lockstep. This is all unless you have setRetainInstance(true), of course.
I hope this clears it up. I strongly suggest enabling debugging and just playing around with the fragments to get a better feeling for their lifecycles.

If you want to pass arguments to a fragment, the new instance method is necessary.
You pass arguments this way, because fragments can be in a detached state, and when they are re added may need to be reinstantiated. Android will then call the newInstance method again with the same arguments, and your fragment will work correctly.
If you passed your arguments to an onCreate method (which is called only when the fragment is first created), then when a fragment is reinstantiated, it wouldn't get those arguments.

Related

Memory leak with writeToParcel() called multiple times after onSaveInstanceState()

In some activity, I have to save MVC model as a parcelable. The parcelable is built according to the doc, which I've read more than enough (but who knows, I obviously could have missed something). There is a leak in this activity, but I'm struggling to understand its cause. The SO question Communication objects between multiple fragments in ViewPager was interesting but my code was already following guidelines from the answer.
The activity own a viewpager with around 60 fragments inside (but up to 350). The model is passed from the activity to all fragments, and user actions in the fragments are saved into the model.
Whenever I pause my activity, onSaveInstanceState is triggered once, and immediately after multiple triggers of my parcelable's writeToParcel method. The number of triggers depends on the number of Fragments ever loaded in the viewpager + 1. So at activity startup, if I turn the emulator off and back on, writeToParcel is called 3 times (only 1st and 2nd fragment are loaded), if I swipe once right and do it again, it is called 4 times (the 2nd is showing and the 3rd is loaded), if I setExtPosition() on the adapter and go to 10th fragment, writeToParcel is called 7 times (9th, 10th and 11h are loaded).
Of course if my user swipe every fragment, it will eventually get an ugly TransactionTooLargeException, which brings me here.
Here is some code. There may be a ton of code/concept improvements here, and any tips is very welcome, but my main problem is this dirty little leak I've found.
In my activity:
#Override
public void onSaveInstanceState (Bundle outState) {
outState.putParcelable("model", myParcelable);
super.onSaveInstanceState(outState);
}
In my fragment:
public static MyFragment newInstance(Model model) {
MyFragment fragment = new MyFragment();
Bundle args = new Bundle();
args.putParcelable(KEY_MODEL, model);
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
mModel = args.getParcelable(KEY_MODEL);
}
In my parcelable model:
#Override
public void writeToParcel(Parcel dest, int flags) {
int startSize = dest.dataSize();
dest.writeString(foo); //one of these string is supposed to be null
dest.writeString(bar);
dest.writeString(foobar);
dest.writeByte((byte) (isMyObjectTrue ? 1 : 0));
dest.writeList(someStringList);
dest.writeList(someIntegerList);
dest.writeBundle(someBundle); //I use a Bundle to save a Map<String,String>
int endSize = dest.dataSize();
}
I ran the debugger inside the writeToParcel() method, and I was surprised to see that startSize is never 0. Is it normal ?
I searched throughout my code, and putParcelable() or any writing method with parcelable in its name is only called in this activity and in the fragment newInstance().
How can I find the cause of this weird exponential behaviour ?
PS: of course feel free to ask for more code.
EDIT
I've implemented the solution advised by #Ben P., and the problem have improved a lot, but is not totally solved. My activity implements an interface which now has a getModel() method called in onAttach(), and a setUserInput(char userInput) I use to update the model from the fragment. The fragment's newInstance() method don't save the model anymore.
MyFragment
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
callBack = (MyInterface) context; //callBack is a class field
mModel = callBack.getModel(); //mModel too
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement MyInterface");
}
}
This turned the exponential problem into a linear problem which is obviously better but still a problem.
Now, writeToParcel() is only called once, but the total parcel size is still growing with the number of item loaded. My model takes around 3kb inside the parcel (+/-10% depending on the number of inputs), as measured by endSize-startSize.
How can I know where the growth comes from ?
Before I get into your problem specifically, I want to point out that the Bundle passed to setArguments() is part of the fragment's instance state. Every time a fragment is destroyed and recreated, these arguments need to be persisted. So anything you put into that Bundle has the potential to be parceled and unparceled during configuration changes.
The activity own a viewpager with around 60 fragments inside (but up to 350). The model is passed from the activity to all fragments, and user actions in the fragments are saved into the model.
This sounds like you have one single Model object that all fragments share. If this is the case, I recommend not passing the model object to each fragment as part of its arguments Bundle. Doing this will cause tremendous duplication when instance state is saved and restored. Instead, I'd expose a method in your Activity (something like getModel()) and then call that from your fragments to retrieve the model instance.
On the other hand, it also sounds like maybe you're only starting with the same Model object, and that each fragment can mutate it in some way. This would mean that you do have to save something to instance state for each fragment... but it's possible you can optimize here. Rather than saving and restoring the entire Model object, perhaps you could just store the diffs. That is, if fragment #1 changes the model's name, and fragment #2 changes the model's value, then you could have fragment #1 only save the new name and have fragment #2 only save the new value. Doing this instead of saving two extra copies of the model object could potentially amount to huge savings.

Why use bundle to pass data to fragment?

I have a fragment that is always visible. I don't understand why I should use bundles to pass data to it from activity.
Most of the questions here recommend this method of passing data:
Bundle bundle=new Bundle();
bundle.putString("name", "From Activity");
Fragmentclass fragobj=new Fragmentclass();
fragobj.setArguments(bundle);
I prefer creating Fragment object in OnCreate function of activity and then use this object to display fragment(FragmentTransaction.add). As I have refence to this fragment I can create create function showName() in it and call it from activity like that:
myFragment.showName("name");
Is there anything wrong with this approach?
The Android documentation states:
Every fragment must have an empty constructor, so it can be instantiated when restoring its activity's state. It is strongly recommended that subclasses do not have other constructors with parameters, since these constructors will not be called when the fragment is re-instantiated; instead, arguments can be supplied by the caller with setArguments(Bundle) and later retrieved by the Fragment with getArguments().
That's why it's better to use a bundle and set the parameters of the Fragment this way, it's easier for the system to restore its values when the fragment is re-instantiated.
Now, I wouldn't use myFragment.showName("name"); because you don't know if the lifecycle of the fragment has already finished (attached to the activity and inflated the views), so instead, I would call the showName("name") in the onActivityCreated or onViewCreated callbacks.
Applications should generally not implement a constructor. The first place application code can run where the fragment is ready to be used is in onAttach(Activity), the point where the fragment is actually associated with its activity. Some applications may also want to implement onInflate(Activity, AttributeSet, Bundle) to retrieve attributes from a layout resource, though should take care here because this happens for the fragment is attached to its activity.
There's nothing wrong with this approach for setting one off data values, you just need to be careful to make sure that the view that you want to set your name on actually exists at the point that the showName method is called.
Part of the reason that using Bundles to pass information is popular is that they can hold all types of data using keys and also they can easily be used to pass view states around during device rotation. Ultimately it's a matter of preference and exactly what your use case is.
When app is in background, the Fragment can recreated (eg: by change the theme (light/dark), language, ...).
So if you dont pass data use Bundle to Fragment, your Fragment will not have this data when it recreated

Setting Fragment arguments from Activity

I'm wondering if calling setArguments on a Fragment immediately after its instantiation creates any problems.
For example, say we have the following snippet:
Fragment myFragment = new CustomFragment();
Bundle args = new Bundle();
args.putBoolean("amIAnArg", true);
myFragment.setArguments(args);
This code seems to work fine, although it looks like the code should create a race condition since a Fragment's arguments can only be set before the onAttach method is called.
Are there any issues with setting a Fragment's arguments in this way?
Just like an Activity, Fragments have a specific lifecycle, and are not "created" like simple Java objects. When you commit a FragmentTransaction, it's asynchronous and isn't immediately attached or created. It's queued on the main thread to occur at a later time. Only then will it go through its lifecycle methods (e.g. onCreate(), onAttach()).
You should set the arguments this way, and should do so before committing the FragmentTransaction -- however, you could technically do it right after committing the transaction with no ill effects. As others have stated, what you're doing is the suggested newInstance() factory method for fragments [1]. For example:
private static final String ARG_IS_ARG = "is_arg";
public static CustomFragment newInstance(boolean isArg) {
CustomFragment result = new CustomFragment();
Bundle args = new Bundle();
args.putBoolean(ARG_IS_ARG, isArg);
result.setArguments(args);
return result;
}
[1] http://developer.android.com/reference/android/app/Fragment.html
There should be no problem. I am working on a project right now that uses this exact format in several spots.
This format is in the Android Developers example project as well (find 'Arguments'):
http://developer.android.com/reference/android/app/Fragment.html
Similar to what #kcoppock said, in most cases you will be instantiating the Fragment on the UI thread, and then Android queues up the arguments you pass to the Fragment on the UI thread as well. There's no race conditions because the operations take place at different times on the same thread.
For more information, check out this blog post on Activities and Fragments: http://www.zerotohired.com/2015/02/passing-data-between-activities-and-fragments-in-android.

How can getArguments() be called in onCreate() of a Fragment..?

Can someone explain how setArguments() works? I don't get it. I get how to use it, but I don't really understand how the code below can actually work:
public class MyDialogFragment extends DialogFragment{
public static MyDialogFragment newInstance(String test) {
MyDialogFragment f = new MyDialogFragment();
Bundle args = new Bundle();
args.putString("test", test);
f.setArguments(args);
return f;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("DEBUG", getArguments().getString("test")); // This actually works!
}
}
I would have thought that onCreate() got called before the call to setArguments(), but clearly it does not. Is this the way it is supposed to work, or is it just pure luck that the call to setArguments() gets executed before onCreate() on my particular device? Could the opposite happen under different circumstances (faster/slower/different device, etc.)?
Maybe someone can point me to an article that describes how the flow of events for this works? I don't see where onCreate() would get called in the code above, unless it is called asynchronously, which to me sounds like it would be risky to rely on getArguments() inside onCreate()...
Your newInstance() is a factory method you call yourself. It creates the Fragment object and sets the arguments on it.
The created fragment object is then passed to a fragment transaction which eventually makes the fragment lifecycle callbacks such as onCreate() to be invoked at appropriate times.
In case the framework needs to recreate your fragment e.g. due to an orientation change, it will use the no-arg constructor of the fragment and retain the arguments you have set on the object. newInstance() and such are useful when the fragment is created for the first time.
For documentation, the Fragment class documentation is a good starting point.
onCreate() is a method that is only called later by the framework, after the Fragment has been attached to the Activity, as you can see in the Fragment Lifecycle.

What's the point of setArguments?

Hi I was looking at the following Fragments example on the android site.
http://developer.android.com/guide/components/fragments.html#Example
I would like to know why certain methods are performed.
Why for instance, in the detailsFragment is the following method performed:
public static DetailsFragment newInstance(int index) {
DetailsFragment f = new DetailsFragment();
// Supply index input as an argument.
Bundle args = new Bundle();
args.putInt("index", index);
f.setArguments(args);
return f;
}
Could you not also simply instantiate the DetailsFragment and use a setter method to set index instead. Bypassing the whole setArguments.
What's the point of using setArguments in the first place? Could you not just use setters and getters?
You can use getters and setters, but by passing in a bundle you don't need to write that code, since it's already there. Also, I believe that these arguments are automatically passed in again if the screen orientation changes, which also makes life easier.
Essentially, setArguments and getArguments is just a design pattern that Google suggests you follow:
Every fragment must have an empty constructor, so it can be
instantiated when restoring its activity's state. It is strongly
recommended that subclasses do not have other constructors with
parameters, since these constructors will not be called when the
fragment is re-instantiated; instead, arguments can be supplied by the
caller with setArguments(Bundle) and later retrieved by the Fragment
with getArguments().
http://developer.android.com/reference/android/app/Fragment.html
I take that to include setters which are needed for your Fragment to operate as well. Then again - there's nothing forcing you to do it this way, and as you know - it's not the only way things could be made to work.
Just to add to Matthew's answer: he quoted correctly that Fragments need to have an empty constructor, so that the framework can re-instantiate them when needed.
It is fine to use getters and setters, but as the framework may destroy and re-create your Fragment, you must ensure to not lose those parameters.
This must be done via Fragment.onSaveInstanceState(). The saved stated will be passed back to you as the parameter savedInstanceState in Fragment.onCreate(), Fragment.onCreateView() and several other methods.
Using Fragment.setArguments() is (in most cases, I assume) easier, at the framework will automatically preserve the arguments and thus will do most of the work for you.
Setters may be the way to go for parameters you supply to the Fragment initially and which the fragment may adjust itself over time. Dealing with the savedInstanceState alone may be easier in this case than dealing with savedInstanceState and arguments - where you have to make a decision which is the valid parameter.
public void setArguments (Bundle args)
Supply the construction arguments for this fragment. This can only be
called before the fragment has been attached to its activity; that is,
you should call it immediately after constructing the fragment. The
arguments supplied here will be retained across fragment destroy and
creation (may be the text in bold was missing from official
documentation previously)
Fragments.setArguments(Bundle args)
In addition setters can be misused. If updateSomeOtherStuff() will change some view this will crash.
public class MyFragment extends Fragment {
void setData(someData){
this.someData = someData;
updateSomeOtherStuff()
}
}
If one passing in a bundle it is not possible to misuse the setter and you will always know that this will be set within the lifecycle methods.

Categories

Resources