Why use serialization to pass info to a fragment? - android

I have a list like fragment, and currently I am passing in info like so:
Fragment:
public void populate(Map<String, List<Book>> booksGroupedByType)
{
BookListAdapter bookListAdapter = new BookListAdapter(this.getActivity(), booksGroupedByType);
_lstBooks.setAdapter(bookListAdapter);
}
Activity:
private void populateBooksFragment()
{
Map<String, List<Book>> booksGroupedByType = _repository.getAllBooksGroupedByType();
BookListFragment bookListFragment = (BookListFragment) getFragment(R.id.fragBooks);
if (bookListFragment != null)
{
bookListFragment.populate(booksGroupedByType);
}
}
Then I felt it would be better if I could pass this information when creating the fragment, since we have no constructor available I looked up the method and found this:
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;
}
I tried to implement, but found my Map was not serializable as it was and needed more work. So my question is, why go through this? is there a disadvantage to using my original approach (populate), which would even be faster than serializing?
I thought perhaps my fragment will lose its data when rotated, but no, when rotating (in emulator) the list was kept intact.

Let's say you have some data obtained in time/resource consuming way. If you don't want to download them each time configuration changes (and activity is destroyed), you have to somehow persist them.
First option is to put data into the bundle, so it will be available for the fragment even after it is autorecreated by the system. It may work for simple types, but for arbitrary object it's usually not an option because of performance reasons (serialization/parcelization).
Second option would be retaining the fragment, by setting a flag in fragment's onCreate():
setRetainInstanceState(true)
In that case fragment won't be destroyed after configuration change, but just detached from activity being destroyed, and attached to the new one. Any data you will pass e.g. via setters will be available too.
See also: Understanding Fragment's setRetainInstance(boolean)

Related

Android / Dagger2 - How to add bundle arguments ? Inject fragment or use newInstance?

I'm looking to find a solution on how to inject fragment and pass arguments to it.
And i didn't find any proper solution because injecting the fragment means by the constructor which is not safe for states.
Is there any way to do this, without calling the newInstance pattern ?
Thanks,
Best.
Because Android manages the lifecycle of your Fragment, you should separate the problems of passing state into the Fragment through its bundle and injecting the Fragment with injectable deps. Usually, the best way to separate these is by providing a static factory method, which you might be calling the newInstance pattern.
public class YourFragment extends Fragment {
// Fragments must have public no-arg constructors that Android can call.
// Ideally, do not override the default Fragment constructor, but if you do
// you should definitely not take constructor parameters.
#Inject FieldOne fieldOne;
#Inject FieldTwo fieldTwo;
public static YourFragment newInstance(String arg1, int arg2) {
YourFragment yourFragment = new YourFragment();
Bundle bundle = new Bundle();
bundle.putString("arg1", arg1);
bundle.putInt("arg2", arg2);
yourFragment.setArguments(bundle);
return yourFragment;
}
#Override public void onAttach(Context context) {
// Inject here, now that the Fragment has an Activity.
// This happens automatically if you subclass DaggerFragment.
AndroidSupportInjection.inject(this);
}
#Override public void onCreate(Bundle bundle) {
// Now you can unpack the arguments/state from the Bundle and use them.
String arg1 = bundle.getString("arg1");
String arg2 = bundle.getInt("arg2");
// ...
}
}
Note that this is a different type of injection than you may be used to: Rather than getting a Fragment instance by injecting it, you are telling the Fragment to inject itself later once it has been attached to an Activity. This example uses dagger.android for that injection, which uses subcomponents and members-injection methods to inject #Inject-annotated fields and methods even when Android creates the Fragment instance outside of Dagger's control.
Also note that Bundle is a general key-value store; I've used "arg1" and "arg2" instead of coming up with more creative names, but you can use any String keys you'd like. See Bundle and its superclass BaseBundle to see all of the data types Bundle supports in its get and put methods. This Bundle is also useful for saving Fragment data; if your app is interrupted by a phone call and Android destroys your Activity to save memory, you can use onSaveInstanceState to put form field data into the Bundle and then restore that information in onCreate.
Finally, note that you don't need to create a static factory method like newInstance; you could also have your consumers create a new YourFragment() instance and pass in a particular Bundle design themselves. However, at that point the Bundle structure becomes a part of your API, which you may not want. By creating a static factory method (or Factory object or other structure), you allow the Bundle design to be an implementation detail of your Fragment, and provide a documented and well-kept structure for consumers to create new instances.

