diff --git a/maps-app/src/main/AndroidManifest.xml b/maps-app/src/main/AndroidManifest.xml
index 5ef1eda6..5b29a5f1 100644
--- a/maps-app/src/main/AndroidManifest.xml
+++ b/maps-app/src/main/AndroidManifest.xml
@@ -100,6 +100,9 @@
+
diff --git a/maps-app/src/main/java/com/google/maps/android/compose/Demo.kt b/maps-app/src/main/java/com/google/maps/android/compose/Demo.kt
index 9cf9bea5..5fd362c3 100644
--- a/maps-app/src/main/java/com/google/maps/android/compose/Demo.kt
+++ b/maps-app/src/main/java/com/google/maps/android/compose/Demo.kt
@@ -106,6 +106,11 @@ sealed class ActivityGroup(
R.string.accessibility_activity_description,
AccessibilityActivity::class
),
+ Activity(
+ R.string.tile_overlay_activity,
+ R.string.tile_overlay_activity_description,
+ TileOverlayActivity::class
+ ),
)
)
diff --git a/maps-app/src/main/java/com/google/maps/android/compose/TileOverlayActivity.kt b/maps-app/src/main/java/com/google/maps/android/compose/TileOverlayActivity.kt
new file mode 100644
index 00000000..5f531cfc
--- /dev/null
+++ b/maps-app/src/main/java/com/google/maps/android/compose/TileOverlayActivity.kt
@@ -0,0 +1,123 @@
+package com.google.maps.android.compose
+
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.os.Build
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.dp
+import androidx.core.graphics.createBitmap
+import com.google.android.gms.maps.model.Tile
+import com.google.android.gms.maps.model.TileProvider
+import java.io.ByteArrayOutputStream
+import kotlinx.coroutines.delay
+
+/**
+ * This activity demonstrates how to use Tile Overlays with Jetpack Compose.
+ */
+class TileOverlayActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ Content()
+ }
+ }
+}
+
+@Composable
+private fun Content() {
+ GoogleMap(
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ UpdatedTileOverlay()
+ }
+}
+
+/**
+ * This composable demonstrates how to use a [TileOverlay] with a [TileProvider] that
+ * updates its content periodically.
+ */
+@Composable
+private fun UpdatedTileOverlay() {
+ var tileProviderIndex by remember { mutableIntStateOf(0) }
+ var renderedIndex by remember { mutableIntStateOf(0) }
+ val state = rememberTileOverlayState()
+
+ val size = with(LocalDensity.current) { 256.dp.toPx() }.toInt()
+ val tileProvider = remember(tileProviderIndex) {
+ TileProvider { _, _, _ ->
+ Tile(size, size, renderTiles(renderedIndex, size))
+ }
+ }
+
+ TileOverlay(tileProvider = tileProvider, state = state, fadeIn = false)
+
+ LaunchedEffect(Unit) {
+ // This LaunchedEffect demonstrates two ways to update a tile overlay.
+
+ // 1. Invalidate the cache to redraw tiles with new data.
+ // Here, we're calling `state.clearTileCache()` every second for 5 seconds.
+ // This tells the map to request new tiles from the *existing* TileProvider,
+ // which will then re-render them using the latest `renderedIndex`.
+ repeat(5) {
+ delay(1000)
+ renderedIndex += 1
+ state.clearTileCache()
+ }
+
+ // 2. Update the TileProvider instance itself.
+ // After 5 seconds, we update `tileProviderIndex`. Because this is a key
+ // to the `remember` block for our TileProvider, Compose will discard the
+ // old provider and create a new one.
+ tileProviderIndex += 1
+
+ // Now, we continue invalidating the cache to demonstrate that the *new*
+ // TileProvider is the one responding to the `clearTileCache` calls.
+ while (true) {
+ delay(1000)
+ renderedIndex += 1
+ state.clearTileCache()
+ }
+ }
+}
+
+/**
+ * Helper function to dynamically generate a tile image.
+ * The [TileProvider] interface requires that a [ByteArray] is returned for each tile.
+ * This function creates a [Bitmap], draws the current [index] on it, and then compresses
+ * it into a [ByteArray] to be returned by the provider.
+ */
+private fun renderTiles(index: Int, size: Int): ByteArray {
+ val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ textAlign = Paint.Align.CENTER
+ color = Color.Black.toArgb()
+ textSize = 100f
+ }
+ val bitmap = createBitmap(size, size).also {
+ Canvas(it).drawText(index.toString(), size / 2f, size / 2f, paint)
+ }
+
+ val format = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ Bitmap.CompressFormat.WEBP_LOSSLESS
+ } else {
+ Bitmap.CompressFormat.PNG
+ }
+
+ return ByteArrayOutputStream().use { stream ->
+ bitmap.compress(format, 0, stream)
+ stream.toByteArray()
+ }
+}
diff --git a/maps-app/src/main/res/values/strings.xml b/maps-app/src/main/res/values/strings.xml
index d06ac870..de8d3772 100644
--- a/maps-app/src/main/res/values/strings.xml
+++ b/maps-app/src/main/res/values/strings.xml
@@ -69,10 +69,13 @@
Recomposition
Understanding how recomposition works with maps.
+ Tile Overlay
+ Adding a tile overlay to the map.
+
Map Types
Map Features
Markers
UI Integration
Performance
-
\ No newline at end of file
+