Walk Through: Google Map Platform 101 in Android — Ktx Update

Homan Huang
Geek Culture
Published in
14 min readMay 21, 2021

I will use the exercise,Map-Platform-101-Android from the Codelab. However, its instructions are NOT user-friendly, you probably get a blank screen at the beginning.

👹: What D! He#$!

Logcat:

E/Google Maps Android API: Authorization failure.  Please see https://developers.google.com/maps/documentation/android-api/start for how to correctly set up the map.
E/Google Maps Android API: In the Google Developer Console (https://console.developers.google.com)
Ensure that the "Google Maps Android API v2" is enabled.
Ensure that the following Android Key exists:
API Key: {GOOGLE_MAPS_API_KEY}
Android Application (<cert_fingerprint>;<package_name>): YOUR_SHA1;com.homan.huang.mapdemo

Don’t worry! I’ll help you to solve this issue. Let’s begin.

— === MenU === —

👊1. Generate API Key
💰2.
Enable Billing
🔗3.
Link API key to Map Project
✒️4.
Start the Project
………….. 💯 Google Cloud Support
🖍 5.
Insert Markers
🎱 6.
Customize Marker
🎳 7.
Marker Cluster → Group Overlapped Markers
🎨 8.
Draw on the Map
📷 9.
Camera Control
🚀 10.
KTX Upgrade
🤐 11. Secrets Upgrade

👊1. Generate API Key …… → Menu

Google Cloud Platform: https://console.cloud.google.com/

I create a project: Map Demo

Add APIs: Maps Static API, Maps SDK for Android, and Places

According to my test, these APIs are responded on my first run.

Credentials

Create Credentials

API Key

Restrict to Places API

Edit →

Set name →

Set restrictions →

Choose → All map APIs you enabled

Save →

Test API Key Online

Please add your key to this link

https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=-33.8670522,151.1957362&radius=500&types=food&name=harbour&key=YOUR_API_KEY

For the first time, I have this message:

{
error_message: "You must enable Billing on the Google Cloud Project at https://console.cloud.google.com/project/_/billing/enable Learn more at https://developers.google.com/maps/gmp-get-started",
html_attributions: [ ],
results: [ ],
status: "REQUEST_DENIED"
}

💰2. Enable Billing …… → Menu

Project — Map Demo: Billing

If you have a billing account already, you can skip next step and jump to “Link a billing account .

Manage Billing Aaccount →

Create Account →

Create a new billing account →

Next, you can add your payment type. After that, let’s go back to Billing.

Link a billing account →

Your billing is ready. Let’s run the test link again. You’d have,

No more error!

✌️ You are ready to create your Android project.

🔗3. Link API key to Map Project …… → Menu

Before I restrict the API key, I need a package name and SHA-1 fingerprint. Let’s open a new project for Google Map.

Check google_maps_api.xml

Under debug folder, you’ll find a file, google_maps_api.xml.

Your package name and SHA-1 are ready for you.

Restrict Key to Your Package

Application Restriction:

I will choose Android apps.

Paste Android package name and SHA-1 →

Gradle.Module

Enable view binding.

android {
...
buildFeatures {
viewBinding true
}
}

And insert these lines to the dependencies{},

implementation 'com.google.android.gms:play-services-maps:17.0.1'
implementation 'com.google.android.gms:play-services-location:18.0.0'
implementation 'com.google.code.gson:gson:2.8.6'

Sync.

Hide Your Key in local.properties

Gradle.Module — Read local.properties

// get property from local.properties
def getProps(String propName) {
def propsFile = rootProject.file('local.properties')
if (propsFile.exists()) {
def props = new Properties()
props.load(new FileInputStream(propsFile))
return props[propName]
} else {
return "";
}
}
android {
...

defaultConfig {
...

//AndroidManifest -- [ Variable: Value ]
manifestPlaceholders = [ mapKey: getProps('MAPS_API_KEY') ]

AndroidManifest.xml

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />



<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MapDemo">

<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="${mapKey}"
/>

<activity
android:name=".ui.MapsActivity"
android:label="@string/title_activity_maps">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

✒️4. Start the Project …… → Menu

res/layout/activity_maps.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="ui.MapsActivity"
>

<fragment
class="com.google.android.gms.maps.SupportMapFragment"
android:id="@+id/map_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>

</FrameLayout>

If you have a red line under SupportMapFragment, please lower the version of play-services-maps in gradle.

🚁 — I don’t like the folder structure in this lab. So I change it to MVVM style.

ui/MapsActivity.kt

// check manifests for permissions
private val REQUIRED_PERMISSIONS = arrayOf(
Manifest.permission.INTERNET,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_NETWORK_STATE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)

class MapsActivity : AppCompatActivity(), OnMapReadyCallback {

private lateinit var mMap: GoogleMap
private lateinit var binding: ActivityMapsBinding

private val places: List<Place> by lazy {
PlacesReader(this).read()
}

// app permission
private val reqMultiplePermissions = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
permissions.entries.forEach {
lgd("mainAct: Permission: ${it.key} = ${it.value}")
if (!it.value) {
// toast
msg
(this, "Permission: ${it.key} denied!", 1)
finish()
}
}
}

Use Google Maps to find a location in San Francisco for this demo.

Next, you can replace Sydney with your location.

override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap

// Add a marker in Sydney and move the camera
val sf = LatLng(37.75, -122.45)
mMap.addMarker(MarkerOptions()
.position(sf)
.title("Marker in San Francisco"))
mMap.moveCamera(CameraUpdateFactory.newLatLng(sf))
mMap.animateCamera(CameraUpdateFactory.zoomIn())
mMap.animateCamera(CameraUpdateFactory.zoomTo(13f), 2000, null)

}

onCreate():

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

binding = ActivityMapsBinding.inflate(layoutInflater)
setContentView(binding.root)

// check app permissions
reqMultiplePermissions.launch(REQUIRED_PERMISSIONS)

// Obtain the SupportMapFragment and get notified when the map is ready to be used.
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map_fragment) as SupportMapFragment
mapFragment.getMapAsync(this)
mapFragment.getMapAsync { googleMap ->
addMarkers(googleMap)
}
}