Communication objects between multiple fragments in ViewPager

I have 5 fragments in ViewPager used to fill business object with several fields step by step, in each step some of those fields will be set. I've read many articles about communication between fragments but I'm not feeling comfortable the way others preferred, so after thinking about HOW should I do this in my case, finally I start thinking to use singleton model object which all fragments can easily access to its fields and fill them in specific steps.
As I'm new to android I want to hear from experts about using singleton instead of passing data between fragments such as implemented interface(It seems its so complicated and hard to maintenance). Any advice will be helpful.
While singleton approach seems easy to implement and understand it is way not to best way to achieve what you need. One reason is that your model object or as you call it business object lives outside of your activity's context which can create hard to find bugs. E.g. in case when more than one instance of your activity class is created by system and both keep reference to your singleton. See how you lose track of your objects?
What I would do is
Make my model object to implement Parcelable you will hate it at the beginning but once you get use to it it will become your model's best friend
Since your model is parcelable now you can easily pass it between fragments, activities, and even save it in shared preferences. One important thing to note here when you pass your parcelable between fragment or activity it is like pass by value, i.e. every time new instance is created.
Set your fragment's argument or if it is already instantiated then get arguments and add your model. here is an example:
if a fragment is not active yet:
Bundle args = new Bundle();
args.putParcable("businessObject", yourBusinessObjectThatIsParcable);
yourFragment.setArguments(args);
Otherwise:
yourFragment.getArguments().putParcelable("businessObject", yourBusinessObjectThatIsParcable);
In your fragment perhaps in onCreateView method get your model object like this MyParcableObject mpo = (MyParcableObject)getArguments().getParcelable("businessObject") and use it set whatever data you want.
When you finish editing your object on button click or in onPause method updated your fragment's arguments same way getArguments().putParcelable("businessObject", mpo);
in your last page or last fragment you can pass your object to your activity, here is how to do it
Even though it looks cumbersome but it is a practice that you need to get used to as an android developer. You get lot more control when your model implements parcelable.
Another way to do what you need is thru Delegation Pattern but it is mostly used for callbacks even though you can pass objects as well.
I wouldn't recommend a global singleton. There are two main reasons:
By definition, a singleton limits your app to a single instance of the main business object. If you (or a designer, or your boss's boss's boss) ever decide to have multiple of these ViewPagers at a time, you will have to change your architecture anyways.
The "Android way of thinking" is to expect that your user may put your app in the background and use other apps before returning to your app. If the system decides to kill your app in the background, then your singleton memory object will be destroyed, and your user will have lost all of their progress. The correct Android way to save state is by keeping the state in an Activity or Fragment, saving it appropriately in onSaveInstanceState(), and restoring it in onCreate().
All of the Fragments in the ViewPager can get a reference to the parent Activity via a call to getActivity(). Or if your ViewPager is within a Fragment, then all of the Fragments can access the parent Fragment via a call to getParentFragment(). You can then cast the result to the appropriate class (or better yet, interface) and make method calls to pass data back and forth. Keep track of your business data in the parent Activity/Fragment. This way, you don't need a global singleton
For example,
public class MyParentFragment extends Fragment {
private String mPageOneData;
private int mPageTwoData;
private List<Date> mPageThreeData;
public void setPageOneData(String data) {
mPageOneData = data;
}
...
}
public class PageOneFragment extends Fragment {
private void sendDataToParent(String data) {
Fragment f = getParentFragment();
if (f != null && f instanceof MyParentFragment) {
MyParentFragment parent = (MyParentFragment) f;
f.setPageOneData(data);
}
}
}
you can save your data in onSaveInstanceState() event of the activity in case your process will go into the background.
you can restore your data in onCreate() event by using Bundle and getExtras().
you can save your data in application class and the data will still be there in case your process will go into the background.
i prefer the first option because you don't want to make a mess in the application class with all the data from different activities and fragments.
I hope i could help :)
Have you checkout EventBus?
I'm not sure if it is the best approach, specially when your question is too broad, however it will be cool with just 5 fragments.
Hope it helps
I suppose in your MainActivity there is a ViewPager, and FragmentOne will be one of the fragments inside the view pager. Here the MainActivity is communicating to the FragmentOne to refreshhis adapter. Hope is clear.
In your MainActivity add this interface:
public interface Updateable {
public void update();
}
Implement this interface in a fragment that needs to be updated, and write the code to notify the adapter inside the update method:
public class FragmentOne extends Fragment implements MainActivity.Updateable {
...
#Override
public void update() {
// YOUR CODE TO UPDATE HERE, FOR EXAMPLE, HERE I'M UPDATING THE ADAPTER
if ( adapter != null ) {
adapter.notifyDataSetChanged();
} else {
Log.d("LOG_TAG", "null");
}
}
...
}
Call the update method from the MainActivity when the fragment loads first. You can do this overriding the getItemPosition method in your PagerAdapter, like this:
#Override
public int getItemPosition(Object object) {
if ( object != null && object instanceof FragmentOne ) {
FragmentOne f = (FragmentOne) object;
f.update();
}
return super.getItemPosition(object);
}
Finally, you have to call notifyDataSetChanged() of your viewPager adapter. This will force the adapter of your viewpager to call the getItemPosition method.
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
int previousState;
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
}
#Override
public void onPageScrollStateChanged(int state) {
if (previousState == ViewPager.SCROLL_STATE_SETTLING && state == ViewPager.SCROLL_STATE_IDLE) {
if ( viewPagerAdapter.getItem(viewpager.getCurrentItem()) instanceof Pictures ) {
Log.d("LOG_TAG", "New Position=" + viewpager.getCurrentItem());
viewPagerAdapter.notifyDataSetChanged();
}
}
previousState = state;
}
});
Before choosing any option, keep in mind user can navigate or open any other app(s) so you lost your data.
You can use onSaveInstanceState but it will somehow difficult to maintain (as you said you are new in android). You can go with with singleton by using
Database - Use when you want to store maintain multiple records but you have to create a database getter/setter or use any ORM like RushOrm etc.
SharefPreference(preferably) - If you want to use single values.
In both cases you will create a singleton object and access its properties in your fragments.
make your objects parcelable and then pass it to other fragments using bundle. i.e bundle.putParcelable(obj) parcelable is very efficient and fast.
it should motivate you
http://www.developerphil.com/parcelable-vs-serializable/

