web-dev-qa-db-fra.com

Restauration de l'état de MapView en rotation et à l'arrière

Contexte

J'ai une application plus grande dans laquelle j'ai eu/j'ai plusieurs problèmes avec la nouvelle API Google Maps. J'ai essayé de le décrire dans une question différente mais comme cela me paraissait trop complexe, j'ai décidé de lancer un nouveau projet, aussi simple que possible, et d'essayer de reproduire les problèmes. Alors le voici.

La situation

J'utilise Fragments et je veux mettre MapView à l'intérieur. Je ne veux pas utiliser MapFragment. L'exemple de projet que j'ai préparé n'est peut-être pas très beau, mais j'ai essayé de le rendre aussi simple que possible et il devait contenir quelques éléments (encore simplifiés) de l'application d'origine . J'ai un Activity et mon habit Fragment avec MapView, ajouté par programme. La Map contient des points/Markers. Après avoir cliqué sur un Marker, le InfoWindow s'affiche et un clic dessus provoque l'affichage du prochain Fragment (avec la fonction replace()) dans le contenu. 

Les problèmes

J'ai deux problèmes:

  1. Lorsque la Map avec Markers est affichée, la rotation de l'écran provoque une erreur Class not found when unmarshalling avec ma classe personnalisée MyMapPoint - je ne sais pas pourquoi ni ce que cela signifie.

  2. Je clique sur Marker puis sur InfoWindow. Après cela, j'appuie sur le bouton de retour matériel. Maintenant, je peux voir le Map mais sans Markers et centré dans le point 0,0.

Le code

Activité principale

public class MainActivity extends FragmentActivity {

    private ArrayList<MyMapPoint> mPoints = new ArrayList<MyMapPoint>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);     

        if (savedInstanceState == null) {
            mPoints.add(new MyMapPoint(1, new LatLng(20, 10), 
                "test point", "description", null));            
            mPoints.add(new MyMapPoint(2, new LatLng(10, 20), 
                "test point 2", "second description", null));

            Fragment fragment = MyMapFragment.newInstance(mPoints);
            getSupportFragmentManager().beginTransaction()
                .add(R.id.contentPane, fragment).commit();
        }
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:id="@+id/contentPane"
    Android:layout_width="fill_parent"
    Android:layout_height="fill_parent" />

map_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:orientation="vertical" >

    <com.google.Android.gms.maps.MapView
        xmlns:map="http://schemas.Android.com/apk/res-auto"
        Android:id="@+id/map"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent" />

</LinearLayout>

MyMapFragment