🐟Run!

No good with a blank screen!

I search error in Logcat:

💯 Google Cloud Support …… → Menu

I am not so sure whether it is an issue from the server or from my setting. So I chatted with Google Cloud Support. I suggest that you choose to chat also because a real person will help you in real-time.

❌ The first guy asked me to add Google Map Javascript API. This guy must be a newbie and he gave me some terrible advice. So I have tried to add as many as APIs to make the problem go away. No, it was not helping.

✔️I chatted with Google support again. This man studied my case for 10 minutes. He told me that API key had a reading issue, and asked me to replace the key in the AndroidManifest.xml directly. It worked. However, this is not a good idea to place the plain key in the manifest.

Search for Problem

I thanked the help from Google Cloud Support. After that, I still need to find a solution to secure the key. Finally, I found this is a Double-Quote issue. The API key is NOT a STRING type. It’s a text.

local.properties

After I removed the double quote in local.properties, I have this:

🎊Relax, the problem solved.

🖍 5. Insert Markers …… → Menu

Marker Data location: places.json

You need to download this file to the res/raw folder.

Codelab has provided the Place package:

place/Place.kt

data class Place(
val name: String,
val latLng: LatLng,
val address: String,
val rating: Float
)

place/PlaceReader.kt

class PlacesReader(private val context: Context) {

private val gson = Gson()

private val inputStream: InputStream
get() = context.resources.openRawResource(R.raw.places)

fun read(): List<Place> {
val itemType = object : TypeToken<List<PlaceResponse>>() {}.type
val reader = InputStreamReader(inputStream)
return gson.fromJson<List<PlaceResponse>>(reader, itemType).map {
it.toPlace()
}

}
}

place/PlaceResponse.kt

data class PlaceResponse(
val geometry: Geometry,
val name: String,
val vicinity: String,
val rating: Float
) {

data class Geometry(
val location: GeometryLocation
)

data class GeometryLocation(
val lat: Double,
val lng: Double
)
}

