Google Maps Android API v2 - restoring map state
Asked Answered
V

5

7

I'm building a very simple map app using Google Maps Android API v2. As expected, when the user leaves and then returns to the app, any changes they've made in location, zoom, etc. are lost as the activity is destroyed and re-created.

I know I can save the map's camera state programmatically (perhaps as values in shared preferences) in onPause() and restore it in onResume(), but does the API have any built-in mechanism for persisting map state between activity launches?

Vivyan answered 22/5, 2013 at 17:17 Comment(0)
S
9

I don't think you can, but you can save your CameraPosition which has your Position/Zoom/Angle...

http://developer.android.com/reference/com/google/android/gms/maps/model/CameraPosition.html

so you can write a function in your onDestroy which gets the CameraPosition from your map and store it in your SharedPreferences. In your onCreate() you recreate your CameraPosition from the SharedPreferences (after your map is instanciated).

// somewhere in your onDestroy()
@Override
protected void onDestroy() {
    CameraPosition mMyCam = MyMap.getCameraPosition();
    double longitude = mMyCam.target.longitude;
    (...)

    SharedPreferences settings = getSharedPreferences("SOME_NAME", 0);
    SharedPreferences.Editor editor = settings.edit();
    editor.putDouble("longitude", longitude);
    (...) //put all other values like latitude, angle, zoom...
    editor.commit();
}

in your onCreate()

SharedPreferences settings = getSharedPreferences("SOME_NAME", 0);
// "initial longitude" is only used on first startup
double longitude = settings.getDouble("longitude", "initial_longitude");  
(...)  //add the other values

LatLng startPosition = new LatLng() //with longitude and latitude


CameraPosition cameraPosition = new CameraPosition.Builder()
.target(startPosition)      // Sets the center of the map to Mountain View
.zoom(17)                   // Sets the zoom
.bearing(90)                // Sets the orientation of the camera to east
.tilt(30)                   // Sets the tilt of the camera to 30 degrees
.build();                   // Creates a CameraPosition from the builder

create a new cameraPosition and animate it. be sure, map is instanziated at that point

 map.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
Sailfish answered 22/5, 2013 at 18:0 Comment(3)
Saving the CameraPosition as a parcelable object during onSaveInstanceState() would seem to work for a change in orientation, when it's called, but it isn't called when the user exits by touching the back button. There I'm getting onPause(), onStop(), onDestroy() - none of which give me access to the instance state Bundle. I'm just learning about state management so forgive me if I've got this wrong.Vivyan
Am I missing something? there is no guarantee that onDestroy will be called so it is better to save the data in onPause method. Similarly it is good practice to retrieve the data in onResume rather than in onCreate.Thermosetting
beside you onDestroy-won't-be-called objection, it won't make any difference. We used onCreate/onDestroy because we only wanted to recreate mapstate on startup of application. However, moving the blocks to onResume and onPause will work tooSailfish
M
1

As instructed here Retain instance of MapFragment, you can simply call setRetainInstance(true); for a MapFragment or SupportMapFragment in order to retain camera state and markers after a device orientation change. However, SharedPreferences is the way to go if you'd like to keep the state between app launches as mentioned by longilong

Macegan answered 3/8, 2018 at 14:1 Comment(0)
L
1

