Nested fragments - Can't retain fragements that are nested in other fragments

I have an android app that consists of a number of nested fragments, for example I have a set of tabs and clicking a tab loads a fragment into the content area.

One of my tabs is a map showing locations on it and I am doing a geo lookup on a postcode to get back the coordinates for it.

This is all working and now finally I need to ensure that if a user clicks the map tab and initiates the async task which does the geo lookup that a device rotation doesnt stop this.

My map fragment which is loaded when the tab is clicked:

public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    View rootView = (View)inflater.inflate(R.layout.fragment_locations, container, false);

    // Make sure user's device supports Google play services
    try {
        MapsInitializer.initialize(getActivity());
        mapView = (MapView) rootView.findViewById(R.id.mapView);
        mapView.onCreate(savedInstanceState);
        googleMap = mapView.getMap();
        Log.i(LOG_TAG, "Google play service is available.");

        if(googleMap == null) {
            Toast.makeText(getActivity().getApplicationContext(), "Problem creating Google map", Toast.LENGTH_LONG).show();
        } else {
            googleMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
               googleMap.getUiSettings().setZoomControlsEnabled(true);                     

            FragmentManager fm = getFragmentManager();
            mRetainedTaskFragment = (LocationNetworkOperationsRetainedFragment) fm.findFragmentByTag(TAG_RETAINED_TASK_FRAGMENT);

            // If the Fragment is non-null, then it is currently being
            // retained across a configuration change.
            if (mRetainedTaskFragment == null) {
                mRetainedTaskFragment = new LocationNetworkOperationsRetainedFragment();
                fm.beginTransaction().add(mRetainedTaskFragment, TAG_RETAINED_TASK_FRAGMENT).commit();
            }
        }
    } catch (Exception e) {
        Log.e(LOG_TAG, "Google play service is not available.");
        Toast.makeText(getActivity().getApplicationContext(), "Google play service is not available", Toast.LENGTH_LONG).show();
    }

    return rootView;
}

My async task:

public class LocationNetworkOperations extends AsyncTask<String, Void, LatLng> {

    @Override
    protected LatLng doInBackground(String... postcodes) {
        Geocoder geocoder = new Geocoder(getActivity().getApplicationContext());
        LatLng coords= null;

        try {
            List<Address> addresses = geocoder.getFromLocationName(postcodes[0], 1);

            if (addresses != null && !addresses.isEmpty()) {
                Address address = addresses.get(0);
                coords = new LatLng(address.getLatitude(), address.getLongitude());
            } else {
                Toast.makeText(getActivity().getApplicationContext(), "Unable to geocode postcode" + postcodes[0], Toast.LENGTH_LONG).show();
            }
        } catch(Exception ioe) {
            ioe.printStackTrace();
        }

        return coords;
    }

    @Override
    protected void onPostExecute(LatLng coords) {
        if(coords != null) {
            Marker mapMarkerHome = googleMap.addMarker(new MarkerOptions().position(coords).title("Home")
                    .snippet("E10 6JQ").icon(BitmapDescriptorFactory
                            .fromResource(R.drawable.home_map_marker)));
            CameraPosition cameraPosition = new CameraPosition.Builder().target(coords).zoom(15).build();
            googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
        }
    }

    @Override
    protected void onPreExecute() {}

    @Override
    protected void onProgressUpdate(Void... values) {}
}

The retained fragment:

public class LocationNetworkOperationsRetainedFragment extends Fragment {

interface LocationNetworkOperationsTaskCallbacks {
    void onPreExecute();
    void onProgressUpdate(int percent);
    void onCancelled();
    void onPostExecute();
}

private LocationNetworkOperationsTaskCallbacks mCallbacks;
private LocationsFragment.LocationNetworkOperations mTask;

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    mCallbacks = (LocationNetworkOperationsTaskCallbacks) context;
}

/**
 * This method will only be called once when the retained
 * Fragment is first created.
 */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Retain this fragment across configuration changes.
    setRetainInstance(true);

    // Create and execute the background task.
    mTask = new LocationsFragment().new LocationNetworkOperations();
    mTask.execute("E106JQ");
}

@Override
public void onDetach() {
    super.onDetach();
    mCallbacks = null;
}

}

So I followed a tutorial and wrote a retained fragment and kick off the aysnc task in that but I am running into the following exception:

java.lang.IllegalStateException: Can't retain fragements that are nested in other fragments
        at android.support.v4.app.Fragment.setRetainInstance(Fragment.java:820)
        at com.example.android.LocationNetworkOperationsRetainedFragment.onCreate(LocationNetworkOperationsRetainedFragment.java:37)
        at android.support.v4.app.Fragment.performCreate(Fragment.java:1939)
        at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:988)
        at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1207)
        at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:738)
        at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1572)
        at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:493)
        at android.os.Handler.handleCallback(Handler.java:739)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:148)
        at android.app.ActivityThread.main(ActivityThread.java:5417)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

Why is it not possible to retain the fragment if it is nested and is there a solution to this?

Answers


Try using FragmentManager.saveFragmentInstanceState(Fragment) to retrieve a fragment state. The return value implements Parcelable, so you can put it in a Bundle.

For restoration, you can provide the state after creating the fragment using Fragment.setInitialSavedState(Fragment.SavedState).


Can't retain fragements that are nested in other fragments

This is a limitation of nested Fragments. I'm guessing one or more of your child Fragments have setRetainInstance(true) somewhere in their code. You need to remove that to prevent the error.


Need Your Help

What would be the best approach to HTML5 video?

javascript jquery html5 video

I'm working on this site ( http://martindue.dk/esoft/ ) where I'd like to have a movie player inside the very first block. So I'm thinking I need to build something from scratch, because I'd like the