From 96a64a8cd9d95ec61a93e1421b75930e5e057c75 Mon Sep 17 00:00:00 2001 From: Matt Creaser Date: Mon, 5 Dec 2022 12:30:09 -0400 Subject: [PATCH] Add Geo Rx Bindings --- .idea/codeStyles/Project.xml | 8 +- rxbindings/build.gradle | 7 +- .../com/amplifyframework/rx/RxAmplify.java | 1 + .../com/amplifyframework/rx/RxGeoBinding.java | 95 ++++++ .../rx/RxGeoCategoryBehavior.java | 114 +++++++ .../amplifyframework/rx/RxGeoBindingTest.kt | 279 ++++++++++++++++++ 6 files changed, 502 insertions(+), 2 deletions(-) create mode 100644 rxbindings/src/main/java/com/amplifyframework/rx/RxGeoBinding.java create mode 100644 rxbindings/src/main/java/com/amplifyframework/rx/RxGeoCategoryBehavior.java create mode 100644 rxbindings/src/test/java/com/amplifyframework/rx/RxGeoBindingTest.kt diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 74709d9df5..df19e986dc 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,5 +1,8 @@ + + - \ No newline at end of file + diff --git a/rxbindings/build.gradle b/rxbindings/build.gradle index 78061b9c03..72ffa620c3 100644 --- a/rxbindings/build.gradle +++ b/rxbindings/build.gradle @@ -13,7 +13,11 @@ * permissions and limitations under the License. */ -apply plugin: 'com.android.library' +plugins { + id "com.android.library" + id "kotlin-android" +} + apply from: rootProject.file("configuration/checkstyle.gradle") apply from: rootProject.file("configuration/publishing.gradle") @@ -26,6 +30,7 @@ dependencies { testImplementation project(path: ':testutils') testImplementation dependency.junit testImplementation dependency.mockito + testImplementation dependency.mockk testImplementation dependency.androidx.test.core testImplementation dependency.robolectric testImplementation project(path: ':rxbindings') diff --git a/rxbindings/src/main/java/com/amplifyframework/rx/RxAmplify.java b/rxbindings/src/main/java/com/amplifyframework/rx/RxAmplify.java index b4f144f81c..1216307aa3 100644 --- a/rxbindings/src/main/java/com/amplifyframework/rx/RxAmplify.java +++ b/rxbindings/src/main/java/com/amplifyframework/rx/RxAmplify.java @@ -41,6 +41,7 @@ private RxAmplify() {} @SuppressWarnings({"checkstyle:all"}) public static final RxHubCategoryBehavior Hub = new RxHubBinding(); @SuppressWarnings({"checkstyle:all"}) public static final RxDataStoreCategoryBehavior DataStore = new RxDataStoreBinding(); @SuppressWarnings({"checkstyle:all"}) public static final RxPredictionsCategoryBehavior Predictions = new RxPredictionsBinding(); + @SuppressWarnings({"checkstyle:all"}) public static final RxGeoCategoryBehavior Geo = new RxGeoBinding(); /** * Read the configuration from amplifyconfiguration.json file. diff --git a/rxbindings/src/main/java/com/amplifyframework/rx/RxGeoBinding.java b/rxbindings/src/main/java/com/amplifyframework/rx/RxGeoBinding.java new file mode 100644 index 0000000000..3042ed753a --- /dev/null +++ b/rxbindings/src/main/java/com/amplifyframework/rx/RxGeoBinding.java @@ -0,0 +1,95 @@ +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.amplifyframework.rx; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +import com.amplifyframework.core.Amplify; +import com.amplifyframework.geo.GeoCategoryBehavior; +import com.amplifyframework.geo.GeoException; +import com.amplifyframework.geo.models.Coordinates; +import com.amplifyframework.geo.models.MapStyle; +import com.amplifyframework.geo.models.MapStyleDescriptor; +import com.amplifyframework.geo.options.GeoSearchByCoordinatesOptions; +import com.amplifyframework.geo.options.GeoSearchByTextOptions; +import com.amplifyframework.geo.options.GetMapStyleDescriptorOptions; +import com.amplifyframework.geo.result.GeoSearchResult; + +import java.util.Collection; +import java.util.Objects; + +import io.reactivex.rxjava3.core.Single; + +final class RxGeoBinding implements RxGeoCategoryBehavior { + private final GeoCategoryBehavior delegate; + + RxGeoBinding() { + delegate = Amplify.Geo; + } + + @VisibleForTesting + RxGeoBinding(@NonNull GeoCategoryBehavior delegate) { + this.delegate = Objects.requireNonNull(delegate); + } + + @Override + public Single> getAvailableMaps() { + return toSingle(delegate::getAvailableMaps); + } + + @Override + public Single getDefaultMap() { + return toSingle(delegate::getDefaultMap); + } + + @Override + public Single getMapStyleDescriptor() { + return toSingle(delegate::getMapStyleDescriptor); + } + + @Override + public Single getMapStyleDescriptor(@NonNull GetMapStyleDescriptorOptions options) { + return toSingle((onResult, onError) -> delegate.getMapStyleDescriptor(options, onResult, onError)); + } + + @Override + public Single searchByText(@NonNull String query) { + return toSingle((onResult, onError) -> delegate.searchByText(query, onResult, onError)); + } + + @Override + public Single searchByText(@NonNull String query, @NonNull GeoSearchByTextOptions options) { + return toSingle((onResult, onError) -> delegate.searchByText(query, options, onResult, onError)); + } + + @Override + public Single searchByCoordinates(@NonNull Coordinates position) { + return toSingle((onResult, onError) -> delegate.searchByCoordinates(position, onResult, onError)); + } + + @Override + public Single searchByCoordinates( + @NonNull Coordinates position, + @NonNull GeoSearchByCoordinatesOptions options + ) { + return toSingle((onResult, onError) -> delegate.searchByCoordinates(position, options, onResult, onError)); + } + + private static Single toSingle(RxAdapters.VoidBehaviors.ResultEmitter behavior) { + return RxAdapters.VoidBehaviors.toSingle(behavior); + } +} diff --git a/rxbindings/src/main/java/com/amplifyframework/rx/RxGeoCategoryBehavior.java b/rxbindings/src/main/java/com/amplifyframework/rx/RxGeoCategoryBehavior.java new file mode 100644 index 0000000000..35d580aa2c --- /dev/null +++ b/rxbindings/src/main/java/com/amplifyframework/rx/RxGeoCategoryBehavior.java @@ -0,0 +1,114 @@ +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.amplifyframework.rx; + +import androidx.annotation.NonNull; + +import com.amplifyframework.geo.GeoCategoryBehavior; +import com.amplifyframework.geo.GeoException; +import com.amplifyframework.geo.models.Coordinates; +import com.amplifyframework.geo.models.MapStyle; +import com.amplifyframework.geo.models.MapStyleDescriptor; +import com.amplifyframework.geo.options.GeoSearchByCoordinatesOptions; +import com.amplifyframework.geo.options.GeoSearchByTextOptions; +import com.amplifyframework.geo.options.GetMapStyleDescriptorOptions; +import com.amplifyframework.geo.result.GeoSearchResult; + +import java.util.Collection; + +import io.reactivex.rxjava3.core.Single; + +/** + * An Rx-idiomatic expression of the behaviors in {@link GeoCategoryBehavior}. + */ +public interface RxGeoCategoryBehavior { + /** + * Gets a collection of maps and their corresponding styles. + * + * @return An Rx {@link Single} which emits a {@link MapStyle} on success, or a + * {@link GeoException} on failure + */ + Single> getAvailableMaps(); + + /** + * Gets the default map and style from available maps. + * + * @return An Rx {@link Single} which emits a {@link MapStyle} on success, or a + * {@link GeoException} on failure + */ + Single getDefaultMap(); + + /** + * Uses default options to get map style descriptor JSON. + * + * @return An Rx {@link Single} which emits a {@link MapStyleDescriptor} on success, or a + * {@link GeoException} on failure + */ + Single getMapStyleDescriptor(); + + /** + * Uses given options to get map style descriptor JSON. + * + * @param options Options to specify for this operation. + * @return An Rx {@link Single} which emits a {@link MapStyleDescriptor} on success, or a + * {@link GeoException} on failure + */ + Single getMapStyleDescriptor(@NonNull GetMapStyleDescriptorOptions options); + + /** + * Searches for locations that match text query. + * + * @param query Search query text. + * @return An Rx {@link Single} which emits a {@link GeoSearchResult} on success, or a + * {@link GeoException} on failure + */ + Single searchByText(@NonNull String query); + + /** + * Searches for locations that match text query. + * + * @param query Search query text. + * @param options Search options to use. + * @return An Rx {@link Single} which emits a {@link GeoSearchResult} on success, or a + * {@link GeoException} on failure + */ + Single searchByText( + @NonNull String query, + @NonNull GeoSearchByTextOptions options + ); + + /** + * Searches for location with given set of coordinates. + * + * @param position Coordinates to look-up. + * @return An Rx {@link Single} which emits a {@link GeoSearchResult} on success, or a + * {@link GeoException} on failure + */ + Single searchByCoordinates(@NonNull Coordinates position); + + /** + * Searches for location with given set of coordinates. + * + * @param position Coordinates to look-up. + * @param options Search options to use. + * @return An Rx {@link Single} which emits a {@link GeoSearchResult} on success, or a + * {@link GeoException} on failure + */ + Single searchByCoordinates( + @NonNull Coordinates position, + @NonNull GeoSearchByCoordinatesOptions options + ); +} diff --git a/rxbindings/src/test/java/com/amplifyframework/rx/RxGeoBindingTest.kt b/rxbindings/src/test/java/com/amplifyframework/rx/RxGeoBindingTest.kt new file mode 100644 index 0000000000..ce95160611 --- /dev/null +++ b/rxbindings/src/test/java/com/amplifyframework/rx/RxGeoBindingTest.kt @@ -0,0 +1,279 @@ +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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.amplifyframework.rx + +import com.amplifyframework.core.Consumer +import com.amplifyframework.geo.GeoCategoryBehavior +import com.amplifyframework.geo.GeoException +import com.amplifyframework.geo.models.Coordinates +import com.amplifyframework.geo.models.MapStyle +import com.amplifyframework.geo.models.MapStyleDescriptor +import com.amplifyframework.geo.options.GeoSearchByCoordinatesOptions +import com.amplifyframework.geo.options.GeoSearchByTextOptions +import com.amplifyframework.geo.options.GetMapStyleDescriptorOptions +import com.amplifyframework.geo.result.GeoSearchResult +import io.mockk.every +import io.mockk.mockk +import java.util.concurrent.TimeUnit +import org.junit.Test + +private const val TIMEOUT_SECONDS = 2L + +/** + * Unit tests for the [RxGeoBinding] class + */ +class RxGeoBindingTest { + private val delegate = mockk() + private val geo = RxGeoBinding(delegate) + + @Test + fun `returns available maps`() { + val maps = listOf(MapStyle("a", "b"), MapStyle("c", "d")) + every { delegate.getAvailableMaps(any(), any()) } answers { + val callback = firstArg>>() + callback.accept(maps) + } + + val observer = geo.availableMaps.test() + observer.await(TIMEOUT_SECONDS, TimeUnit.SECONDS) + + observer.assertNoErrors().assertValue(maps) + } + + @Test + fun `returns error for available maps`() { + val error = GeoException("message", "suggestion") + every { delegate.getAvailableMaps(any(), any()) } answers { + val callback = lastArg>() + callback.accept(error) + } + + val observer = geo.availableMaps.test() + observer.await(TIMEOUT_SECONDS, TimeUnit.SECONDS) + + observer.assertNoValues().assertError(error) + } + + @Test + fun `returns default map`() { + val map = MapStyle("map", "style") + every { delegate.getDefaultMap(any(), any()) } answers { + val callback = firstArg>() + callback.accept(map) + } + + val observer = geo.defaultMap.test() + observer.await(TIMEOUT_SECONDS, TimeUnit.SECONDS) + + observer.assertNoErrors().assertValue(map) + } + + @Test + fun `returns error for default map`() { + val error = GeoException("message", "suggestion") + every { delegate.getDefaultMap(any(), any()) } answers { + val callback = lastArg>() + callback.accept(error) + } + + val observer = geo.defaultMap.test() + observer.await(TIMEOUT_SECONDS, TimeUnit.SECONDS) + + observer.assertNoValues().assertError(error) + } + + @Test + fun `returns map style descriptor`() { + val descriptor = MapStyleDescriptor("") + every { delegate.getMapStyleDescriptor(any(), any()) } answers { + val callback = firstArg>() + callback.accept(descriptor) + } + + val observer = geo.mapStyleDescriptor.test() + observer.await(TIMEOUT_SECONDS, TimeUnit.SECONDS) + + observer.assertNoErrors().assertValue(descriptor) + } + + @Test + fun `returns map style descriptor with options`() { + val options = GetMapStyleDescriptorOptions.builder().mapName("map").build() + val descriptor = MapStyleDescriptor("") + every { delegate.getMapStyleDescriptor(options, any(), any()) } answers { + val callback = secondArg>() + callback.accept(descriptor) + } + + val observer = geo.getMapStyleDescriptor(options).test() + observer.await(TIMEOUT_SECONDS, TimeUnit.SECONDS) + + observer.assertNoErrors().assertValue(descriptor) + } + + @Test + fun `returns error for map style descriptor`() { + val error = GeoException("message", "suggestion") + every { delegate.getMapStyleDescriptor(any(), any()) } answers { + val callback = lastArg>() + callback.accept(error) + } + + val observer = geo.mapStyleDescriptor.test() + observer.await(TIMEOUT_SECONDS, TimeUnit.SECONDS) + + observer.assertNoValues().assertError(error) + } + + @Test + fun `returns error for map style descriptor with options`() { + val error = GeoException("message", "suggestion") + val options = GetMapStyleDescriptorOptions.builder().mapName("map").build() + every { delegate.getMapStyleDescriptor(options, any(), any()) } answers { + val callback = lastArg>() + callback.accept(error) + } + + val observer = geo.getMapStyleDescriptor(options).test() + observer.await(TIMEOUT_SECONDS, TimeUnit.SECONDS) + + observer.assertNoValues().assertError(error) + } + + @Test + fun `returns search by text`() { + val query = "query" + val searchResult = GeoSearchResult.withPlaces(emptyList()) + every { delegate.searchByText(query, any(), any()) } answers { + val callback = secondArg>() + callback.accept(searchResult) + } + + val observer = geo.searchByText(query).test() + observer.await(TIMEOUT_SECONDS, TimeUnit.SECONDS) + + observer.assertNoErrors().assertValue(searchResult) + } + + @Test + fun `returns search by text with options`() { + val query = "query" + val options = GeoSearchByTextOptions.builder().maxResults(2).build() + val searchResult = GeoSearchResult.withPlaces(emptyList()) + every { delegate.searchByText(query, options, any(), any()) } answers { + val callback = thirdArg>() + callback.accept(searchResult) + } + + val observer = geo.searchByText(query, options).test() + observer.await(TIMEOUT_SECONDS, TimeUnit.SECONDS) + + observer.assertNoErrors().assertValue(searchResult) + } + + @Test + fun `returns error for search by text`() { + val query = "query" + val error = GeoException("message", "suggestion") + every { delegate.searchByText(query, any(), any()) } answers { + val callback = lastArg>() + callback.accept(error) + } + + val observer = geo.searchByText(query).test() + observer.await(TIMEOUT_SECONDS, TimeUnit.SECONDS) + + observer.assertNoValues().assertError(error) + } + + @Test + fun `returns error for search by text with options`() { + val query = "query" + val options = GeoSearchByTextOptions.builder().maxResults(5).build() + val error = GeoException("message", "suggestion") + every { delegate.searchByText(query, options, any(), any()) } answers { + val callback = lastArg>() + callback.accept(error) + } + + val observer = geo.searchByText(query, options).test() + observer.await(TIMEOUT_SECONDS, TimeUnit.SECONDS) + + observer.assertNoValues().assertError(error) + } + + @Test + fun `returns search by coordinates`() { + val coordinates = Coordinates(2.0, 5.0) + val searchResult = GeoSearchResult.withPlaces(emptyList()) + every { delegate.searchByCoordinates(coordinates, any(), any()) } answers { + val callback = secondArg>() + callback.accept(searchResult) + } + + val observer = geo.searchByCoordinates(coordinates).test() + observer.await(TIMEOUT_SECONDS, TimeUnit.SECONDS) + + observer.assertNoErrors().assertValue(searchResult) + } + + @Test + fun `returns search by coordinates with options`() { + val coordinates = Coordinates(2.0, 5.0) + val options = GeoSearchByCoordinatesOptions.builder().maxResults(6).build() + val searchResult = GeoSearchResult.withPlaces(emptyList()) + every { delegate.searchByCoordinates(coordinates, options, any(), any()) } answers { + val callback = thirdArg>() + callback.accept(searchResult) + } + + val observer = geo.searchByCoordinates(coordinates, options).test() + observer.await(TIMEOUT_SECONDS, TimeUnit.SECONDS) + + observer.assertNoErrors().assertValue(searchResult) + } + + @Test + fun `returns error for search by coordinates`() { + val coordinates = Coordinates(3.0, 4.0) + val error = GeoException("message", "suggestion") + every { delegate.searchByCoordinates(coordinates, any(), any()) } answers { + val callback = lastArg>() + callback.accept(error) + } + + val observer = geo.searchByCoordinates(coordinates).test() + observer.await(TIMEOUT_SECONDS, TimeUnit.SECONDS) + + observer.assertNoValues().assertError(error) + } + + @Test + fun `returns error for search by coordinates with options`() { + val coordinates = Coordinates(3.0, 4.0) + val options = GeoSearchByCoordinatesOptions.builder().maxResults(6).build() + val error = GeoException("message", "suggestion") + every { delegate.searchByCoordinates(coordinates, options, any(), any()) } answers { + val callback = lastArg>() + callback.accept(error) + } + + val observer = geo.searchByCoordinates(coordinates, options).test() + observer.await(TIMEOUT_SECONDS, TimeUnit.SECONDS) + + observer.assertNoValues().assertError(error) + } +}