Getting location once android kotlin widget - android

I am trying to implement a way to get the location once and then return it in Kotlin. This is implemented in an Android widget to fetch a location that is used to fetch more data that is then displayed in a widget.
Currently my implementation is as following:
suspend fun getLocation(
context: Context,
fusedLocationClient: FusedLocationProviderClient
): LocationInterface? {
if (ContextCompat.checkSelfPermission(
context,
android.Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(
context,
android.Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
return null
}
val def = CompletableDeferred<LocationInterface?>()
fusedLocationClient.getCurrentLocation(Priority.PRIORITY_BALANCED_POWER_ACCURACY, object : CancellationToken() {
override fun onCanceledRequested(p0: OnTokenCanceledListener) = CancellationTokenSource().token
override fun isCancellationRequested() = false
})
.addOnSuccessListener { location: Location? ->
if (location == null) {
def.complete(null)
}
var name = ""
try {
runBlocking {
val geocoder = Geocoder(context, Locale.getDefault())
val addresses =
geocoder.getFromLocation(location!!.latitude, location!!.longitude, 1)
}
} catch (e: IOException) {
def.complete(null)
}
if (name.isNotEmpty()) {
def.complete(
LocationInterface(
name,
location!!.latitude,
location.longitude,
)
)
} else {
def.complete(null)
}
}.addOnFailureListener {
def.complete(null)
}
return def.await()
}
It works in my emulator, but I get consistently get errors from real devices with java.lang.NullPointerException referring to getLocation$3$1.invokeSuspend
Does anyone know what I may be doing wrong or have a better solution to simply fetch the location and reverse geocode it and return it from the function that makes it?
Thanks in advance!

Related

Android Kotlin RxJava when first failed, then run second single

New to RxJava.
I want to get UserInfo from cache or Net. when cache is null, then get it from net.
I use two RxJava.Single, try to use concatWith, but it doesn't work.
cacheX is a Single<UserInfo> , when null throw DatabaseItemNotFoundException(self defined.)
userService.getUserInfo is also a Single<UserInfo>
fun getUserInfoFromCacheOrServer(
context: Context, disposables: CompositeDisposable,
token: String?, forceUpdate: Boolean, callback: (UserInfo?, String?) -> Unit
) {
if (token.isNullOrBlank())
return
var save = false
val cacheRepository = CacheRepository(context)
val cacheX = Single.fromCallable {
val cache = if (forceUpdate) null else
cacheRepository.getCacheRx<UserInfo>(KEY_USER_INFO, null, 24 * 60)
Timber.d("cache=$cache")
cache ?: let {
save = true
throw DatabaseItemNotFoundException("cache is null or expired.")
}
}
// I want to get UserInfo from net, when cache is null.
val net = userService.getUserInfo(token.trim()).also { save = true }
cacheX.concatWith(net)
RxJavaResponseUtils.processSingle("getUserInfo", disposables, cacheX) { info, errMsg ->
if (save && info != null) {
cacheRepository.saveCache(KEY_USER_INFO, null, info)
}
callback(info, errMsg)
}
}

Making network call to receive image and show it when location access has been granted

i'm wanting to make a network call when location access has been granted. so i'm using LaunchedEffect(key1 = location.value){...} to decide when to make that network call to recompose, but facing some issues.
upon initial launch user is greeted with the location request (either precise or coarse). during this, the Toast.makeText(context, "Allow location access in order to see image", Toast.LENGTH_SHORT).show() get's called twice and shows up twice. when the user selects an option from the location request dialog, i would assume location.value would end up changing and viewModel.getImage(location.value!!) get's called. debugging through this, that all happens, but the image doesn't end up showing. i got it to work sometimes by force closing the app, then opening it again, then the image shows up. any insights? here is the location code in that same file:
val locationLiveData = LocationLiveData(context)
val location = locationLiveData.observeAsState()
val requestSinglePermissionLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
when {
it.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false) -> {
locationLiveData.startLocationUpdates()
}
it.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false) -> {
locationLiveData.startLocationUpdates()
} else -> {
Toast.makeText(context, "Allow location access", Toast.LENGTH_SHORT).show()
}
}
}
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PermissionChecker.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PermissionChecker.PERMISSION_GRANTED) {
locationLiveData.startLocationUpdates()
} else {
// true so we execute once not again when we compose or so
LaunchedEffect(key1 = true) {
requestSinglePermissionLauncher.launch(arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION))
}
}
EDIT 2
LocationLiveData
class LocationLiveData(var context: Context): LiveData<LocationDetails>() {
// used to get last known location
private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
// We have at least 1 observer or 1 component looking at us
// here we can get the last known location of the device
override fun onActive() {
super.onActive()
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
return
}
fusedLocationClient.lastLocation.addOnSuccessListener {
setLocationData(it)
}
}
// no one is looking at this live data anymore
override fun onInactive() {
super.onInactive()
fusedLocationClient.removeLocationUpdates(locationCallback)
}
internal fun startLocationUpdates() {
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
return
}
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
}
private fun setLocationData(location: Location) {
value = LocationDetails(longitude = location.longitude.toString(), latitude = location.latitude.toString())
}
private val locationCallback = object : LocationCallback() {
override fun onLocationResult(p0: LocationResult) {
super.onLocationResult(p0)
for (location in p0.locations) {
setLocationData(location)
}
}
}
companion object {
private const val ONE_MINUTE: Long = 60_000
val locationRequest: LocationRequest = LocationRequest.create().apply {
interval = ONE_MINUTE
fastestInterval = ONE_MINUTE / 4
priority = Priority.PRIORITY_HIGH_ACCURACY
}
}
}
COMPOSABLE
#RequiresApi(Build.VERSION_CODES.N)
#Composable
fun HomeScreen(viewModel: HomeScreenViewModel = hiltViewModel(), navigateToAuthScreen: () -> Unit, navigateToAddImage: () -> Unit){
var text by remember { mutableStateOf(TextFieldValue("")) }
val context = LocalContext.current
val locationLiveData = remember { LocationLiveData(context) }
val location = locationLiveData.observeAsState()
val requestSinglePermissionLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
when {
it.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false) -> {
locationLiveData.startLocationUpdates()
}
it.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false) -> {
locationLiveData.startLocationUpdates()
}
}
}
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PermissionChecker.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PermissionChecker.PERMISSION_GRANTED) {
locationLiveData.startLocationUpdates()
} else {
// true so we execute once not again when we compose or so
LaunchedEffect(key1 = true) {
requestSinglePermissionLauncher.launch(arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION))
}
}
Scaffold( topBar = {
HomeScreenTopBar()
},
floatingActionButton = {
FloatingActionButton(onClick = {
if (location.value != null) {
navigateToAddImageScreen()
} else {
Toast.makeText(context, "allow location access to add image", Toast.LENGTH_SHORT).show()
}
},
backgroundColor = MaterialTheme.colors.primary
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = "Save note"
)
}
}) {innerPadding ->
Column(modifier = Modifier
.fillMaxSize()
.padding(innerPadding)) {
LaunchedEffect(key1 = location.value) {
if (location.value != null) {
viewModel.getListings(location.value!!)
} else {
Toast.makeText(context, "Allow location access in order to see image", Toast.LENGTH_SHORT).show()
}
}
}
This line
val locationLiveData = LocationLiveData(context)
creates a new LocationLiveData instance on every recomposition.
You have to remember the same instance of LocationLiveData across recompositions, if you want it to hold any state or view state.
Change it to
// remember LocationLiveData across recompositions
// this does not survive configuration changes, nor other short Activity restarts
val locationLiveData = remember { LocationLiveData(context) }
As also mentioned in the code comment above, now locationLiveData will survive re-compositions, but it will still get reset on:
every configuration change (examples include but are not limited to:
orientation change, light/dark mode change, language change...)
every short Activity restart, caused by the system in some cases
also application death (but that is somewhat expected)
To solve 1. and 2. you can use rememberSaveable that can save primitive and other Parcelable types automatically (in your case you can also implement the Saver interface), to solve 3. you have to save the state to any of the persistent storage options and then restore as needed.
To learn more about working with state in Compose see the documentation section on Managing State. This is fundamental information to be able to work with state in Compose and trigger recompositions efficiently. It also covers the fundamentals of state hoisting. If you prefer a coding tutorial here is the code lab for State in Jetpack Compose.
An introduction to handling the state as the complexity increases is in the video from Google about Using Jetpack Compose's automatic state observation.

How to get location using "fusedLocationClient.getCurrentLocation" method in Kotlin?

To request the last known location of the user's device, we can use the fused location provider to retrieve the device's last known location using getLastLocation(), but using getCurrentLocation() gets a refresher, and more accurate location.
so, how to use the fusedLocationClient.getCurrentLocation() in Kotlin as there is no example illustrated in the documentation?
According to the documentation, the getCurrentLocation() takes two parameters.
The 1st parameter it takes is the priority (e.g. PRIORITY_HIGH_ACCURACY) to request the most accurate locations available, or any other priority that can be found here.
The 2nd parameter it takes is a cancellation token that can be used to cancel the current location request.
From the Google play services reference, a CancellationToken can only be created by creating a new instance of CancellationTokenSource.
so here is the code you need to use when using getCurrentLocation()
class YourActivity : AppCompatActivity() {
private lateinit var fusedLocationClient: FusedLocationProviderClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.your_layout)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
fusedLocationClient.getCurrentLocation(LocationRequest.PRIORITY_HIGH_ACCURACY, object : CancellationToken() {
override fun onCanceledRequested(p0: OnTokenCanceledListener) = CancellationTokenSource().token
override fun isCancellationRequested() = false
})
.addOnSuccessListener { location: Location? ->
if (location == null)
Toast.makeText(this, "Cannot get location.", Toast.LENGTH_SHORT).show()
else {
val lat = location.latitude
val lon = location.longitude
}
}
}
}
fusedLocationClient.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, object : CancellationToken() {
override fun onCanceledRequested(listener: OnTokenCanceledListener) = CancellationTokenSource().token
override fun isCancellationRequested() = false
})
.addOnSuccessListener {
if (it == null)
Toast.makeText(this, "Cannot get location.", Toast.LENGTH_SHORT).show()
else {
val lat = it.latitude
val lon = it.longitude
}
}
I know the questio is about Kotlin but I want to add for those searching for Java :
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
fusedLocationClient.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, cancellationTokenSource.getToken())
.addOnSuccessListener(MyActivity.this, new OnSuccessListener<Location>() {
#Override
public void onSuccess(Location location) {
// Got last known location. In some rare situations this can be null.
if (location != null) {
//do your thing
}
Log.w(TAG, "No current location could be found");
}
});