All credits go to longi for the answer in 2013. It helped me a lot. In order to help others even more let me share my exact solution (based on longi's answer).

Save camera position in onStop():

@Override
protected void onStop() {
    super.onStop();

    CameraPosition mMyCam = mMap.getCameraPosition();
    double longitude = mMyCam.target.longitude;
    double latitude = mMyCam.target.latitude;
    float zoom = mMyCam.zoom;

    SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
    SharedPreferences.Editor editor = sharedPref.edit();
    editor.putFloat("longitude", (float)longitude);
    editor.putFloat("latitude", (float)latitude);
    editor.putFloat("zoom", zoom);
    editor.apply();
}

Get camera position in onMapReady():

@Override
public void onMapReady(GoogleMap googleMap) {

    mMap = googleMap;

    // Get previous (or default) camera position
    SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
    double longitude = sharedPref.getFloat("longitude", DEFAULT_LONGITUDE);
    double latitude = sharedPref.getFloat("latitude", DEFAULT_LATITUDE);
    float zoom = sharedPref.getFloat("zoom", DEFAULT_ZOOM);
    LatLng startPosition = new LatLng(latitude, longitude);
    CameraPosition cameraPosition = new CameraPosition.Builder()
            .target(startPosition)
            .zoom(zoom)
            .build();
    mMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
}
Lohengrin answered 15/3, 2020 at 15:8 Comment(0)
A
0

There is nothing in the API for this use case.

You may want to try this library: https://github.com/fsilvestremorais/android-complex-preferences

It will not be as efficient as just writing a bunch of SharedPreferences.Editor.put... for values from CameraPosition in onPause, because it uses Gson internally, but saves some time if you have a more complex object to save and restore.

Anyway you may also post a feature request on gmaps-api-issues (or Android Maps Extensions). It's certainly something that is missing to persist simple objects (CameraPosition, LatLng, LatLngBounds) easily.

Arboreal answered 22/5, 2013 at 20:45 Comment(3)
As an alternative: I maintain the TypedPreferences library.Selemas
@Selemas Hey Tobias, I've seen it before, but your lib doesn't have ObjectPreference (anymore?). Anyway I would now suggest using Hrisey and @Preferences annotation instead. Have you seen it?Caress
No, TypedPreferences only supports the "primitives". I did not know Hrisey - thanks for sharing. I will take a look.Selemas
S
0

Here is an approach using the save/restore mechanism of Fragment/Activity written in Kotlin

First create a data class that will hold the map state and make it implement Parcelable

data class MapState(
    val mapType: Int,
    val zoomLevel: Float,
    val cameraLat: Double,
    val cameraLng: Double
) : Parcelable {
    constructor(parcel: Parcel) : this(
        parcel.readInt(),
        parcel.readFloat(),
        parcel.readDouble(),
        parcel.readDouble()
    )

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeInt(mapType)
        parcel.writeFloat(zoomLevel)
        parcel.writeDouble(cameraLat)
        parcel.writeDouble(cameraLng)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<MapState> {
        override fun createFromParcel(parcel: Parcel): MapState {
            return MapState(parcel)
        }

        override fun newArray(size: Int): Array<MapState?> {
            return arrayOfNulls(size)
        }
    }
}

Then in your Activity/Fragment define

private var mapState: MapState? = null

In onCreate() or in this case onViewCreated() because it's fragment, check if saved instance state is available and if so get the parcelable from it

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        mapState = if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            savedInstanceState?.getParcelable(SAVED_STATE_MAP_STATE, MapState::class.java)
        } else {
            @Suppress("DEPRECATION")
            savedInstanceState?.getParcelable(SAVED_STATE_MAP_STATE)
        }
}

in your onMapReady() method check if mapState is available, if it is restore the map state

mapState?.let {
            googleMap.moveCamera(
                CameraUpdateFactory.newLatLngZoom(
                    LatLng(it.cameraLat, it.cameraLng), it.zoomLevel
                )
            )

            googleMap.mapType = it.mapType
        } ?: run {
            setupUiWithLocationPermission()

            googleMap.mapType = mapType
        }

Override onSaveInstanceState() method of the activity/fragment to save the map state

override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)

        val cameraPosition = googleMap.cameraPosition

        val mapState = MapState(
            mapType,
            cameraPosition.zoom,
            cameraPosition.target.latitude,
            cameraPosition.target.longitude
        )

        outState.putParcelable(SAVED_STATE_MAP_STATE, mapState)
    }

Hope it helps :)

Sibeal answered 29/3, 2024 at 20:5 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.