fun PlaceResponse.toPlace(): Place = Place(
name = name,
latLng = LatLng(geometry.location.lat, geometry.location.lng),
address = vicinity,
rating = rating
)

ui/MapsActivity.kt

Add a new variable — places

private val places: List<Place> by lazy {
PlacesReader(this).read()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

binding = ActivityMapsBinding.inflate(layoutInflater)
setContentView(binding.root)

// Obtain the SupportMapFragment and get notified when the map is ready to be used.
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment

// add marker from places.json
mapFragment.getMapAsync { googleMap ->
addMarkers(googleMap)
}
}
/**
* Adds marker representations of the places list on the provided GoogleMap object
*/
private fun addMarkers(googleMap: GoogleMap) {
places.forEach { place ->
val marker = googleMap.addMarker(
MarkerOptions()
.title(place.name)
.position(place.latLng)
)
}
}

🚵 Run and check markers

Africa? It’s not working. This Codelab gave us a terrible code example.

Fix it

I need to set destination, zoom in and add the marker. It missed two steps: set destination and zoom in. The fix in onCreate() will be,

val mapFragment = supportFragmentManager
.findFragmentById(R.id.map_fragment) as SupportMapFragment
mapFragment.getMapAsync(this)
mapFragment.getMapAsync { googleMap ->
addMarkers(googleMap)
}

🏄Run again.

Perfect!

🎱 6. Customize Marker …… → Menu

In this section, we need to change the marker image to a bike image.

res/layout/marker_info_contents.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:padding="8dp"
>

<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold"
tools:text="Title"
/>

<TextView
android:id="@+id/tv_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:textSize="16sp"
tools:text="123 Main Street"
/>

<TextView
android:id="@+id/tv_rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:textSize="16sp"
tools:text="Rating: 3"
/>

</LinearLayout>

You have a new layout. What will you need in next?

Adapter!

ui/adapter/MarkerInfoWindowAdapter.kt

class MarkerInfoWindowAdapter(
private val context: Context
) : GoogleMap.InfoWindowAdapter {

lateinit var binding: MarkerInfoContentsBinding

override fun getInfoContents(marker: Marker?): View? {
// 1. Get tag
val place = marker?.tag as? Place ?: return null

// 2. Inflate view and set title, address, and rating
binding = MarkerInfoContentsBinding.inflate(
LayoutInflater.from(context) )

binding.tvTitle.text = place.name
binding
.tvAddress.text = place.address
binding
.tvRating.text = "Rating: %.2f".format(place.rating)

return binding.root
}

override fun getInfoWindow(marker: Marker?): View? {
// Return null to indicate that the
// default window (white bubble) should be used
return null
}
}

Add Bicycle Icon

Download bike image
res/drawable: ic_directions_bike_black_24dp.xml

It’s a vector file. So we need a tool to convert the vecort XML to bitmap data.

Tool — BitmapHelper.kt

object BitmapHelper {
/**
* Demonstrates converting a
[Drawable] to a [BitmapDescriptor], for use as a marker icon.
* Taken from ApiDemos on GitHub: https://github.com/googlemaps/android-samples/blob/master/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/MarkerDemoActivity.kt
*/
fun vectorToBitmap(context: Context, @DrawableRes id: Int, @ColorInt color: Int): BitmapDescriptor {
val vectorDrawable = ResourcesCompat.getDrawable(context.resources, id, null)
if (vectorDrawable == null) {
Log.e("BitmapHelper", "Resource not found")
return BitmapDescriptorFactory.defaultMarker()
}
val bitmap = Bitmap.createBitmap(
vectorDrawable.intrinsicWidth,
vectorDrawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
vectorDrawable.setBounds(0, 0, canvas.width, canvas.height)
DrawableCompat.setTint(vectorDrawable, color)
vectorDrawable.draw(canvas)
return BitmapDescriptorFactory.fromBitmap(bitmap)
}
}

ui/MapsActivity.kt →bicycleIcon

private lateinit var bicycleIcon: BitmapDescriptoroverride fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

binding = ActivityMapsBinding.inflate(layoutInflater)
setContentView(binding.root)

// check app permissions
reqMultiplePermissions.launch(REQUIRED_PERMISSIONS)

val color = getColor(this, R.color.colorPrimary)
bicycleIcon = BitmapHelper.vectorToBitmap(
this, R.drawable.ic_directions_bike_black_24dp, color)
...
}
private fun addMarkers(googleMap: GoogleMap) {
places.forEach { place ->
val marker = googleMap.addMarker(
MarkerOptions()
.title(place.name)
.position(place.latLng)
.icon(bicycleIcon)
)
}
}

