-2

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?

berimbolo
  • 3,319
  • 8
  • 43
  • 78

2 Answers2

1

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).

Nizam
  • 5,698
  • 9
  • 45
  • 57
-1

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.

Jelle van Es
  • 613
  • 6
  • 20
  • [... I hate stealing ...](http://stackoverflow.com/questions/25520705/android-cant-retain-fragments-that-are-nested-in-other-fragments#answer-25520822) – Selvin Oct 23 '15 at 14:31
  • I'm just trying to help him. apparently he can't google. – Jelle van Es Oct 23 '15 at 14:32
  • I did google... nice answer, I already read that and yes one of them does... its rthe retained fragment... – berimbolo Oct 23 '15 at 14:34
  • @JellevanEs SO use: http://creativecommons.org/licenses/by-sa/2.5/ <= *You are free to: Share, Adapt* **Under the following terms: Attribution — You must give appropriate credit** – Selvin Oct 23 '15 at 14:35
  • 1
    If the answer works I must be missing something, as in all the examples the fragment which is 'retained' needs to have this set so that device rotation doesnt restart it. Why not just say this cant be done? – berimbolo Oct 23 '15 at 14:36