Skip to content

Commit e0dd0a9

Browse files
committed
feat(demo): Add Java demo for Street View utility
This commit adds a new demo activity written in Java to demonstrate the usage of the Street View utility. To facilitate this, a Kotlin helper has been introduced to bridge the gap between suspend functions and Java's callback-based asynchronous model. The key changes are: - **`StreetViewDemoActivityJava`**: A new demo activity that shows how to check for Street View availability from Java. - **`StreetViewHelper`**: A Kotlin object that wraps the `suspend` function `StreetViewUtils.fetchStreetViewData` and exposes it to Java consumers via a callback interface. - **`ApiKeyValidator`**: A new utility class to check for a valid Maps API key within the demo application. - **Unit Tests**: Added tests for `StreetViewHelper` using Robolectric, MockK, and coroutine testing libraries to verify its behavior. - **Dependencies**: Added `mockk`, `kotlinx-coroutines-test`, and `robolectric` to the demo module's test dependencies.
1 parent db84504 commit e0dd0a9

8 files changed

Lines changed: 322 additions & 0 deletions

File tree

demo/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ dependencies {
6969

7070
testImplementation(libs.junit)
7171
testImplementation(libs.truth)
72+
testImplementation(libs.mockk)
73+
testImplementation(libs.kotlinx.coroutines.test)
74+
testImplementation(libs.robolectric)
7275
// [END_EXCLUDE]
7376
}
7477
// [END maps_android_utils_install_snippet]

demo/src/main/AndroidManifest.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@
124124
<activity
125125
android:name=".StreetViewDemoActivity"
126126
android:exported="true" />
127+
<activity
128+
android:name=".StreetViewDemoActivityJava"
129+
android:exported="true" />
127130
<activity
128131
android:name=".ClusterAlgorithmsDemoActivity"
129132
android:exported="true" />
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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.content.Context;
20+
import android.content.pm.PackageManager;
21+
import android.os.Bundle;
22+
23+
import java.util.regex.Pattern;
24+
25+
/**
26+
* A utility class to validate the Maps API key.
27+
*/
28+
class ApiKeyValidator {
29+
private static final String REGEX = "^AIza[0-9A-Za-z-_]{35}$";
30+
private static final Pattern PATTERN = Pattern.compile(REGEX);
31+
32+
/**
33+
* Checks if the provided context has a valid Google Maps API key in its metadata.
34+
*
35+
* @param context The context to check for the API key.
36+
* @return `true` if the context has a valid API key, `false` otherwise.
37+
*/
38+
static boolean hasMapsApiKey(Context context) {
39+
String mapsApiKey = getMapsApiKey(context);
40+
return mapsApiKey != null && PATTERN.matcher(mapsApiKey).matches();
41+
}
42+
43+
/**
44+
* Retrieves the Google Maps API key from the application metadata.
45+
*
46+
* @param context The context to retrieve the API key from.
47+
* @return The API key if found, `null` otherwise.
48+
*/
49+
private static String getMapsApiKey(Context context) {
50+
try {
51+
Bundle bundle = context.getPackageManager()
52+
.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA)
53+
.metaData;
54+
return bundle.getString("com.google.android.geo.API_KEY");
55+
} catch (PackageManager.NameNotFoundException e) {
56+
e.printStackTrace();
57+
return null;
58+
}
59+
}
60+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ protected void onCreate(Bundle savedInstanceState) {
8787
addDemo("Multi Layer", MultiLayerDemoActivity.class);
8888
addDemo("AnimationUtil sample", AnimationUtilDemoActivity.class);
8989
addDemo("Street View Demo", StreetViewDemoActivity.class);
90+
addDemo("Street View Demo (Java)", StreetViewDemoActivityJava.class);
9091
}
9192

