60

So I've learned that I need an empty constructor in order for my fragments not to crash on reinitialization. My problem is that I use lists of data for my fragments when they are initialized (at least some of them). So what would be a good way to start new fragments with a list of data. Should I in the OnCreate() make a getData method which gets the data from some other source or what would be a proper approach?

Feeding the bundle with data really wouldn't be a very good approach as I have a lot of data.

So let's take a case (I understand it tons better that way).

When a user clicks on a button the fragment is started. What I used to do was creating a new fragment this way:

    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    
    fragmentTransaction.replace(R.id.center_container, new DetailFragment(item));
    fragmentTransaction.addToBackStack(DETAIL_TAG);
    
    fragmentTransaction.commit();

Then in my fragment:

public DetailFragment(EventItem item) {
    mItem = item;
    mPlaces = Database.getContainerPlaces(getActivity()).getValidItems();
}

I can't give all the data to a bundle, so that wouldn't work. So what should I do?

A: Should I initialize the fragment with the empty constructor and then from my activity use setters to set the data directly in the fragment? However, won't I be missing data if the user presses home, Android closes the fragment and the user later returns?

B: Should I initialize the fragment with factory pattern and call setRetainInstance(true), give the fragment a key for identifying the data, and then letting the fragment fetch the data needed in onCreateView from some third source?

C: Should I just make an empty constructor and then in onCreate() fetch the data needed for the fragment?

It should be noted that the app is locked in portrait so the issue is primarily with maintaining the objects when Android closes and the user restarts.

bakoyaro
  • 2,550
  • 3
  • 36
  • 63
Warpzit
  • 27,966
  • 19
  • 103
  • 155
  • Why not just create a headless fragment and setReatinedInstance(true) and put a tag on fragment and when you start again then get the the fragment using tag from fragment manager. You can put all you data in headless fragment and just call getData. isn't it simple ! – AZ_ Feb 22 '14 at 07:07
  • @AZ_ except from I need another fragment to contain the views meaning two fragments which will only make the code less readable. – Warpzit Feb 22 '14 at 19:12
  • I think this will make your code more robust. Just declare an inner headless fragment setRetainedInstance(true) and you can also use it as a background worker thread, this is recommended solution by Google. I read somewhere in developer.android.com – AZ_ Feb 24 '14 at 01:20
  • Handling Runtime Changes http://developer.android.com/guide/topics/resources/runtime-changes.html – AZ_ Feb 24 '14 at 01:21
  • 1
    @AZ_ Yes but handling a thread and handling data is two different things. If I had some threading to handle I'd aggree with you, but if it's just raw data, the factory pattern is the way to go. This is also the way google does according to their own documentation: http://developer.android.com/guide/components/fragments.html – Warpzit Feb 24 '14 at 07:55

1 Answers1

74

So what would be a good way to start new fragments with a list of data.

Use the factory pattern and the "arguments" Bundle, such as:

package com.commonsware.empublite;

import android.os.Bundle;

public class SimpleContentFragment extends AbstractContentFragment {
  private static final String KEY_FILE="file";

  protected static SimpleContentFragment newInstance(String file) {
    SimpleContentFragment f=new SimpleContentFragment();

    Bundle args=new Bundle();

    args.putString(KEY_FILE, file);
    f.setArguments(args);

    return(f);
  }

  @Override
  String getPage() {
    return(getArguments().getString(KEY_FILE));
  }
}

