Skip to content

Commit 0aaae62

Browse files
authored
chore(demo): add DemoApplication for API key/Map ID validation (#1600)
* chore(demo): add DemoApplication for API key/Map ID validation Introduces a centralized mechanism to validate the Maps API key and configure a Map ID, which is required for features like Advanced Markers. This change adds a new `DemoApplication` class that runs two checks on startup: - `checkApiKey()`: Validates that a non-default API key is present in the manifest metadata. - `getMapId()`: Retrieves a Map ID, prioritizing `secrets.properties` (via `BuildConfig`) and falling back to `strings.xml`. `BaseDemoActivity` is refactored to: - Get the Map ID from `DemoApplication`. - Programmatically create the `SupportMapFragment` using `GoogleMapOptions` to apply the `mapId`. This replaces the previous method of inflating the map from an XML layout, which didn't allow for easy programmatic Map ID configuration. Documentation in `README.md` is updated to reflect the new configuration options for setting the Map ID. * chore: add missing header * chore(demo): Initialize map fragment when map ID is null Allows the demo application to run even when a map ID is not provided by instantiating the `SupportMapFragment` without `GoogleMapOptions`. * chore: Reorder string resource to put map_id as last item * chore: Adds a link to the map id documentation. * docs: address review comment
1 parent af704c5 commit 0aaae62

File tree

8 files changed

+165
-10
lines changed

8 files changed

+165
-10
lines changed

README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,27 @@ To run the demo app, ensure you've met the requirements above then:
6565
1. Add a single line to `local.properties` that looks like `MAPS_API_KEY=YOUR_API_KEY`, where `YOUR_API_KEY` is the API key you obtained earlier
6666
1. Build and run the `debug` variant for the Maps SDK for Android version
6767

68+
### Setting up the Map ID
69+
70+
Some of the features in the demo app, such as Advanced Markers, require a Map ID. You can learn more about map IDs in the [official documentation](https://developers.google.com/maps/documentation/android-sdk/map-ids/mapid-over). You can set the Map ID in one of the following ways:
71+
72+
1. **`secrets.properties`:** Add a line to your `secrets.properties` file with your Map ID:
73+
```
74+
MAP_ID=YOUR_MAP_ID
75+
```
76+
77+
2. **`strings.xml`:** Alternatively, you can set the Map ID in the `demo/src/main/res/values/strings.xml` file:
78+
```xml
79+
<string name="map_id">YOUR_MAP_ID</string>
80+
```
81+
82+
3. **XML Layout Files:** You can also hardcode the Map ID directly in the XML layout files where a map is defined, by setting the `map:mapId` attribute:
83+
```xml
84+
<androidx.fragment.app.FragmentContainerView
85+
map:mapId="YOUR_MAP_ID"
86+
/>
87+
```
88+
6889
## Documentation
6990
7091
See the [documentation] for a full list of classes and their methods.
@@ -74,6 +95,7 @@ Full guides for using the utilities are published in
7495
7596
## Usage
7697
98+
<details>
7799
<summary>Marker utilities</summary>
78100
79101
### Marker utilities
@@ -82,7 +104,6 @@ Full guides for using the utilities are published in
82104
- Marker clustering [source](https://github.com/googlemaps/android-maps-utils/tree/main/library/src/main/java/com/google/maps/android/clustering), [guide](https://developers.google.com/maps/documentation/android-sdk/utility/marker-clustering)
83105
- Advanced Markers clustering [source](https://github.com/googlemaps/android-maps-utils/tree/main/library/src/main/java/com/google/maps/android/clustering), [sample code](https://github.com/googlemaps/android-maps-utils/blob/main/demo/src/main/java/com/google/maps/android/utils/demo/CustomAdvancedMarkerClusteringDemoActivity.java)
84106
- Marker icons [source](https://github.com/googlemaps/android-maps-utils/blob/main/library/src/main/java/com/google/maps/android/ui/IconGenerator.java), [sample code](https://github.com/googlemaps/android-maps-utils/blob/main/demo/src/main/java/com/google/maps/android/utils/demo/IconGeneratorDemoActivity.java)
85-
86107
</details>
87108
88109
<details>

demo/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
android:required="true" />
3636

3737
<application
38+
android:name=".DemoApplication"
3839
android:allowBackup="false"
3940
android:icon="@drawable/ic_launcher"
4041
android:label="@string/app_name"

demo/src/main/java/com/google/maps/android/utils/demo/BaseDemoActivity.java

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,10 @@
2626
import androidx.fragment.app.FragmentActivity;
2727

2828
import com.google.android.gms.maps.GoogleMap;
29+
import com.google.android.gms.maps.GoogleMapOptions;
2930
import com.google.android.gms.maps.OnMapReadyCallback;
3031
import com.google.android.gms.maps.SupportMapFragment;
3132

32-
import java.util.Objects;
33-
3433
import androidx.core.graphics.Insets;
3534
import androidx.core.view.ViewCompat;
3635
import androidx.core.view.WindowCompat;
@@ -79,7 +78,7 @@ public void onCreate(Bundle savedInstanceState) {
7978
// Return CONSUMED to signal that we've handled the insets.
8079
return WindowInsetsCompat.CONSUMED;
8180
});
82-
setUpMap();
81+
setUpMap(savedInstanceState);
8382
}
8483

8584
@Override
@@ -91,8 +90,37 @@ public void onMapReady(@NonNull GoogleMap map) {
9190
startDemo(mIsRestore);
9291
}
9392

94-
private void setUpMap() {
95-
((SupportMapFragment) Objects.requireNonNull(getSupportFragmentManager().findFragmentById(R.id.map))).getMapAsync(this);
93+
private void setUpMap(Bundle savedInstanceState) {
94+
// 1. Get the Application instance and cast it
95+
DemoApplication app = (DemoApplication) getApplication();
96+
97+
// 2. Call the getMapId() method
98+
String mapId = app.getMapId();
99+
100+
// Create a new SupportMapFragment instance
101+
SupportMapFragment mapFragment;
102+
103+
if (mapId == null) {
104+
mapFragment = SupportMapFragment.newInstance();
105+
} else {
106+
// Create the map options
107+
GoogleMapOptions mapOptions = new GoogleMapOptions();
108+
mapOptions.mapId(mapId);
109+
// Create a new SupportMapFragment instance
110+
mapFragment = SupportMapFragment.newInstance(mapOptions);
111+
}
112+
113+
// Add the fragment to the container (R.id.map)
114+
// Check savedInstanceState to prevent re-adding on rotation
115+
if (savedInstanceState == null) {
116+
getSupportFragmentManager()
117+
.beginTransaction()
118+
.add(R.id.map, mapFragment)
119+
.commit();
120+
}
121+
122+
// Get the map
123+
mapFragment.getMapAsync(this);
96124
}
97125

98126
/**

demo/src/main/java/com/google/maps/android/utils/demo/CustomAdvancedMarkerClusteringDemoActivity.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,21 +78,22 @@ public AdvancedMarkerRenderer() {
7878
protected void onBeforeClusterItemRendered(@NonNull Person person,
7979
@NonNull AdvancedMarkerOptions markerOptions) {
8080
markerOptions
81-
.icon(BitmapDescriptorFactory.fromPinConfig(getPinConfig().build()))
81+
.icon(BitmapDescriptorFactory.fromPinConfig(getPinConfig(person).build()))
8282
.title(person.name);
8383
}
8484

8585
@Override
8686
protected void onClusterItemUpdated(@NonNull Person person, @NonNull Marker marker) {
8787
// Same implementation as onBeforeClusterItemRendered() (to update cached markers)
88-
marker.setIcon(BitmapDescriptorFactory.fromPinConfig(getPinConfig().build()));
88+
marker.setIcon(BitmapDescriptorFactory.fromPinConfig(getPinConfig(person).build()));
8989
marker.setTitle(person.name);
9090
}
9191

92-
private PinConfig.Builder getPinConfig() {
92+
private PinConfig.Builder getPinConfig(Person person) {
9393
PinConfig.Builder pinConfigBuilder = PinConfig.builder();
9494
pinConfigBuilder.setBackgroundColor(Color.MAGENTA);
9595
pinConfigBuilder.setBorderColor(getResources().getColor(R.color.colorPrimaryDark));
96+
pinConfigBuilder.setGlyph(new PinConfig.Glyph(BitmapDescriptorFactory.fromResource(person.profilePhoto)));
9697
return pinConfigBuilder;
9798
}
9899

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.maps.android.utils.demo;
18+
19+
import android.app.Application;
20+
import android.util.Log;
21+
import android.content.pm.ApplicationInfo;
22+
import android.content.pm.PackageManager;
23+
import android.os.Bundle;
24+
import android.widget.Toast;
25+
import androidx.annotation.Nullable;
26+
import java.util.Objects;
27+
28+
/**
29+
* {@code DemoApplication} is a custom Application class for the API demo.
30+
*
31+
* <p>This class is responsible for application-wide initialization and setup,
32+
* such as checking for the presence and validity of the API key during the
33+
* application's startup.</p>
34+
*
35+
* <p>It extends the {@link Application} class and overrides the {@link #onCreate()}
36+
* method to perform these initialization tasks.</p>
37+
*/
38+
public class DemoApplication extends Application {
39+
private static final String TAG = "DemoApplication";
40+
private boolean mapIdSet = false;
41+
private String mapId = null;
42+
43+
@Override
44+
public void onCreate() {
45+
super.onCreate();
46+
checkApiKey();
47+
}
48+
49+
/**
50+
* Checks if the API key for Google Maps is properly configured in the application's metadata.
51+
* <p>
52+
* This method retrieves the API key from the application's metadata, specifically looking for
53+
* a string value associated with the key "com.google.android.geo.API_KEY".
54+
* The key must be present, not blank, and not set to the placeholder value "DEFAULT_API_KEY".
55+
* <p>
56+
* If any of these checks fail, a Toast message is displayed indicating that the API key is missing or
57+
* incorrectly configured, and a RuntimeException is thrown.
58+
* <p>
59+
*/
60+
private void checkApiKey() {
61+
try {
62+
ApplicationInfo appInfo = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
63+
Bundle bundle = Objects.requireNonNull(appInfo.metaData);
64+
65+
String apiKey = bundle.getString("com.google.android.geo.API_KEY"); // Key name is important!
66+
67+
if (apiKey == null || apiKey.isBlank() || apiKey.equals("DEFAULT_API_KEY")) {
68+
Toast.makeText(this, "API Key was not set in secrets.properties", Toast.LENGTH_LONG).show();
69+
throw new RuntimeException("API Key was not set in secrets.properties");
70+
}
71+
} catch (PackageManager.NameNotFoundException e) {
72+
Log.e(TAG, "Package name not found.", e);
73+
throw new RuntimeException("Error getting package info.", e);
74+
} catch (NullPointerException e) {
75+
Log.e(TAG, "Error accessing meta-data.", e); // Handle the case where meta-data is completely missing.
76+
throw new RuntimeException("Error accessing meta-data in manifest", e);
77+
}
78+
}
79+
80+
/**
81+
* Retrieves the map ID from the BuildConfig or string resource.
82+
*
83+
* @return The valid map ID or null if no valid map ID is found.
84+
*/
85+
@Nullable
86+
public String getMapId() {
87+
if (!mapIdSet) {
88+
if (!BuildConfig.MAP_ID.equals("MAP_ID")) {
89+
mapId = BuildConfig.MAP_ID;
90+
} else if (!getString(R.string.map_id).equals("DEMO_MAP_ID")) {
91+
mapId = getString(R.string.map_id);
92+
} else {
93+
Log.w(TAG, "Map ID is not set. See README for instructions.");
94+
Toast.makeText(this, "Map ID is not set. Some features may not work. See README for instructions.", Toast.LENGTH_LONG).show();
95+
}
96+
mapIdSet = true;
97+
}
98+
return mapId;
99+
}
100+
}

demo/src/main/res/layout/activity_cluster_algorithms_demo.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
android:layout_height="match_parent">
2121

2222
<!-- Map Fragment -->
23-
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
23+
<androidx.fragment.app.FragmentContainerView
2424
xmlns:map="http://schemas.android.com/apk/res-auto"
2525
android:id="@+id/map"
2626
android:name="com.google.android.gms.maps.SupportMapFragment"

demo/src/main/res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,7 @@
3535
<string name="button_opacity">Opacity</string>
3636
<string name="bad_maps_api_key">The Google Maps API key looks invalid or is missing. If you are sure your key is correct, you can remove this check from the demo.</string>
3737
<string name="percentage_format">%1$d%%</string>
38+
39+
<!-- This should be set here or in secrets.properties. -->
40+
<string name="map_id">DEMO_MAP_ID</string>
3841
</resources>

local.defaults.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
MAPS_API_KEY=YOUR_API_KEY
22
PLACES_API_KEY=YOUR_API_KEY
3+
MAP_ID=MAP_ID

0 commit comments

Comments
 (0)