This week's giveaway is in the Android forum.
We're giving away four copies of Android Security Essentials Live Lessons and have Godfrey Nolan on-line!
See this thread for details.
The moose likes Android and the fly likes Dynamic Fragments: How can I access them when they are recreated from saved state? Big Moose Saloon
  Search | Java FAQ | Recent Topics | Flagged Topics | Hot Topics | Zero Replies
Register / Login


Win a copy of Android Security Essentials Live Lessons this week in the Android forum!
JavaRanch » Java Forums » Mobile » Android
Bookmark "Dynamic Fragments: How can I access them when they are recreated from saved state?" Watch "Dynamic Fragments: How can I access them when they are recreated from saved state?" New topic
Author

Dynamic Fragments: How can I access them when they are recreated from saved state?

Steve Luke
Bartender

Joined: Jan 28, 2003
Posts: 4167
    
  21

I am a little frustrated after yesterday's coding session (which spilled into today) - something that should have been a quick fix related to lifecycles ended up taking all day and needed an ugly workaround to fix. I am posting it here in hopes someone has a better answer. I don't really expect an answer, but I found nothing online related to this stuff so I wanted to get my findings up, maybe show some debugging logic, and perhaps get some feedback. Prepare for a long post :-)

I have a FragmentActivity that hosts several Fragmetns. Whenever the activity needs to be recreated - like when the device is rotated - the Fragments are created twice. One instance is created in the Activity - I have a reference to it and can pass info to it. The other one is not created in my code - it is created by the FragmentActivity's super class. I have no reference to this second instance of the Fragment and the methods I have found to get it seem fragile (see below). Unfortunately it is this second instance that I don't have access to is what gets displayed and has a meaningful life cycle (its onCreate() and onResume() are called, and it is displayed. The instance created by the Activity is not displayed.

By debugging I learned that the problem is this: When the Activity is destroyed, the state is saved. This state is passed to the onCreate() method of the re-made Activity as a Bundle. Typically you pass it to the super class. The super class re-builds the view hierarchy and displays it. This sounds all pretty good - except that there is data that the Activity has that it needs to pass to the Fragment. Not just data either - since only the Activity can be used as the onclick target in XML, click events also have to be passed to fragments for handling. Since I don't have access to the Fragment instances that are actually used, I can't pass the data or events from the Activity to the Fragment.

The kludgey fix I made was to not pass the saved state to the super class, I simply pass null to super.onCreate(). This ended up making the re-creating the views much faster and gave me access to instances of everything that is actually used. But it seems wrong. What seems correct is to somehow use the state that was saved to do what I need - rather than recreating it and rather than fighting what super.onCreated(savedState) is doing. I just haven't found a way of doing that yet.

So if there is a question in this mess, it is this: How do I use the fragments that get created in response to the saved instance state? Any input would be much appreciated. How do you go about taking advantage of the saved state instead of throwing it away?

The code below is simpler than my code (doesn't require comms between Activity and Fragment) but shows the problem. Here is the example FragmentActivity class:

It's real basic. I have an XML file whose base element is a ViewPager with an id pager and whose only child is a PagerTitleStrip. In the onCreate() method, I create a new PagerAdapter a new StatusFragment, and add the fragment to the adapter. Here is the code for my adapter:

Really the only difference between this and the real one is the real one holds a list of fragrments and names.

The example StatusFragment is this:

It logs its own 'toString()' in each of the lifecycle methods to show which instances are created and progress through the lifecycle. It also has a layout it inflates which is unimportant. You run this and get the LogCat output:
On first running LogCat wrote:
D/Status(457): Being created as StatusFragment{40524db8}
D/Status(457): Creating as StatusFragment{40524db8 ...}
D/Status(457): Resuming as StatusFragment{40524db8 ...}

But when you force the activity to be recreated:
After rotating to landscape LogCat wrote:
D/Status(457): Being created as StatusFragment{4052fa60}
D/Status(457): Being created as StatusFragment{40533570}
D/Status(457): Creating as StatusFragment{4052fa60 ...}
D/Status(457): Resuming as StatusFragment{4052fa60 ...}

You can see that when you first launch the app, one instance is created, and when I rotate the screen two are. The one that gets into the onCreate() method and onResume() method is not the same one I create in the activity, and that is the problem. What if I had a value I needed to pass to the fragment, how would I do so?

So there are two improvements I need to make. One, I want to make sure just one copy of the fragments are created to reduce resource and loading time. Second I want to have access to the fragments which are actually used. The simplest approach is to just pass null to the super.onCreate() method in the activity, so it has no previous state to restore:

The problem I see with that is it throws away all the saved state. Seems a waste, I am sure I would be losing something in the bargain. To be honest, I haven't figured out what yet - the state of which fragment is visible is maintained even when I pass null to super.onCreate(), the rotation/reload seems to run faster, and I can take advantage of some other optimizations since I control how the instances are created.

But to try to 'do things right' and not throw the saved state out I hacked around a bit to see how I could take advantage of the saved state. The fragments being created by the parent activity make their way into the fragment manager, which is why they become visible. I figured I could take advantage of that. There are three ways to get a Fragment from the FragmentManager that I know of.
  • 1. fragManager.findFragmentById() method. Since my Fragments are generated dynamically they do not have IDs. Instead this lookup would use the ID of the View it is attached to - in this case it ends up being the ViewPager and in the case of ViewPager, if you have multiple fragments in the ViewPager only the last fragment can be found this way. So that is not going to work.
  • 2. fragManager.findFragmentByTag(). I don't create the tag (and can't do so in code - at least not while using the ViewPager/FragmentPagerAdapter), but by looking through code and debugging I have been able to find the format for tags used by the FragmentPagerAdapter. With this I can calculate the tag and use it to lookup the Fragments stored in the FragmentManager:
  • This works, and is what I am tempted to use, but it seems fragile to me. The implementation of how the Tag is created is not public and so could change.

  • 3. fragmentManager.getFragment(Bundle source, String key). I think I have access to the saved Fragment bundle, but again, I don't know the key - I would have to figure out how to reproduce it and since it isn't public it could change. So this solution isn't any better than the previous one so I didn't pursue it.


  • Since the Fragments that get created by the parent class seem to be attached to the ViewPager somehow, since they appear in the GUI and can be switched between, I thought I might be able to look them up from the ViewPager or the FragmentPagerAdapter. I haven't figured out how - it is clear to me that they do not make their way into the FragmentPagerAdapter though: so the only routes seem to be through the FragmentManager (discussed above why I think they are fragile) and the ViewPager which I have yet determined how to retrieve them (if possible).


    Steve
    Steve Luke
    Bartender

    Joined: Jan 28, 2003
    Posts: 4167
        
      21

    Just to follow up, I found a better solution (and am a little miffed I didn't think of this sooner). Essentially, you save the fragment tag to the savedInstanceBundle before the activity is destoyed, so you can retrieve it later on. You no longer have to rely on knowing how the tags are generated - as long as they are generated.
     
    Consider Paul's rocket mass heater.
     
    subject: Dynamic Fragments: How can I access them when they are recreated from saved state?
     
    Similar Threads
    Screen changing
    Problem with onCreateOptionsMenu
    Tring to do a spinner and i am getting errors
    custom view
    Cannot find the problem in the code