@@ -16,17 +16,27 @@ import com.google.android.gms.location.LocationResult
1616import com.google.android.gms.location.LocationServices
1717import io.ionic.libs.iongeolocationlib.controller.helper.IONGLOCFallbackHelper
1818import io.ionic.libs.iongeolocationlib.controller.helper.IONGLOCGoogleServicesHelper
19+ import io.ionic.libs.iongeolocationlib.controller.helper.emitOrTimeoutBeforeFirstEmission
1920import io.ionic.libs.iongeolocationlib.controller.helper.toOSLocationResult
2021import io.ionic.libs.iongeolocationlib.model.IONGLOCException
2122import io.ionic.libs.iongeolocationlib.model.IONGLOCLocationOptions
2223import io.ionic.libs.iongeolocationlib.model.IONGLOCLocationResult
2324import io.ionic.libs.iongeolocationlib.model.internal.LocationHandler
2425import io.ionic.libs.iongeolocationlib.model.internal.LocationSettingsResult
26+ import kotlinx.coroutines.CancellationException
27+ import kotlinx.coroutines.ExperimentalCoroutinesApi
28+ import kotlinx.coroutines.FlowPreview
2529import kotlinx.coroutines.channels.awaitClose
30+ import kotlinx.coroutines.currentCoroutineContext
2631import kotlinx.coroutines.flow.Flow
2732import kotlinx.coroutines.flow.MutableSharedFlow
2833import kotlinx.coroutines.flow.callbackFlow
2934import kotlinx.coroutines.flow.first
35+ import kotlinx.coroutines.flow.flatMapConcat
36+ import kotlinx.coroutines.flow.flow
37+ import kotlinx.coroutines.flow.flowOf
38+ import kotlinx.coroutines.flow.onEach
39+ import kotlinx.coroutines.isActive
3040
3141/* *
3242 * Entry point in IONGeolocationLib-Android
@@ -127,44 +137,33 @@ class IONGLOCController internal constructor(
127137 * @param activity the Android activity from which the location request is being triggered
128138 * @param options location request options to use
129139 * @param watchId a unique id identifying the watch
130- * @return Flow in which location updates will be emitted
140+ * @return Flow in which location updates will be emitted, or failure if something went wrong in retrieving updates
131141 */
142+ @OptIn(FlowPreview ::class , ExperimentalCoroutinesApi ::class )
132143 fun addWatch (
133144 activity : Activity ,
134145 options : IONGLOCLocationOptions ,
135146 watchId : String
136- ): Flow <Result <List <IONGLOCLocationResult >>> = callbackFlow {
137- try {
138- fun onNewLocations (locations : List <Location >) {
139- if (checkWatchInBlackList(watchId)) {
140- return
141- }
142- val locationResultList = locations.map { currentLocation ->
143- currentLocation.toOSLocationResult()
144- }
145- trySend(Result .success(locationResultList))
146- }
147+ ): Flow <Result <List <IONGLOCLocationResult >>> {
147148
148- val checkResult: Result <Unit > =
149- checkLocationPreconditions(activity, options, isSingleLocationRequest = false )
150- if (checkResult.shouldNotProceed(options)) {
151- trySend(
152- Result .failure(checkResult.exceptionOrNull() ? : NullPointerException ())
153- )
149+ val setupFlow = watchSetupPreconditionsFlow(activity, options)
150+ // Concatenate flows: only proceed with watch if setup is successful
151+ return setupFlow.flatMapConcat { setupResult ->
152+ if (setupResult.isFailure) {
153+ flowOf(Result .failure(setupResult.exceptionOrNull() ? : NullPointerException ()))
154154 } else {
155- requestLocationUpdates(
156- watchId,
155+ watchLocationUpdatesFlow(
157156 options,
158- useFallback = checkResult.isFailure && options.enableLocationManagerFallback
159- ) { onNewLocations(it) }
157+ useFallback = setupResult.getOrNull() ? : false ,
158+ watchId
159+ )
160+ .emitOrTimeoutBeforeFirstEmission(timeoutMillis = options.timeout)
161+ .onEach { emission ->
162+ if (emission.exceptionOrNull() is IONGLOCException .IONGLOCLocationRetrievalTimeoutException ) {
163+ clearWatch(watchId)
164+ }
165+ }
160166 }
161- } catch (exception: Exception ) {
162- Log .d(LOG_TAG , " Error requesting location updates: ${exception.message} " )
163- trySend(Result .failure(exception))
164- }
165-
166- awaitClose {
167- clearWatch(watchId)
168167 }
169168 }
170169
@@ -175,6 +174,68 @@ class IONGLOCController internal constructor(
175174 */
176175 fun clearWatch (id : String ): Boolean = clearWatch(id, addToBlackList = true )
177176
177+ /* *
178+ * Create a flow for setup and checking preconditions for watch location
179+ * @param activity the Android activity from which the location request is being triggered
180+ * @param options location request options to use
181+ * @return Flow with success if pre-condition checks passed and boolean flag to decide whether or not fallback is required, or failure otherwise.
182+ */
183+ private fun watchSetupPreconditionsFlow (
184+ activity : Activity ,
185+ options : IONGLOCLocationOptions
186+ ): Flow <Result <Boolean >> = flow {
187+ try {
188+ val checkResult: Result <Unit > =
189+ checkLocationPreconditions(activity, options, isSingleLocationRequest = false )
190+ if (checkResult.shouldNotProceed(options)) {
191+ emit(Result .failure(checkResult.exceptionOrNull() ? : NullPointerException ()))
192+ } else {
193+ val useFallback = checkResult.isFailure && options.enableLocationManagerFallback
194+ emit(Result .success(useFallback))
195+ }
196+ } catch (exception: Exception ) {
197+ Log .d(LOG_TAG , " Error getting pre-conditions for watch: ${exception.message} " )
198+ if (currentCoroutineContext().isActive) {
199+ emit(Result .failure(exception))
200+ } else if (exception is CancellationException ) {
201+ throw exception
202+ }
203+ }
204+ }
205+
206+ /* *
207+ * Create a flow where location updates are emitted for a watch.
208+ * @param options location request options to use
209+ * @param useFallback whether or not the fallback should be used
210+ * @param watchId a unique id identifying the watch
211+ * @return Flow in which location updates will be emitted
212+ */
213+ private fun watchLocationUpdatesFlow (
214+ options : IONGLOCLocationOptions ,
215+ useFallback : Boolean ,
216+ watchId : String ,
217+ ): Flow <Result <List <IONGLOCLocationResult >>> = callbackFlow {
218+ fun onNewLocations (locations : List <Location >) {
219+ if (checkWatchInBlackList(watchId)) return
220+ val locationResultList = locations.map { it.toOSLocationResult() }
221+ trySend(Result .success(locationResultList))
222+ }
223+
224+ try {
225+ requestLocationUpdates(
226+ watchId,
227+ options,
228+ useFallback = useFallback
229+ ) { onNewLocations(it) }
230+ } catch (e: Exception ) {
231+ trySend(Result .failure(e))
232+ }
233+
234+ awaitClose {
235+ Log .d(LOG_TAG , " channel closed" )
236+ }
237+ }
238+
178239 /* *
179240 * Checks if all preconditions for retrieving location are met
180241 * @param activity the Android activity from which the location request is being triggered
0 commit comments