🗽Looking fine. Let’s run!

Good! No error!

🎳 7. Marker Cluster → Group Overlapped Markers …… → Menu

Overlapped icons view is looking terrible on the screen. That’s why we need Marker Cluster, which will give us a neat and clean look.

Gradle.Module

//region Google Map
implementation 'com.google.android.gms:play-services-maps:17.0.1'
implementation 'com.google.android.gms:play-services-location:18.0.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.google.maps.android:android-maps-utils:1.1.0'
//endregion

Three Steps

There are three simple steps to implement the cluster.

  1. Implement the ClusterItem interface.
  2. Subclass the DefaultClusterRenderer class.
  3. Create a ClusterManager and add items.

Let’s follow the steps.

1️⃣. Place → ClusterItem

data class Place(
val name: String,
val latLng: LatLng,
val address: String,
val rating: Float
): ClusterItem {
override fun getPosition(): LatLng = latLng

override fun getTitle(): String = name

override fun getSnippet(): String = address
}

2️⃣. Add a new class: place/PlaceRenderer.kt

class PlaceRenderer(
private val context: Context,
map: GoogleMap,
clusterManager: ClusterManager<Place>
) : DefaultClusterRenderer<Place>(context, map, clusterManager) {

/**
* The icon to use for each cluster item
*/
private val bicycleIcon: BitmapDescriptor = run {
val
color = ContextCompat.getColor(
context,
R.color.colorPrimary
)
BitmapHelper.vectorToBitmap(
context,
R.drawable.ic_directions_bike_black_24dp,
color
)
}

/**
* Before the cluster item (the marker) is rendered.
* Assign location on map.
*/
override fun onBeforeClusterItemRendered(
item: Place,
markerOptions: MarkerOptions
) {
markerOptions.title(item.name)
.position(item.latLng)
.icon(bicycleIcon)
}

/**
* After the cluster item (the marker) is rendered.
* Properties location on map.
*/
override fun onClusterItemRendered(
clusterItem: Place,
marker: Marker
) {
marker.tag = clusterItem
}
}

I get rid of lazy, which is not working on Kotlin 1.5.

3️⃣. ui/MapsActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
...
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map_fragment) as SupportMapFragment
mapFragment.getMapAsync(this)
mapFragment.getMapAsync { googleMap ->
addClusteredMarkers(googleMap)
// add zoom control bar
googleMap.uiSettings.isZoomControlsEnabled = true
}
}

One line will do the trick. I also add zoom control bar, so you can zoom in or out in the emulator.

/**
* Adds markers to the map with clustering support.
*/
private fun addClusteredMarkers(googleMap: GoogleMap) {
// Create the ClusterManager class and set the custom renderer.
val clusterManager = ClusterManager<Place>(this, googleMap)
clusterManager.renderer =
PlaceRenderer(
this,
googleMap,
clusterManager
)

// Set custom info window adapter
clusterManager
.markerCollection
.setInfoWindowAdapter(MarkerInfoWindowAdapter(this))

// Add the places to the ClusterManager.
clusterManager.addItems(places)
clusterManager.cluster()

// Set ClusterManager as the OnCameraIdleListener so that it
// can re-cluster when zooming in and out.
googleMap.setOnCameraIdleListener {
clusterManager.onCameraIdle()
}
}

The clusterManager is a busy manager, which works on every single call.

✈️Run!

🎨 8. Draw on the Map …… → Menu