Retrieve background location using WorkManager always return same lastknown location

My app works fine if using foreground service to get user location, however in background I just need location for each 15 minutes, also Google requires new Policy for getting background location so, foreground service is over my expectation.
I am trying to get location from background using WorkManager, it can run normally every (around) 15 minutes. My location is requested, however it always returns the previous address, even 1, 2... hours are passed.
Here is my code:
class LocationWorker(private val context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
private var fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
override suspend fun doWork() = withContext(Dispatchers.IO) {
val location = getLocation()
if (location == null) {
if (runAttemptCount < MAX_ATTEMPT) { // max_attempt = 3
Result.retry()
} else {
Result.failure()
}
} else {
Log.d(TAG, "doWork success $location")
Result.success()
}
}
private suspend fun getLocation(): Location? = withTimeoutOrNull(TIMEOUT) {
suspendCancellableCoroutine<Location?> { continuation ->
val intent = PendingIntent.getBroadcast(context, REQUEST_CODE, Intent(ACTION), 0)
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, data: Intent?) {
if (data?.action != ACTION) return
val lastLocation = LocationResult.extractResult(data)?.lastLocation
Log.e(TAG, "Get lastLocation success $lastLocation")
fusedLocationClient.removeLocationUpdates(intent)
context?.unregisterReceiver(this)
continuation.resume(lastLocation)
}
}
context.registerReceiver(receiver, IntentFilter(ACTION))
val request = LocationRequest().apply { priority = LocationRequest.PRIORITY_HIGH_ACCURACY }
fusedLocationClient.requestLocationUpdates(request, intent)
continuation.invokeOnCancellation {
fusedLocationClient.removeLocationUpdates(intent)
context.unregisterReceiver(receiver)
}
}
companion object {
val TAG = LocationWorker::class.java.simpleName
const val LOCATION_WORKER_TAG = "LOCATION_WORKER_TAG"
const val MAX_ATTEMPT = 3
private const val ACTION = "my.background.location"
private const val TIMEOUT = 60_000L
private const val REQUEST_CODE = 888
}
}
Pre-condition:
Tested device: emulator android 27 (O_MR1)
Route Play normally
GPS is enabled
Allow location permission (allow all time)
Why the lastknown location not updated ?
I also tried this demo https://github.com/pratikbutani/LocationTracker-WorkManager/ . However, the problem is same, lastknown location is not updated.
The previous location gets cached.
Clear the cache before you store the new location in lastknown location