What is the difference between 2 ways to set arguments of fragment

I want to pass a data object to Fragment, here are two ways to do this:
public class MyFragment extends Fragment {
private Serializable way1;
private Serializable way2;
public void setDataWay1(Serializable way1) {
this.way1 = way1;
}
public void setDataWay2(Serializable way2) {
Bundle data = new Bundle();
data.putSerializable("data", way2);
setArguments(data);
}
}
So, what is the difference between the 2 ways? Sometimes, way1 may cause NullPointerException,why? If I want to pass a OnClickListener to Fragment, what should I do?
While both methods can set the appropriate data to your fragment for first time initialization. Note that fragments will be recreated and destroyed by the system (for example on screen rotation). When that happens the system will not really call the setter way (method 1) hence, it will be a giant FAIL. Therefore, it is recommended to use the setArguments() way.

Parameterised constructor for a fragment in android

I have the following constructor inside a fragment:-
public PlaceDialogFragment(Place place, DisplayMetrics dm){
super();
this.mPlace = place;
this.mMetrics = dm;
}
I have also tried this:-
public static final DialogFragment newInstance(Place place, DisplayMetrics dm)
{
DialogFragment fragment = new DialogFragment();
Bundle bundle = new Bundle(2);
bundle.putParcelable("Place", place);
bundle.putLong("Metrics", dm);
fragment.setArguments(bundle);
return fragment ;
}
But There is an error on bundle.putLong("Metrics", dm) line
Here Place is a class which implements the Parceable interface
But i get an error saying:-
Avoid non-default constructors in fragments: use a default constructor plus Fragment#setArguments(Bundle) instead
Any suggestions how to resolve this?
The reason you should use default constructors and pass arguments as bundles is because when the system restores your fragment state, it's gonna call the default constructor and restore the bundle. If you get your parameters from the bundle, then you can restore the state correctly.
Using your current method, everything you do in your custom constructor will be lost when the fragment is recreated.
See this answer for an example.
Use setArguments instead, while transacting the fragment, pass the constructor params in bundle, then use them in fragment using getArguments()
How to use setArguments() and getArguments() methods in Fragments?
What's the point of setArguments?
http://developer.android.com/reference/android/app/Fragment.html
You added :
bundle.putLong("Metrics", dm);
Do like this:
bundle.putFloat("Metrics_density", dm.density);
Refer to DisplatMetrics documentation and add arguments separately, or add DisplayMetrics object as static in your Application memory, and use it from anywhere.
DisplayMetrics is not a Long object, not even parcelable, add relevant DisplayMetrics fields in bundle instead.
public static final PlaceDialogFragment newInstance(Place place, DisplayMetrics dm)
{
PlaceDialogFragment fragment = new DialogFragment();
Bundle bundle = new Bundle(2);
bundle.putParcelable("Place", place);
bundle.putFloat("Metrics_density", dm.density);
//bundle.putFloat("Metrics_other", dm.<other fields>);
fragment.setArguments(bundle);
return fragment ;
}
Note : Don't use public constructor with parameters.
The error message is right!
Avoid the use of Parameterized constructors to Fragment/Activity..
You can do "quick-fix" by going into Lint settings and excluding the rule + adding a default constructor. But quick fix is not the way. This will result in problem.
Consider this case, you just rotate the screen, then your fragment gets destroyed and recreated when you call super.onCreate(savedState) of your activity, which will call default constructor => this results in NullPointerException.
So respect the Android Lint, make use of setArguments() to pass the instance of Place. If Place is your model class, make it Parcelable
you can get arguments by calling getArguments() inside your fragment
Ideally, a fragment needs to reconstruct itself using only its arguments. A parameterised constructor does not work well for this as the parameters are lost in the case of a (for example) device orientation change (although you can mitigate this with a call to setRetainInstance).
Use a static method instead of a constructor to create your fragment.
e.g.
public static MyFragment newInstance() {
MyFragment f = new FragStateList_();
Bundle args = new Bundle();
args.putInt("someInt", someInt);
args.putString("someString", someString);
f.setArguments(args);
return f;
}
You should then include a default constructor so the system car re-create your fragment when it needs to.
In your updated question, you are attempting to place a DisplayMetrics objects into the bundle as a Long. The types are not compatible. Do not pass in the DisplayMetrics. Instead, try this in your fragment to get the DisplayMetrics object.
DisplayMetrics metrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);
Ideally, a fragment needs to reconstruct itself using only its arguments. A parameterised constructor does not work well for this as the parameters are lost in the case of a (for example) device orientation change (although you can mitigate this with a call to setRetainInstance).
Like Kuffs wrote, the proper way to do this is to have a static method that calls a (default) constructor and after it is initialized, it adds your custom values. You can place arguments in that method, for example:
public static PlaceDialogFragment newInstance(Place place, DisplayMetrics dm) {
PlaceDialogFragment f = new PlaceDialogFragment(); //alternatively new Fragment()
f.mPlace = place;
f.mMetrics = dm;
return f;
}
Then from you Activity, you call it like:
PlaceDialogFragment pdf = PlaceDialogFragment.newInstance(param1, param2);

