diff --git a/ApiDemos/kotlin/app/src/gms/AndroidManifest.xml b/ApiDemos/kotlin/app/src/gms/AndroidManifest.xml index af466568..18268f60 100644 --- a/ApiDemos/kotlin/app/src/gms/AndroidManifest.xml +++ b/ApiDemos/kotlin/app/src/gms/AndroidManifest.xml @@ -52,6 +52,7 @@ + diff --git a/ApiDemos/kotlin/app/src/gms/java/com/example/kotlindemos/CameraClampingDemoActivity.kt b/ApiDemos/kotlin/app/src/gms/java/com/example/kotlindemos/CameraClampingDemoActivity.kt index a4c3dd16..20386103 100644 --- a/ApiDemos/kotlin/app/src/gms/java/com/example/kotlindemos/CameraClampingDemoActivity.kt +++ b/ApiDemos/kotlin/app/src/gms/java/com/example/kotlindemos/CameraClampingDemoActivity.kt @@ -23,8 +23,6 @@ import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.gms.maps.GoogleMap -import com.google.android.gms.maps.GoogleMap.OnCameraIdleListener -import com.google.android.gms.maps.OnMapReadyCallback import com.google.android.gms.maps.SupportMapFragment import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.LatLng @@ -33,7 +31,6 @@ import com.google.maps.android.ktx.CameraIdleEvent import com.google.maps.android.ktx.awaitMap import com.google.maps.android.ktx.cameraEvents import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch diff --git a/ApiDemos/kotlin/app/src/gms/java/com/example/kotlindemos/DemoDetailsList.kt b/ApiDemos/kotlin/app/src/gms/java/com/example/kotlindemos/DemoDetailsList.kt index 81e9651b..2267426e 100644 --- a/ApiDemos/kotlin/app/src/gms/java/com/example/kotlindemos/DemoDetailsList.kt +++ b/ApiDemos/kotlin/app/src/gms/java/com/example/kotlindemos/DemoDetailsList.kt @@ -86,6 +86,9 @@ class DemoDetailsList { DemoDetails(R.string.raw_map_view_demo_label, R.string.raw_map_view_demo_description, RawMapViewDemoActivity::class.java), + DemoDetails(R.string.save_state_demo_label, + R.string.save_state_demo_description, + SaveStateDemoActivity::class.java), DemoDetails(R.string.street_view_panorama_basic_demo_label, R.string.street_view_panorama_basic_demo_details, StreetViewPanoramaBasicDemoActivity::class.java), diff --git a/ApiDemos/kotlin/app/src/gms/java/com/example/kotlindemos/SaveStateDemoActivity.kt b/ApiDemos/kotlin/app/src/gms/java/com/example/kotlindemos/SaveStateDemoActivity.kt new file mode 100755 index 00000000..118fe41b --- /dev/null +++ b/ApiDemos/kotlin/app/src/gms/java/com/example/kotlindemos/SaveStateDemoActivity.kt @@ -0,0 +1,160 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.example.kotlindemos + +import android.os.Bundle +import android.os.Parcelable +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener +import com.google.android.gms.maps.GoogleMap.OnMarkerDragListener +import com.google.android.gms.maps.SupportMapFragment +import com.google.android.gms.maps.model.BitmapDescriptorFactory +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Marker +import com.google.maps.android.ktx.CameraEvent +import com.google.maps.android.ktx.addMarker +import com.google.maps.android.ktx.awaitMap +import com.google.maps.android.ktx.cameraEvents +import kotlinx.android.parcel.Parcelize +import kotlinx.coroutines.flow.collect +import java.util.Random + +/** + * This activity shows how to save the state of a MapFragment when the activity is recreated, like + * after rotation of the device. + */ +class SaveStateDemoActivity : AppCompatActivity() { + + @Parcelize + internal data class MarkerInfo(var hue: Float) : Parcelable + + /** + * Example of a custom `MapFragment` showing how the position of a marker and other + * custom + * [Parcelable]s objects can be saved after rotation of the device. + * + * + * Storing custom [Parcelable] objects directly in the [Bundle] provided by the + * [.onActivityCreated] method will throw a `ClassNotFoundException`. This + * is due to the fact that this Bundle is parceled (thus losing its ClassLoader attribute at + * this moment) and unparceled later in a different ClassLoader. + *

+ * A workaround to store these objects is to wrap the custom [Parcelable] objects in a + * new + * [Bundle] object. + * + * + * However, note that it is safe to store [Parcelable] objects from the Maps API (eg. + * MarkerOptions, LatLng, etc.) directly in the Bundle provided by the + * [.onActivityCreated] method. + */ + class SaveStateMapFragment : SupportMapFragment(), OnMarkerClickListener, OnMarkerDragListener { + + private lateinit var mMarkerPosition: LatLng + private lateinit var mMarkerInfo: MarkerInfo + private var mMoveCameraToMarker = false + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Extract the state of the MapFragment: + // - Objects from the API (eg. LatLng, MarkerOptions, etc.) were stored directly in + // the savedInsanceState Bundle. + // - Custom Parcelable objects were wrapped in another Bundle. + mMarkerPosition = + savedInstanceState?.getParcelable(MARKER_POSITION) ?: DEFAULT_MARKER_POSITION + mMarkerInfo = + savedInstanceState?.getBundle(OTHER_OPTIONS)?.getParcelable(MARKER_INFO) ?: MarkerInfo( + BitmapDescriptorFactory.HUE_RED) + mMoveCameraToMarker = savedInstanceState == null + + lifecycleScope.launchWhenCreated { + val map = awaitMap() + map.addMarker { + icon(BitmapDescriptorFactory.defaultMarker(mMarkerInfo.hue)) + position(mMarkerPosition) + draggable(true) + } + + map.setOnMarkerDragListener(this@SaveStateMapFragment) + map.setOnMarkerClickListener(this@SaveStateMapFragment) + + if (mMoveCameraToMarker) { + map.animateCamera(CameraUpdateFactory.newLatLng(mMarkerPosition)) + } + } + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + + // All Parcelable objects of the API (eg. LatLng, MarkerOptions, etc.) can be set + // directly in the given Bundle. + outState.putParcelable(MARKER_POSITION, mMarkerPosition) + + // All other custom Parcelable objects must be wrapped in another Bundle. Indeed, + // failing to do so would throw a ClassNotFoundException. This is due to the fact that + // this Bundle is being parceled (losing its ClassLoader at this time) and unparceled + // later in a different ClassLoader. + val bundle = Bundle() + bundle.putParcelable(MARKER_INFO, mMarkerInfo) + outState.putBundle(OTHER_OPTIONS, bundle) + } + + override fun onMarkerClick(marker: Marker): Boolean { + val newHue = MARKER_HUES[Random() + .nextInt(MARKER_HUES.size)] + mMarkerInfo.hue = newHue + marker.setIcon(BitmapDescriptorFactory.defaultMarker(newHue)) + return true + } + + override fun onMarkerDragStart(marker: Marker) {} + override fun onMarkerDrag(marker: Marker) {} + override fun onMarkerDragEnd(marker: Marker) { + mMarkerPosition = marker.position + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.save_state_demo) + } + + companion object { + /** Default marker position when the activity is first created. */ + private val DEFAULT_MARKER_POSITION = LatLng(48.858179, 2.294576) + + /** List of hues to use for the marker */ + private val MARKER_HUES = floatArrayOf( + BitmapDescriptorFactory.HUE_RED, + BitmapDescriptorFactory.HUE_ORANGE, + BitmapDescriptorFactory.HUE_YELLOW, + BitmapDescriptorFactory.HUE_GREEN, + BitmapDescriptorFactory.HUE_CYAN, + BitmapDescriptorFactory.HUE_AZURE, + BitmapDescriptorFactory.HUE_BLUE, + BitmapDescriptorFactory.HUE_VIOLET, + BitmapDescriptorFactory.HUE_MAGENTA, + BitmapDescriptorFactory.HUE_ROSE) + + // Bundle keys. + private const val OTHER_OPTIONS = "options" + private const val MARKER_POSITION = "markerPosition" + private const val MARKER_INFO = "markerInfo" + } +} \ No newline at end of file diff --git a/ApiDemos/kotlin/app/src/gms/res/layout/save_state_demo.xml b/ApiDemos/kotlin/app/src/gms/res/layout/save_state_demo.xml new file mode 100755 index 00000000..98400fb2 --- /dev/null +++ b/ApiDemos/kotlin/app/src/gms/res/layout/save_state_demo.xml @@ -0,0 +1,33 @@ + + + + + + + + diff --git a/ApiDemos/kotlin/app/src/main/res/values/strings.xml b/ApiDemos/kotlin/app/src/main/res/values/strings.xml index 42f5c5c7..2ad5f9ef 100644 --- a/ApiDemos/kotlin/app/src/main/res/values/strings.xml +++ b/ApiDemos/kotlin/app/src/main/res/values/strings.xml @@ -310,4 +310,9 @@ Retain map Demonstrates how to reuse a MapFragment. + + Save the state of a MapFragment. + Demonstrates how to save the state of a MapFragment upon rotation of the device. + Drag the marker, tap on it to change its color and rotate the device to check that the state of the map is preserved. + \ No newline at end of file