If you are retaining your fragment instance, you should be able to get away with just using ordinary setters to put stuff in data members. The "arguments" Bundle is retained as part of configuration changes, so for non-retained instances, this is the way to ensure your setup data is retained if the user rotates the screen, etc.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • In my case I've got the screen fixed so the issue will only show when users go to home screen and Android start closing the fragments and the user gets back before whole the activity is closed. So if I want to feed my fragment with data from my activity I'll need to put the data somewhere where it is accessible for the fragment to get itself and then use factory pattern to get the data? – Warpzit May 29 '12 at 12:11
  • This just doesn't answer all my questions which follows from this approach. What happens when the activity is restarted? If I set the data directly with setters in the fragment will they then be retained when a fragment has been killed by Android? What do I get from setting setRetainInstance(true); if I got my screen fixed in portrait? – Warpzit May 29 '12 at 12:40
  • @Warpzit: This is all covered in my answer. – CommonsWare May 29 '12 at 14:46
  • @CommonsWare Yes I kinda get that, just chewing on all the details to make sure I don't misunderstand something and adds errors to our companies projects :) – Warpzit May 29 '12 at 14:48
  • @Warpzit: "I'll need to put the data somewhere where it is accessible for the fragment to get itself and then use factory pattern to get the data?" -- the factory pattern is specifically and exclusively for "So what would be a good way to start new fragments with a list of data". I now see that you edited the question. I don't have much advice, because "I can't give all the data to a bundle" means that your app is already broken IMHO. Just bear in mind that your "cons" for A are also valid for B, C, and every other scenario -- if Android terminates your process, your fragments go away. – CommonsWare May 29 '12 at 14:56
  • @CommonsWare thanks, I just need to get some examples of how to handle fragments with more than 10+ data fields. – Warpzit May 29 '12 at 15:05
  • 1
    @Warpzit: Your controller (fragment) retrieves the data from the model (database, `ContentProvider`, POJOs, whatever) and updates the views. I can't tell if this is B or C in your edited question. – CommonsWare May 29 '12 at 15:07
  • @CommonsWare It would be both B and C. But this is also what I was starting to understand. The fragment itself needs to get the data and update its own views and if it only need a certain peace it should use a key saved in bundle to identify which peace. Thanks a lot for your help. – Warpzit May 29 '12 at 15:19
  • 2
    @Warpzit: Yes, the fragment would receive from the hosting activity whatever identifying information it needs to know what to go get from the model. Some of that might be supplied on creation. If there are scenarios in which that data might be updated in an existing fragment, that's where `setRetainInstance()` starts coming in real handy... :-) – CommonsWare May 29 '12 at 15:21
  • @CommonsWare I've though about it but why is setRetainInstance() so important? Can't I just give the fragment a tag and then be able to find it again? I though it only was nice to use setRetainInstance() in order to persist objects on screen orientation change etc.? – Warpzit May 30 '12 at 07:34
  • If I set the activity to portrait only, wont this give the same behaviour? Or am I missing something important? – Warpzit May 30 '12 at 08:27
  • 1
    setRetainInstance() is for *all* configuration changes. There are more than orentation. – CommonsWare May 30 '12 at 13:11
  • @CommonsWare, thanks for your great solution, but I have a question. Setting the bundle using `f.setArguments()`, is not the same with overriding `onSaveInstanceState()` and adding there your data on the `Bundle`? – Ovidiu Latcu Jun 26 '12 at 07:40
  • @OvidiuLatcu You should start a new question instead of asking a question in another thread. I'd recommend you delete your comment and start a question instead. – Warpzit Jun 26 '12 at 08:35
  • 2
    @CommonsWare I don't think this answer is complete: is the file name pointing to a file where the list of data is actually serialized? Is the factory pattern used to retrieve this list. In the current code I see only implementation of Singleton. Maybe the suggestion of the answer is to retain single fragment instance and then use setters for the additional data? – Boris Strandjev Aug 10 '13 at 11:12
  • @BorisStrandjev: "is the file name pointing to a file where the list of data is actually serialized?" -- the particular hunk of code where this came from is an ebook reader; the file is a piece of static HTML. That being said, the pattern I recommend works fine for anything that can go into a `Bundle`. If you have something other than that, you will have to consider the retained instance approach, though I wouldn't expose setters. – CommonsWare Aug 10 '13 at 21:44
  • @CommonsWare How do you exchange data in the retained instance approach if not with setters? Also I still do not see where the factory pattern appears here. As for `anything that can go into a Bundle` - what is your opinion in making the thing `Parcelable`/`Serializable` if you have control over its type declaration? – Boris Strandjev Aug 11 '13 at 05:34
  • 1
    @BorisStrandjev: "How do you exchange data in the retained instance approach if not with setters?" -- pass it into the factory method (`newInstance()`), which can then provide the data to the instance. To be clear, I really meant "public setters" in my previous response. "what is your opinion in making the thing Parcelable/Serializable if you have control over its type declaration?" -- on the whole I try to avoid doing this. If you have additional concerns, please ask a fresh SO question, rather than extending a comment chain on year-old answer. – CommonsWare Aug 11 '13 at 11:29
  • @CommonsWare Don't get me wrong. I have learnt a huge pile of things from CommonsWare in SO, so many of your answers I have found useful. It is just that it seems to me this particular one is not exactly of the quality I am used to seeing from you. Actually the reason I am asking the questions here is that I hope with them your answer will get as profound as we are used to. – Boris Strandjev Aug 11 '13 at 15:28