9293
private void addDemo(String demoName, Class<? extends Activity> activityClass) {
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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.annotation.SuppressLint;
20+
import android.app.Activity;
21+
import android.os.Bundle;
22+
import android.widget.TextView;
23+
import android.widget.Toast;
24+
25+
import com.google.android.gms.maps.model.LatLng;
26+
import com.google.maps.android.Status;
27+
import com.google.maps.android.utils.demo.BuildConfig;
28+
import com.google.maps.android.utils.demo.R;
29+
30+
/**
31+
* This activity demonstrates how to use the Street View utility in Java.
32+
*/
33+
public class StreetViewDemoActivityJava extends Activity {
34+
35+
@SuppressLint("SetTextI18n")
36+
@Override
37+
protected void onCreate(Bundle savedInstanceState) {
38+
super.onCreate(savedInstanceState);
39+
setContentView(R.layout.street_view_demo);
40+
41+
if (!ApiKeyValidator.hasMapsApiKey(this)) {
42+
Toast.makeText(this, R.string.bad_maps_api_key, Toast.LENGTH_LONG).show();
43+
finish();
44+
return;
45+
}
46+
47+
// Fetches Street View data for the first location.
48+
StreetViewHelper.fetchStreetViewData(
49+
new LatLng(48.1425918, 11.5386121),
50+
BuildConfig.MAPS_API_KEY,
51+
new StreetViewHelper.StreetViewCallback() {
52+
@Override
53+
public void onStreetViewResult(Status status) {
54+
// Updates the UI with the result.
55+
runOnUiThread(() -> {
56+
((TextView) findViewById(R.id.textViewFirstLocation)).setText("Location 1 is supported in StreetView: " + status);
57+
});
58+
}
59+
60+
@Override
61+
public void onStreetViewError(Exception e) {
62+
// Handles the error.
63+
e.printStackTrace();
64+
Toast.makeText(StreetViewDemoActivityJava.this, "Error fetching Street View data: " + e.getMessage(), Toast.LENGTH_SHORT).show();
65+
}
66+
}
67+
);
68+
69+
// Fetches Street View data for the second location.
70+
StreetViewHelper.fetchStreetViewData(
71+
new LatLng(8.1425918, 11.5386121),
72+
BuildConfig.MAPS_API_KEY,
73+
new StreetViewHelper.StreetViewCallback() {
74+
@Override
75+
public void onStreetViewResult(Status status) {
76+
// Updates the UI with the result.
77+
runOnUiThread(() -> {
78+
((TextView) findViewById(R.id.textViewSecondLocation)).setText("Location 2 is supported in StreetView: " + status);
79+
});
80+
}
81+
82+
@Override
83+
public void onStreetViewError(Exception e) {
84+
// Handles the error.
85+
e.printStackTrace();
86+
Toast.makeText(StreetViewDemoActivityJava.this, "Error fetching Street View data: " + e.getMessage(), Toast.LENGTH_SHORT).show();
87+
}
88+
}
89+
);
90+
}
91+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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 com.google.android.gms.maps.model.LatLng
20+
import com.google.maps.android.StreetViewUtils
21+
import com.google.maps.android.Status
22+
import kotlinx.coroutines.CoroutineScope
23+
import kotlinx.coroutines.Dispatchers
24+
import kotlinx.coroutines.launch
25+
26+
/**
27+
* A helper object to call the suspend function `fetchStreetViewData` from Java.
28+
*/
29+
object StreetViewHelper {
30+
/**
31+
* A callback interface to receive the result of the Street View data fetch.
32+
*/
33+
interface StreetViewCallback {
34+
/**
35+
* Called when the Street View data is fetched successfully.
36+
*
37+
* @param status The status of the Street View data.
38+
*/
39+
fun onStreetViewResult(status: Status)
40+
41+
/**
42+
* Called when there is an error fetching the Street View data.
43+
*
44+
* @param e The exception that occurred.
45+
*/
46+
fun onStreetViewError(e: Exception)
47+
}
48+
49+
/**
50+
* Fetches Street View data for the given location and returns the result via a callback.
51+
*
52+
* @param latLng The location to fetch Street View data for.
53+
* @param apiKey The API key to use for the request.
54+
* @param callback The callback to receive the result.
55+
*/
56+
@JvmStatic
57+
fun fetchStreetViewData(latLng: LatLng, apiKey: String, callback: StreetViewCallback) {
58+
CoroutineScope(Dispatchers.Main).launch {
59+
try {
60+
val status = StreetViewUtils.fetchStreetViewData(latLng, apiKey)
61+
callback.onStreetViewResult(status)
62+
} catch (e: Exception) {
63+
callback.onStreetViewError(e)
64+
}
65+
}
66+
}
67+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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 com.google.android.gms.maps.model.LatLng
20+
import com.google.maps.android.Status
21+
import com.google.maps.android.StreetViewUtils
22+
import io.mockk.coEvery
23+
import io.mockk.mockk
24+
import io.mockk.mockkObject
25+
import io.mockk.verify
26+
import kotlinx.coroutines.Dispatchers
27+
import kotlinx.coroutines.ExperimentalCoroutinesApi
28+
import kotlinx.coroutines.test.StandardTestDispatcher
29+
import kotlinx.coroutines.test.resetMain
30+
import kotlinx.coroutines.test.runTest
31+
import kotlinx.coroutines.test.setMain
32+
import org.junit.After
33+
import org.junit.Before
34+
import org.junit.Test
35+
import org.junit.runner.RunWith
36+
import org.robolectric.RobolectricTestRunner
37+
38+
/**
39+
* Tests for [StreetViewHelper].
40+
*/
41+
@ExperimentalCoroutinesApi
42+
@RunWith(RobolectricTestRunner::class)
43+
class StreetViewHelperTest {
44+
45+
private val testDispatcher = StandardTestDispatcher()
46+
47+
@Before
48+
fun setUp() {
49+
Dispatchers.setMain(testDispatcher)
50+
mockkObject(StreetViewUtils)
51+
}
52+
53+
@After
54+
fun tearDown() {
55+
Dispatchers.resetMain()
56+
}
57+
58+
/**
59+
* Tests that [StreetViewHelper.fetchStreetViewData] calls the onStreetViewResult callback with the OK status when the call is successful.
60+
*/
61+
@Test
62+
fun `fetchStreetViewData should call onStreetViewResult with OK status`() = runTest {
63+
// Arrange
64+
val latLng = LatLng(1.0, 2.0)
65+
val apiKey = "some_api_key"
66+
val callback = mockk<StreetViewHelper.StreetViewCallback>(relaxed = true)
67+
coEvery { StreetViewUtils.fetchStreetViewData(latLng, apiKey) } returns Status.OK
68+
69+
// Act
70+
StreetViewHelper.fetchStreetViewData(latLng, apiKey, callback)
71+
testDispatcher.scheduler.advanceUntilIdle()
72+
73+
// Assert
74+
verify { callback.onStreetViewResult(Status.OK) }
75+
}
76+
77+
/**
78+
* Tests that [StreetViewHelper.fetchStreetViewData] calls the onStreetViewError callback when an exception occurs.
79+
*/
80+
@Test
81+
fun `fetchStreetViewData should call onStreetViewError when an exception occurs`() = runTest {
82+
// Arrange
83+
val latLng = LatLng(1.0, 2.0)
84+
val apiKey = "some_api_key"
85+
val callback = mockk<StreetViewHelper.StreetViewCallback>(relaxed = true)
86+
val exception = Exception("some_error")
87+
coEvery { StreetViewUtils.fetchStreetViewData(latLng, apiKey) } throws exception
88+
89+
// Act
90+
StreetViewHelper.fetchStreetViewData(latLng, apiKey, callback)
91+
testDispatcher.scheduler.advanceUntilIdle()
92+
93+
// Assert
94+
verify { callback.onStreetViewError(exception) }
95+
}
96+
}

gradle/libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx
3535
kotlin-stdlib-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
3636
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
3737
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
38+
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
3839
junit = { module = "junit:junit", version.ref = "junit" }
3940
mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito-core" }
4041
secrets-gradle-plugin = { module = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin", version.ref = "secrets-gradle-plugin" }

0 commit comments

Comments
 (0)