Skip to content

Commit

Permalink
create helper for geocoder and handle exceptions
Browse files Browse the repository at this point in the history
  • Loading branch information
saleniuk committed Jun 28, 2024
1 parent 8adf422 commit 64077c8
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.ui.home.messagecomposer.location

import android.location.Geocoder
import android.location.Location
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class GeocoderHelper @Inject constructor(private val geocoder: Geocoder) {

fun getGeoLocatedAddress(location: Location): GeoLocatedAddress =
try {
geocoder.getFromLocation(location.latitude, location.longitude, 1).orEmpty()
} catch (e: Exception) {
emptyList()
}.let { addressList ->
GeoLocatedAddress(addressList.firstOrNull(), location)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package com.wire.android.ui.home.messagecomposer.location

import android.annotation.SuppressLint
import android.content.Context
import android.location.Geocoder
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
Expand Down Expand Up @@ -49,7 +48,7 @@ class LocationPickerHelper @Inject constructor(
@ApplicationContext private val context: Context,
@ApplicationScope private val scope: CoroutineScope,
private val currentTimestampProvider: CurrentTimestampProvider,
private val geocoder: Geocoder,
private val geocoderHelper: GeocoderHelper,
private val parameters: LocationPickerParameters,
) {

Expand All @@ -67,7 +66,8 @@ class LocationPickerHelper @Inject constructor(
lastLocation != null
&& currentTimestampProvider() - lastLocation.time <= parameters.lastLocationTimeLimit.inWholeMilliseconds
) {
onSuccess(lastLocation.toGeoLocatedAddress()) // use last known location if present and not older than given limit
// use last known location if present and not older than given limit
onSuccess(geocoderHelper.getGeoLocatedAddress(lastLocation))
} else {
locationManager.requestCurrentLocationWithoutGms(onSuccess, onError)
}
Expand Down Expand Up @@ -98,7 +98,7 @@ class LocationPickerHelper @Inject constructor(
val consumer: Consumer<Location?> = Consumer { location ->
timeoutJob.cancel()
if (location != null) {
onSuccess(location.toGeoLocatedAddress())
onSuccess(geocoderHelper.getGeoLocatedAddress(location))
} else {
onError()
}
Expand All @@ -107,7 +107,7 @@ class LocationPickerHelper @Inject constructor(
} else {
val listener = LocationListener { location ->
timeoutJob.cancel()
onSuccess(location.toGeoLocatedAddress())
onSuccess(geocoderHelper.getGeoLocatedAddress(location))
}
cancellationSignal.setOnCancelListener {
this.removeUpdates(listener)
Expand All @@ -121,11 +121,6 @@ class LocationPickerHelper @Inject constructor(
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
return LocationManagerCompat.isLocationEnabled(locationManager)
}

private fun Location.toGeoLocatedAddress(): GeoLocatedAddress =
geocoder.getFromLocation(latitude, longitude, 1).orEmpty().let { addressList ->
GeoLocatedAddress(addressList.firstOrNull(), this)
}
}

data class LocationPickerParameters(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package com.wire.android.ui.home.messagecomposer.location

import android.annotation.SuppressLint
import android.content.Context
import android.location.Geocoder
import com.google.android.gms.location.LocationServices
import com.google.android.gms.location.Priority
import com.google.android.gms.tasks.CancellationTokenSource
Expand All @@ -31,7 +30,7 @@ import javax.inject.Singleton
@Singleton
class LocationPickerHelperFlavor @Inject constructor(
private val context: Context,
private val geocoder: Geocoder,
private val geocoderHelper: GeocoderHelper,
private val locationPickerHelper: LocationPickerHelper,
) {
suspend fun getLocation(onSuccess: (GeoLocatedAddress) -> Unit, onError: () -> Unit) {
Expand All @@ -58,8 +57,7 @@ class LocationPickerHelperFlavor @Inject constructor(
val locationProvider = LocationServices.getFusedLocationProviderClient(context)
val currentLocation =
locationProvider.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, CancellationTokenSource().token).await()
val address = geocoder.getFromLocation(currentLocation.latitude, currentLocation.longitude, 1).orEmpty()
onSuccess(GeoLocatedAddress(address.firstOrNull(), currentLocation))
onSuccess(geocoderHelper.getGeoLocatedAddress(currentLocation))
} else {
onError()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.ui.home.messagecomposer.location

import android.location.Address
import android.location.Geocoder
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.test.runTest
import okio.IOException
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test

class GeocoderHelperTest {

@Test
fun `given non-null result, when getting geocoder address, then return result with address`() = runTest {
// given
val location = mockLocation(latitude = 1.0, longitude = 1.0)
val address = mockAddress(addressFirstLine = "address")
val (_, geocoderHelper) = Arrangement()
.withGetFromLocation(1.0, 1.0, address)
.arrange()

// when
val result = geocoderHelper.getGeoLocatedAddress(location)

// then
assertEquals(address, result.address)
}

@Test
fun `given empty result, when getting geocoder address, then return result without address`() = runTest {
// given
val location = mockLocation(latitude = 1.0, longitude = 1.0)
val (_, geocoderHelper) = Arrangement()
.withGetFromLocation(1.0, 1.0, null)
.arrange()

// when
val result = geocoderHelper.getGeoLocatedAddress(location)

// then
assertEquals(null, result.address)
}

@Test
fun `given failure, when getting geocoder address, then return result without address`() = runTest {
// given
val location = mockLocation(latitude = 1.0, longitude = 1.0)
val (_, geocoderHelper) = Arrangement()
.withGetFromLocationFailure()
.arrange()

// when
val result = geocoderHelper.getGeoLocatedAddress(location)

// then
assertEquals(null, result.address)
}

inner class Arrangement {

@MockK
lateinit var geocoder: Geocoder

init {
MockKAnnotations.init(this, relaxUnitFun = true)
}

fun withGetFromLocation(latitude: Double, longitude: Double, result: Address?) = apply {
coEvery { geocoder.getFromLocation(latitude, longitude, 1) } returns listOfNotNull(result)
}

fun withGetFromLocationFailure() = apply {
coEvery { geocoder.getFromLocation(any(), any(), any()) } throws IOException()
}

fun arrange() = this to GeocoderHelper(geocoder)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package com.wire.android.ui.home.messagecomposer.location

import android.content.Context
import android.location.Address
import android.location.Geocoder
import android.location.Location
import android.location.LocationManager
import com.google.android.gms.location.FusedLocationProviderClient
Expand Down Expand Up @@ -95,7 +94,7 @@ class LocationPickerHelperFlavorTest {
.withIsGoogleServicesAvailable(true)
.withIsLocationServiceEnabled(true)
.withGetCurrentLocation(location)
.withGeocoderGetFromLocation(1.0, 1.0, address)
.withGetGeoLocatedAddress(location, address)
.arrange()

// when
Expand All @@ -122,7 +121,7 @@ class LocationPickerHelperFlavorTest {
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient

@MockK
private lateinit var geocoder: Geocoder
private lateinit var geocoderHelper: GeocoderHelper

@MockK
lateinit var locationPickerHelper: LocationPickerHelper
Expand All @@ -133,7 +132,7 @@ class LocationPickerHelperFlavorTest {
private val locationPickerHelperFlavor by lazy {
LocationPickerHelperFlavor(
context = context,
geocoder = geocoder,
geocoderHelper = geocoderHelper,
locationPickerHelper = locationPickerHelper,
)
}
Expand Down Expand Up @@ -166,20 +165,20 @@ class LocationPickerHelperFlavorTest {
coEvery { fusedLocationProviderClient.getCurrentLocation(any<Int>(), any<CancellationToken>()) } returns task
}

fun withGeocoderGetFromLocation(latitude: Double, longitude: Double, result: Address) = apply {
coEvery { geocoder.getFromLocation(latitude, longitude, 1) } returns listOf(result)
fun withGetGeoLocatedAddress(location: Location, result: Address) = apply {
coEvery { geocoderHelper.getGeoLocatedAddress(location) } returns GeoLocatedAddress(result, location)
}

fun arrange() = this to locationPickerHelperFlavor
}
}

fun mockLocation(latitude: Double, longitude: Double) = mockk<Location>().let {
coEvery { it.latitude } returns latitude
coEvery { it.longitude } returns longitude
it
}
fun mockLocation(latitude: Double, longitude: Double) = mockk<Location>().let {
coEvery { it.latitude } returns latitude
coEvery { it.longitude } returns longitude
it
}

fun mockAddress(addressFirstLine: String) = mockk<Address>().also {
coEvery { it.getAddressLine(0) } returns addressFirstLine
}
fun mockAddress(addressFirstLine: String) = mockk<Address>().also {
coEvery { it.getAddressLine(0) } returns addressFirstLine
}
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ class LocationPickerHelperTest {
private val context: Context = ApplicationProvider.getApplicationContext()
private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + dispatcher)
private val geocoder: Geocoder = Geocoder(context)
private val geocoderHelper: GeocoderHelper = GeocoderHelper(geocoder)
private val locationManager: LocationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val address = Address(Locale.getDefault()).apply {
setAddressLine(0, "address")
Expand All @@ -169,7 +170,7 @@ class LocationPickerHelperTest {
context = context,
scope = scope,
currentTimestampProvider = dispatcher.scheduler::currentTime,
geocoder = geocoder,
geocoderHelper = geocoderHelper,
parameters = LocationPickerParameters(
lastLocationTimeLimit = lastLocationTimeLimit,
requestLocationTimeout = requestLocationTimeout
Expand Down

0 comments on commit 64077c8

Please sign in to comment.