public class MyMapFragment extends Fragment implements
    OnInfoWindowClickListener {

    public static final String KEY_POINTS = "points";

    private MapView mMapView;
    private GoogleMap mMap;
    private HashMap<MyMapPoint, Marker> mPoints = 
        new HashMap<MyMapPoint, Marker>();

    public static MyMapFragment newInstance(ArrayList<MyMapPoint> points) {
        MyMapFragment fragment = new MyMapFragment();
        Bundle args = new Bundle();
        args.putParcelableArrayList(KEY_POINTS, points);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        mMapView.onSaveInstanceState(outState);
        MyMapPoint[] points = mPoints.keySet().toArray(
            new MyMapPoint[mPoints.size()]);
        outState.putParcelableArray(KEY_POINTS, points);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (savedInstanceState == null) {
            Bundle extras = getArguments();
            if ((extras != null) && extras.containsKey(KEY_POINTS)) {
                for (Parcelable pointP : extras.getParcelableArrayList(KEY_POINTS)) {
                    mPoints.put((MyMapPoint) pointP, null);
                }
            }
        } else {
            MyMapPoint[] points = (MyMapPoint[]) savedInstanceState
                .getParcelableArray(KEY_POINTS);
            for (MyMapPoint point : points) {
                mPoints.put(point, null);
            }
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, 
        ViewGroup container, Bundle savedInstanceState) {
        View layout = inflater.inflate(R.layout.map_fragment, container, false);
        mMapView = (MapView) layout.findViewById(R.id.map);
        return layout;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mMapView.onCreate(savedInstanceState);
        setUpMapIfNeeded();
        addMapPoints();
    }

    @Override
    public void onPause() {
        mMapView.onPause();
        super.onPause();
    }

    @Override
    public void onResume() {
        super.onResume();
        setUpMapIfNeeded();
        mMapView.onResume();
    }

    @Override
    public void onDestroy() {
        mMapView.onDestroy();
        super.onDestroy();
    }

    public void onLowMemory() {
        super.onLowMemory();
        mMapView.onLowMemory();
    };

    private void setUpMapIfNeeded() {
        if (mMap == null) {
            mMap = ((MapView) getView().findViewById(R.id.map)).getMap();
            if (mMap != null) {
                setUpMap();
            }
        }
    }

    private void setUpMap() {
        mMap.setOnInfoWindowClickListener(this);
        addMapPoints();
    }

    private void addMapPoints() {
        if (mMap != null) {
            HashMap<MyMapPoint, Marker> toAdd = 
                new HashMap<MyMapPoint, Marker>();
            for (Entry<MyMapPoint, Marker> entry : mPoints.entrySet()) {
                Marker marker = entry.getValue();
                if (marker == null) {
                    MyMapPoint point = entry.getKey();
                    marker = mMap.addMarker(point.getMarkerOptions());
                    toAdd.put(point, marker);
                }
            }
            mPoints.putAll(toAdd);
        }
    }

    @Override
    public void onInfoWindowClick(Marker marker) {
        Fragment fragment = DetailsFragment.newInstance();
        getActivity().getSupportFragmentManager().beginTransaction()
            .replace(R.id.contentPane, fragment)
            .addToBackStack(null).commit();
    }

    public static class MyMapPoint implements Parcelable {
        private static final int CONTENTS_DESCR = 1;

        public int objectId;
        public LatLng latLng;
        public String title;
        public String snippet;

        public MyMapPoint(int oId, LatLng point, 
            String infoTitle, String infoSnippet, String infoImageUrl) {
            objectId = oId;
            latLng = point;
            title = infoTitle;
            snippet = infoSnippet;
        }

        public MyMapPoint(Parcel in) {
            objectId = in.readInt();
            latLng = in.readParcelable(LatLng.class.getClassLoader());
            title = in.readString();
            snippet = in.readString();
        }

        public MarkerOptions getMarkerOptions() {
            return new MarkerOptions().position(latLng)
                .title(title).snippet(snippet);
        }

        @Override
        public int describeContents() {
            return CONTENTS_DESCR;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(objectId);
            dest.writeParcelable(latLng, 0);
            dest.writeString(title);
            dest.writeString(snippet);
        }

        public static final Parcelable.Creator<MyMapPoint> CREATOR = 
            new Parcelable.Creator<MyMapPoint>() {
            public MyMapPoint createFromParcel(Parcel in) {
                return new MyMapPoint(in);
            }

            public MyMapPoint[] newArray(int size) {
                return new MyMapPoint[size];
            }
        };

    }
}

Si vous avez besoin de regarder un autre fichier - faites le moi savoir. Ici vous pouvez trouver un projet complet, il vous suffit de mettre votre propre clé API Maps dans le fichier AndroidManifest.xml.

MODIFIER

J'ai réussi à rendre l'exemple encore plus simple et à mettre à jour le code ci-dessus.

26
Izydorr

Voici ma solution au premier problème:

Il semble que Map essaie de supprimer tout le paquet, pas seulement ses propres informations lorsque j'appelle mMap.onCreate(savedInstanceState). Cela pose un problème si j'utilise ma classe Parcelable personnalisée. La solution qui a fonctionné pour moi a été de retirer mes suppléments de savedInstanceState dès que je les ai utilisés, avant que j'appelle le onCreate() de Map. Je le fais avec savedInstanceState.remove(MY_KEY). Une autre chose que je devais faire était d'appeler mMap.onSaveInstanceState() avant d'ajouter mes propres informations à outState dans la fonction Fragment'sonSaveInstanceState(Bundle outState).

Et voici comment j'ai géré le second:

J'ai simplifié l'exemple du projet au strict minimum. J'ajoutais une Markers brute à la carte et si je remplaçais la Fragment par une autre, puis après avoir cliqué sur "retour", la carte "annulée" restait active ... J'ai donc fait deux choses:

  1. enregistrement de CameraPosition dans la fonction onPause() pour le restaurer dans onResume()
  2. régler mMap sur null dans onPause() afin que lorsque Fragment revienne, les Markers soient ajoutés à nouveau par la fonction addMapPoints() (je devais le modifier un peu puisque je sauvegardais et vérifiais les Markers id).

Voici des exemples de code:

private CameraPosition cp;

...

public void onPause() {
    mMapView.onPause();
    super.onPause();

    cp = mMap.getCameraPosition();
    mMap = null;
}

...

public void onResume() {
    super.onResume();
    setUpMapIfNeeded();
    mMapView.onResume();
    if (cp != null) {
        mMap.moveCamera(CameraUpdateFactory.newCameraPosition(cp));
        cp = null;
    }
}

Et pour mettre à jour la position de la caméra dans onResume(), je devais initialiser manuellement les cartes. Je l'ai fait dans setUpMap():

private void setUpMap() {
    try {
        MapsInitializer.initialize(getActivity());
    } catch (GooglePlayServicesNotAvailableException e) {
    }
    mMap.setOnInfoWindowClickListener(this);
    mMap.setOnMapLongClickListener(this);
    addMapPoints();
}

Je me rends compte que ce ne sont pas de vraies solutions - juste des substitutions mais c'est le mieux que je puisse faire pour l'instant et le projet doit continuer. Si quelqu'un trouve des solutions plus propres, je serai reconnaissant de me le faire savoir.

31
Izydorr

Je suis venu ici à la recherche d'indices concernant onSaveInstance et NullPointerException. J'ai essayé diverses solutions, dont celle mentionnée par @Izydorr, mais je n'ai pas eu de chance. Le problème était avec FragmentPagerAdapter - l'adaptateur de la ViewPager qui hébergeait mes fragments qui intégraient les MapViews. Après l'avoir changé en FragmentStatePagerAdapter, les NPE sont partis. Ouf!

1
kellogs

Implémente votre fragment/activité avecGoogleMap.OnCameraChangeListeneret la méthode de substitution onCameraChange dans laquelle vous pouvez enregistrer la nouvelle position de la caméra et utiliser onResume

googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPositionSaved));
0
PeppinoL