Creating a Fragment: constructor vs newInstance()

I recently grew tired of constantly having to know String keys to pass arguments into Bundles when creating my Fragments. So I decided to make constructors for my Fragments that would take the parameters I wanted to set, and put those variables into the Bundles with the correct String keys, therefore eliminating the need for other Fragments and Activities needing to know those keys.
public ImageRotatorFragment() {
super();
Log.v(TAG, "ImageRotatorFragment()");
}
public ImageRotatorFragment(int imageResourceId) {
Log.v(TAG, "ImageRotatorFragment(int imageResourceId)");
// Get arguments passed in, if any
Bundle args = getArguments();
if (args == null) {
args = new Bundle();
}
// Add parameters to the argument bundle
args.putInt(KEY_ARG_IMAGE_RES_ID, imageResourceId);
setArguments(args);
}
And then I pull out those arguments like normal.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.v(TAG, "onCreate");
// Set incoming parameters
Bundle args = getArguments();
if (args != null) {
mImageResourceId = args.getInt(KEY_ARG_IMAGE_RES_ID, StaticData.getImageIds()[0]);
}
else {
// Default image resource to the first image
mImageResourceId = StaticData.getImageIds()[0];
}
}
However, Lint took issue with this, saying not to have subclasses of Fragment with constructors with other parameters, requiring me to use #SuppressLint("ValidFragment") to even run the app. The thing is, this code works perfectly fine. I can use ImageRotatorFragment(int imageResourceId) or the old school method ImageRotatorFragment() and call setArguments() manually on it. When Android needs to recreate the Fragment (orientation change or low memory), it calls the ImageRotatorFragment() constructor and then passes the same argument Bundle with my values, which get set correctly.
So I have been searching for the "suggested" approach and see a lot of examples using newInstance() to create Fragments with parameters, which seems to do the same thing my constructor is. So I made my own to test it, and it works just as flawlessly as before, minus Lint whining about it.
public static ImageRotatorFragment newInstance(int imageResourceId) {
Log.v(TAG, "newInstance(int imageResourceId)");
ImageRotatorFragment imageRotatorFragment = new ImageRotatorFragment();
// Get arguments passed in, if any
Bundle args = imageRotatorFragment.getArguments();
if (args == null) {
args = new Bundle();
}
// Add parameters to the argument bundle
args.putInt(KEY_ARG_IMAGE_RES_ID, imageResourceId);
imageRotatorFragment.setArguments(args);
return imageRotatorFragment;
}
I personally find that using constructors is a much more common practice than knowing to use newInstance() and passing parameters. I believe you can use this same constructor technique with Activities and Lint will not complain about it. So basically my question is, why does Google not want you to use constructors with parameters for Fragments?
My only guess is so you don't try to set an instance variable without using the Bundle, which won't get set when the Fragment gets recreated. By using a static newInstance() method, the compiler won't let you access an instance variable.
public ImageRotatorFragment(int imageResourceId) {
Log.v(TAG, "ImageRotatorFragment(int imageResourceId)");
mImageResourceId = imageResourceId;
}
I still don't feel like this is enough reason to disallow the use of parameters in constructors. Anyone else have insight into this?
I personally find that using constructors is a much more common practice than knowing to use newInstance() and passing parameters.
The factory method pattern is used fairly frequently in modern software development.
So basically my question is, why does Google not want you to use constructors with parameters for Fragments?
You answered your own question:
My only guess is so you don't try to set an instance variable without using the Bundle, which won't get set when the Fragment gets recreated.
Correct.
I still don't feel like this is enough reason to disallow the use of parameters in constructors.
You are welcome to your opinion. You are welcome to disable this Lint check, either on a per-constructor or per-workspace fashion.
Android only recreates fragments it kills using default constructor, so any initialization we do in additional constructors will be lost.Hence data will be lost.

Categories

Resources