Let’s have some fun on the map. First of all, we must have a click listener.

ui/MapsActivity.kt

private fun addClusteredMarkers(googleMap: GoogleMap) {
...

clusterManager.setOnClusterItemClickListener { item ->
addCircle(googleMap, item)
return@setOnClusterItemClickListener false
}

// Set ClusterManager as the OnCameraIdleListener so that it
// can re-cluster when zooming in and out.
googleMap.setOnCameraIdleListener {
clusterManager.onCameraIdle()
}
}

addCircle()

//region circle function
private var circle: Circle? = null

/**
* Adds a [Circle] around the provided [item]
*/
private fun addCircle(googleMap: GoogleMap, item: Place) {
circle?.remove()
circle = googleMap.addCircle(
CircleOptions()
.center(item.latLng)
.radius(1000.0)
.fillColor(ContextCompat.getColor(
this, R.color.colorPrimaryTranslucent))
.strokeColor(ContextCompat.getColor(
this, R.color.colorPrimary))
)
}
//endregion

Click the emulator so you can draw a circle with 1000 meters.

You can add polyline and polygon, too. I may display them in separate articles.

📷 9. Camera Control …… → Menu

Let’s remark the click listener to have a clear view without a circle.

ui/MapsActivity.kt

addClusteredMarkers():

// clusterManager.setOnClusterItemClickListener { item ->
// addCircle(googleMap, item)
// return@setOnClusterItemClickListener false
// }

onCreate():

mapFragment.getMapAsync { googleMap ->
addClusteredMarkers(googleMap)

// add zoom control bar
googleMap.uiSettings.isZoomControlsEnabled = true

// Ensure all places are visible in the map.
googleMap.setOnMapLoadedCallback {
val bounds = LatLngBounds.builder()
places.forEach { bounds.include(it.latLng) }
googleMap.moveCamera(
CameraUpdateFactory.newLatLngBounds(bounds.build(), 20))
}

}

This setting will move the map center to the marker’s location.

Translucent Effect in motion

addClusteredMarkers()

This effect requires all of the clusters to lower the alpha value while the camera is moving. When the camera stops, everything will roll back to normal.

// Camera moving, alpha ==> translucent.
googleMap.setOnCameraMoveStartedListener {
clusterManager.markerCollection.markers.forEach { it.alpha = 0.3f }
clusterManager.clusterMarkerCollection.markers.forEach { it.alpha = 0.3f }
}

googleMap.setOnCameraIdleListener {
// Camera stops ==> alpha = 1
clusterManager.markerCollection.markers.forEach {
it
.alpha = 1.0f }
clusterManager.clusterMarkerCollection.markers.forEach {
it
.alpha = 1.0f }

// Call clusterManager.onCameraIdle()
// Stop ==> reclustering
clusterManager.onCameraIdle()
}

🛫Run.

It dims. Working fine!

🚀 10. KTX Upgrade …… → Menu

Kotlin is the official language for Android. Let’s use its Ktx library.

Gradel.module

dependencies {
// ...

// Maps SDK for Android KTX Library
implementation 'com.google.maps.android:maps-ktx:3.0.0'

// Maps SDK for Android Utility Library KTX Library
implementation 'com.google.maps.android:maps-utils-ktx:3.0.0'

// Lifecycle Runtime KTX Library
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'

}

Sync.

ui/MapsActivity.kt

  • addMarkers():

original →

places.forEach { place ->
val
marker = googleMap.addMarker(
MarkerOptions()
.title(place.name)
.position(place.latLng)
.icon(bicycleIcon)
)
}

Ktx →

places.forEach { place ->
val
marker = googleMap.addMarker {
title(place.name)
position(place.latLng)
icon(bicycleIcon)
}
// Set place as the tag on the marker object
// so it can be referenced within
// MarkerInfoWindowAdapter
marker.tag = place
}

MarkerOption has turned into a lambda.

  • addCircle():

Original →

circle?.remove()
circle = googleMap.addCircle(
CircleOptions()
.center(item.latLng)
.radius(1000.0)
.fillColor(ContextCompat.getColor(this,
R.color.colorPrimaryTranslucent))
.strokeColor(ContextCompat.getColor(this,
R.color.colorPrimary))
)