Mapbox did not draw Layer when user's moving

Im making a running app and using Sdk mapbox. The requirement is when user moves, I need to draw a layer from the beginning location to the latest one,
I tried this way but it didn’t work
class LocationChangeListeningActivityLocationCallback internal constructor(activity: MapBoxActivity) : LocationEngineCallback<LocationEngineResult> {
private val activityWeakReference: WeakReference<MapBoxActivity> = WeakReference(activity)
override fun onSuccess(result: LocationEngineResult) {
val activity = activityWeakReference.get()
if (activity != null) {
val location = result.lastLocation ?: return
if (result.lastLocation != null) {
activity.mapBoxMap.locationComponent.forceLocationUpdate(result.lastLocation)
activity.listPoint.add(Point.fromLngLat(location.latitude, location.longitude))
activity.trackingLine()
}
}
}
override fun onFailure(exception: Exception) {
val activity = activityWeakReference.get()
if (activity != null) {
Toast.makeText(activity, exception.localizedMessage, Toast.LENGTH_SHORT).show();
}
}
}
var listPoint: ArrayList<Point> = ArrayList()
private var geoJson: GeoJsonSource = GeoJsonSource(LINE_SOURCE_ID)
fun trackingLine() {
mapBoxMap.getStyle {
geoJson.setGeoJson((Feature.fromGeometry(LineString.fromLngLats(listPoint))))
LogCat.d("Set new location in geoJSON")
}
}
private fun initDotLinePath(loadedMapStyle: Style) {
loadedMapStyle.addSource(geoJson)
loadedMapStyle.addLayerBelow(LineLayer(LAYER_ID_3, LINE_SOURCE_ID).withProperties(
PropertyFactory.lineColor(Color.parseColor("#F13C6E")),
PropertyFactory.lineCap(Property.LINE_CAP_ROUND),
PropertyFactory.lineJoin(Property.LINE_JOIN_ROUND),
PropertyFactory.lineWidth(4f)), "road-label")
}
Please show me my mistakes. Thanks a lot
activity.listPoint.add(Point.fromLngLat(location.latitude, location.longitude))
Your coordinates are in the wrong order.

Categories

Resources