diff --git a/android/src/main/java/co/uk/hive/reactnativegeolocation/geofence/GeofenceController.java b/android/src/main/java/co/uk/hive/reactnativegeolocation/geofence/GeofenceController.java index 5392ade..4c2617a 100644 --- a/android/src/main/java/co/uk/hive/reactnativegeolocation/geofence/GeofenceController.java +++ b/android/src/main/java/co/uk/hive/reactnativegeolocation/geofence/GeofenceController.java @@ -2,6 +2,7 @@ import android.os.Build; import android.util.Log; + import com.annimon.stream.Optional; import com.annimon.stream.Stream; import com.annimon.stream.function.Function; @@ -26,12 +27,23 @@ public class GeofenceController { } public void start(Function successCallback, Function failureCallback) { - if (mGeofenceRepository.getGeofences().isEmpty()) { + start(successCallback, failureCallback, mGeofenceRepository.getGeofences()); + } + + public void start(Function successCallback, Function failureCallback, List geofences) { + if (geofences.isEmpty()) { Log.w(getClass().getSimpleName(), "Starting geofences with none set, exiting"); return; } - mGeofenceActivator.setGeofencesActivated(true); - mGeofenceEngine.addGeofences(mGeofenceRepository.getGeofences(), successCallback, failureCallback); + + mGeofenceEngine.addGeofences( + geofences, + successResult -> { + mGeofenceActivator.setGeofencesActivated(true); + return successCallback.apply(successResult); + }, + failureCallback + ); } public void stop(Function successCallback, Function failureCallback) { @@ -39,25 +51,44 @@ public void stop(Function successCallback, Funct Log.w(getClass().getSimpleName(), "Stopping geofences with none set, exiting"); return; } - mGeofenceActivator.setGeofencesActivated(false); - List geofenceIds = getGeofenceIds(); + + List geofenceIds = getGeofenceIdsToRemove(); if (!geofenceIds.isEmpty()) { - mGeofenceEngine.removeGeofences(geofenceIds, successCallback, failureCallback); + mGeofenceEngine.removeGeofences( + geofenceIds, + successResult -> { + mGeofenceActivator.setGeofencesActivated(false); + return successCallback.apply(successResult); + }, + failureCallback + ); } } public void restart(Function successCallback, Function failureCallback) { if (mGeofenceActivator.areGeofencesActivated()) { - start(successCallback, failureCallback); + final List geofences = Stream.of(mGeofenceRepository.getGeofences()) + .filterNot(GeofenceController::isInvalidGeofence) + .toList(); + + start(successCallback, failureCallback, geofences); } } - private List getGeofenceIds() { + private List getGeofenceIdsToRemove() { return Stream.of(mGeofenceRepository.getGeofences()) + .filterNot(GeofenceController::isInvalidGeofence) .map(Geofence::getId) .toList(); } + private static boolean isInvalidGeofence(Geofence geofence) { + // Removes geofences where `setGeofencesActivated` was set to `true` before crashing + // They cannot be restarted (it'll crash) and they cannot be removed (they were never added) + // Geofences added from 1.2.1 should never meet this condition + return geofence.getRadius() == 0; + } + public void addGeofences(List geofences) { mGeofenceRepository.addGeofences(geofences); } diff --git a/android/src/test/java/co/uk/hive/reactnativegeolocation/GeofenceControllerTest.java b/android/src/test/java/co/uk/hive/reactnativegeolocation/GeofenceControllerTest.java index fe5af8f..e09f39a 100644 --- a/android/src/test/java/co/uk/hive/reactnativegeolocation/GeofenceControllerTest.java +++ b/android/src/test/java/co/uk/hive/reactnativegeolocation/GeofenceControllerTest.java @@ -1,19 +1,30 @@ package co.uk.hive.reactnativegeolocation; -import co.uk.hive.reactnativegeolocation.geofence.*; +import com.annimon.stream.Stream; import com.annimon.stream.function.Function; + import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; import java.util.Arrays; import java.util.List; +import co.uk.hive.reactnativegeolocation.geofence.Geofence; +import co.uk.hive.reactnativegeolocation.geofence.GeofenceActivator; +import co.uk.hive.reactnativegeolocation.geofence.GeofenceController; +import co.uk.hive.reactnativegeolocation.geofence.GeofenceEngine; +import co.uk.hive.reactnativegeolocation.geofence.GeofenceRepository; + import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; @SuppressWarnings("Convert2Lambda") @@ -32,11 +43,18 @@ public class GeofenceControllerTest { @InjectMocks private GeofenceController mSut; - private List mGeofences = Arrays.asList( + private List mMixedGeofences = Arrays.asList( TestData.createGeofence("1"), - TestData.createGeofence("2")); + TestData.createGeofence("2"), + TestData.createInvalidGeofence("3") + ); + + private List mGeofences = mMixedGeofences.subList(0, mMixedGeofences.size() - 1); + + @Mock + private Function mSuccessCallback; - private Function mCallback = new Function() { + private Function mFailureCallback = new Function() { @Override public Object apply(Object ignored) { return null; @@ -46,20 +64,34 @@ public Object apply(Object ignored) { @Test public void startsGeofences() { given(mGeofenceRepository.getGeofences()).willReturn(mGeofences); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + return ((Function) invocation.getArgument(1)).apply(null); + } + }).when(mGeofenceEngine).addGeofences(anyList(), any(Function.class), any(Function.class)); - mSut.start(mCallback, mCallback); + mSut.start(mSuccessCallback, mFailureCallback); - verify(mGeofenceEngine).addGeofences(mGeofences, mCallback, mCallback); + verify(mGeofenceEngine).addGeofences(eq(mGeofences), any(Function.class), eq(mFailureCallback)); + verify(mSuccessCallback).apply(null); verify(mGeofenceActivator).setGeofencesActivated(true); } @Test public void stopsGeofences() { given(mGeofenceRepository.getGeofences()).willReturn(mGeofences); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + return ((Function) invocation.getArgument(1)).apply(null); + } + }).when(mGeofenceEngine).removeGeofences(anyList(), any(Function.class), any(Function.class)); - mSut.stop(mCallback, mCallback); + mSut.stop(mSuccessCallback, mFailureCallback); - verify(mGeofenceEngine).removeGeofences(Arrays.asList("1", "2"), mCallback, mCallback); + verify(mGeofenceEngine).removeGeofences(eq(Arrays.asList("1", "2")), any(Function.class), eq(mFailureCallback)); + verify(mSuccessCallback).apply(null); verify(mGeofenceActivator).setGeofencesActivated(false); } @@ -68,12 +100,33 @@ public void restartsGeofences() { given(mGeofenceRepository.getGeofences()).willReturn(mGeofences); given(mGeofenceActivator.areGeofencesActivated()).willReturn(true); - mSut.restart(mCallback, mCallback); + mSut.restart(mSuccessCallback, mFailureCallback); + + verify(mGeofenceActivator).areGeofencesActivated(); + verify(mGeofenceEngine).addGeofences(eq(mGeofences), any(), any()); + } + + @Test + public void filtersInvalidGeofencesOnRestart() { + given(mGeofenceRepository.getGeofences()).willReturn(mMixedGeofences); + given(mGeofenceActivator.areGeofencesActivated()).willReturn(true); + + mSut.restart(mSuccessCallback, mFailureCallback); verify(mGeofenceActivator).areGeofencesActivated(); verify(mGeofenceEngine).addGeofences(eq(mGeofences), any(), any()); } + @Test + public void filtersInvalidGeofencesOnStop() { + given(mGeofenceRepository.getGeofences()).willReturn(mMixedGeofences); + + mSut.stop(mSuccessCallback, mFailureCallback); + + final List expectedIds = Stream.of(mGeofences).map(Geofence::getId).toList(); + verify(mGeofenceEngine).removeGeofences(eq(expectedIds), any(Function.class), eq(mFailureCallback)); + } + @Test public void interactsWithRepository() { mSut.addGeofences(mGeofences); diff --git a/android/src/test/java/co/uk/hive/reactnativegeolocation/TestData.java b/android/src/test/java/co/uk/hive/reactnativegeolocation/TestData.java index 20c1ef9..68d5b97 100644 --- a/android/src/test/java/co/uk/hive/reactnativegeolocation/TestData.java +++ b/android/src/test/java/co/uk/hive/reactnativegeolocation/TestData.java @@ -4,6 +4,10 @@ class TestData { static Geofence createGeofence(String id) { + return new Geofence(id, 1, 0, 0, false, false, false, 0); + } + + static Geofence createInvalidGeofence(String id) { return new Geofence(id, 0, 0, 0, false, false, false, 0); } } diff --git a/package.json b/package.json index b5f5dc1..edf7e72 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@connected-home/react-native-geolocation", - "version": "1.2.0", + "version": "1.2.1", "description": "Basic geolocation + geofencing. Delegates to react-native-background-geolocation for iOS.", "main": "index.js", "scripts": {