Ktx →

circle?.remove()
circle = googleMap.addCircle {
center(item.latLng)
radius(1000.0)
fillColor(ContextCompat.getColor(
this@MapsActivity, R.color.colorPrimaryTranslucent))
strokeColor(ContextCompat.getColor(
this@MapsActivity, R.color.colorPrimary))
}

Same thing, CircleOptions has shrinked into a lambda.

Lifecycle Ktx

I separate the code from onMapReady():

override fun onMapReady(googleMap: GoogleMap) {
setToSF(googleMap)
}

private fun setToSF(googleMap: GoogleMap) {
mMap = googleMap

// Add a marker in Sydney and move the camera
val sf = LatLng(37.75, -122.45)
mMap.addMarker(MarkerOptions()
.position(sf)
.title("Marker in San Francisco"))
mMap.moveCamera(CameraUpdateFactory.newLatLng(sf))
mMap.animateCamera(CameraUpdateFactory.zoomIn())
mMap.animateCamera(CameraUpdateFactory.zoomTo(12f), 2000, null)
}

onCreate():

Original →

val mapFragment = supportFragmentManager
.findFragmentById(R.id.map_fragment) as SupportMapFragment
mapFragment.getMapAsync(this)
mapFragment.getMapAsync { googleMap ->
addClusteredMarkers(googleMap)

// add zoom control bar
googleMap.uiSettings.isZoomControlsEnabled = true

// Ensure all places are visible in the map.
googleMap.setOnMapLoadedCallback {
val bounds = LatLngBounds.builder()
places.forEach { bounds.include(it.latLng) }
googleMap.moveCamera(
CameraUpdateFactory.newLatLngBounds(bounds.build(), 20))
}
}

Ktx →

val mapFragment = supportFragmentManager
.findFragmentById(R.id.map_fragment) as SupportMapFragment
lifecycleScope.launchWhenCreated {
// Get map
val googleMap = mapFragment.awaitMap()

// Wait for map to finish loading
googleMap.awaitMapLoad()

// set location in San Francisco
setToSF(googleMap)

// Ensure all places are visible in the map
val bounds = LatLngBounds.builder()
places.forEach { bounds.include(it.latLng) }
googleMap.moveCamera(
CameraUpdateFactory.newLatLngBounds(
bounds.build(), 20))
googleMap.uiSettings.isZoomControlsEnabled = true
addClusteredMarkers(googleMap)
}

All the async functions are gathered together to run in lifecycleScope.

Let’s run. Ktx modification should not change the result in the past.

🤐 11. Secrets Upgrade …… → Menu

The secrets plugin can save us a lot of work to import the value from the secret file, such as local.properties.

gradle.module

plugin →

plugins {
...
id 'com.google.secrets_gradle_plugin' version '0.6'
}

secrets →

//// get property from local.properties
//def getProps(String propName) {
// def propsFile = rootProject.file('local.properties')
// if (propsFile.exists()) {
// def props = new Properties()
// props.load(new FileInputStream(propsFile))
// return props[propName]
// } else {
// return "";
// }
//}


secrets {
propertiesFileName 'local.properties'
ignoreList.add("sdk.*")
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"

defaultConfig {
applicationId "com.homan.huang.mapdemo"
minSdkVersion 19
targetSdkVersion 30
versionCode 1
versionName "1.0"

//AndroidManifest -- [ Variable: Value ]

//manifestPlaceholders = [mapKey: getProps('MAPS_API_KEY')]

local.properties

#MAPS_API_KEY=blahblahblah
mapKey=blahblahblah

I rename the key name to match the one in the manifest.

<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="${mapKey}"

👍Run. Everything will be the same.

🤗Congrate! You have finished Map 101 …… → Menu

Give me a clap if you can follow my words.

--

--

Homan Huang
Geek Culture

Computer Science BS from SFSU. I studied and worked on Android system since 2017. If you are interesting in my past works, please go to my LinkedIn.