getCurrentLocation() method in Kotlin? - android

While I am trying to implement a simple example getting the location of the device, I found that a document which is "seemingly official": https://developer.android.com/training/location/retrieve-current#BestEstimate
The document claims that FusedLocationProviderClient provides the following two methods: getLastLocation() and getCurrentLocation(). But as one can see in the example - https://developer.android.com/training/location/retrieve-current#last-known - both getLast/CurrentLocation() lives in Java. The corresponding Kotlin example says that fusedLocationClient.getLastLocation() "is the same as" fusedLocationClient.lastLocation and, indeed, it works well.
I naively assume that there should be corresponding "currentLocation" for example, fusedLocationClient.currentLocation.
I am wondering there is no such, or I am the only one who fails to find the corresponding Kotlin method.

in kotlin any method of the form getX can be written as just x, this is called "property access syntax". There is no separate kotlin version. fusedLocationClient.lastLocation is really exactly the same as fusedLocationClient.getLastLocation(). You can even write this last form in kotlin if you want.
However, this is only true for "get" methods without parameters. The thing is, getCurrentLocation does have parameters so property access syntax is not possible in this case. as you can see here this is the signature of this method:
public Task<Location> getCurrentLocation (int priority, CancellationToken token)
So you should use it like that. for example
fusedLocationClient.getCurrentLocation(LocationRequest.PRIORITY_HIGH_ACCURACY, null)
EDIT:
apparently null as parameter is not allowed. According to https://stackoverflow.com/a/72159436/1514861 this is a possibility:
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
}
}

Related

Android Kotlin: Is it possible to store the LastLocation in a variable?

I'm trying to use the last known location of an Android user as a variable for a separate coroutine API call
private fun getLocation() {
lateinit var latLong: String
val client: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(getApplicationContext())
client.lastLocation
val location = client.lastLocation
location.addOnSuccessListener {
latLong = "${it.latitude},${it.longitude}"
}
makeApiCall(latLong)}
}
Is it possible to force a wait for the addOnSuccessListener to ensure the variable is updated accordingly?
You can get the last known location synchronously by working directly with Android's LocationManager.
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val location: Location? = with(locationManager) {
val provider = getProvider(LocationManager.GPS_PROVIDER)
getLastKnownLocation(provider)
}
If you want a fallback in case GPS is off, you can use the getProviders() function with a Criteria argument. In either case, all location services might be turned off by the user, so the result might be a null Location.
Note: You can also convert Java callbacks into suspend functions using suspendCoroutine. Something like this:
/** Await the result of a task and return its result, or null if the task failed or was canceled. */
suspend fun <T> Task<T>.awaitResult() = suspendCoroutine<T?> { continuation ->
if (isComplete) {
if (isSuccessful) continuation.resume(it.result)
else continuation.resume(null)
return#suspendCoroutine
}
addOnSuccessListener { continuation.resume(it.result) }
addOnFailureListener { continuation.resume(null) }
addOnCanceledListener { continuation.resume(null) }
}
Then if your function were a suspend function, you could use it like this:
private suspend fun getLocation() {
val client: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(getApplicationContext())
val location = client.lastLocation.awaitResult()
val latLong = location?.run { "$latitude,$longitude" } ?: "null"
makeApiCall(latLong)
}
(Just an example. Don't know what you want to pass to makeApiCall if you don't have a location, or if you want to call it at all.)

Android kotlin task to be executed using coroutines

As an example, I'm using FusedLocationProviderClient to access the current location, which returns a task which callback will eventually return the location. The method looks something like follows:
fun getLocation(callback: MyCallback){
val flpc = LocationServices.getFusedLocationProviderClient(it)
flpc.lastLocation.addOnSuccessListener {
callback.onLocation(it)
}
}
Is it possible to transform this so that I can use corroutines to suspend this function and wait for the task returned by flpc.lastLocation so I can return it in this method and this way get rid of that callback? For example something like this:
suspend fun getLocation(): Location? =
withContext(Dispachers.IO){
val flpc = LocationServices.getFusedLocationProviderClient(it)
return#withContext flpc.lastLocation.result()
}
My question is if there is something around coroutines where I can return the result of a Task (in this example, a Task<Location>)
Thanks in advance!
The kotlinx-coroutines-play-services library has a Task<T>.await(): T helper.
import kotlinx.coroutines.tasks.await
suspend fun getLocation(): Location? =
LocationServices.getFusedLocationProviderClient(context).lastLocation.await()
Alternatively take a look at Blocking Tasks
It would be used the next way:
suspend fun getLocation(): Location? =
withContext(Dispachers.IO){
val flpc = LocationServices.getFusedLocationProviderClient(context)
try{
return#withContext Tasks.await(flpc.lastLocation)
catch(ex: Exception){
ex.printStackTrace()
}
return#withContext null
}
Just to add to this example, for completion purposes, the call to getLocation() would be done the next way:
coroutineScope.launch(Dispatchers.Main) {
val location = LocationReceiver.getLocation(context)
...
}
However this negates the benefits of coroutines by not leveraging the available callback and blocking a thread on the IO dispatcher and should not be used if the alternative is available.
Another way that I have done this that can also be used with any callback type interface is to use suspendCoroutine<T> {}.
So for this example it would be:
suspend fun getLocation(): Location? {
return suspendCoroutine<Location?> { continuation ->
val flpc = LocationServices.getFusedLocationProviderClient(it)
flpc.lastLocation.addOnSuccessListener { location ->
continuation.resume(location)
}
// you should add error listener and call 'continuation.resume(null)'
// or 'continuation.resumeWith(Result.failure(exception))'
}
}

Use property as accessor for Kotlin Coroutine

Kotlin Coroutines question... struggling w/ using a property instead of a function being the accessor for an asynchronous call.
Background is that I am trying to use the FusedLocationProviderClient with the kotlinx-coroutines-play-services library in order to use the .await() method on the Task instead of adding callbacks...
Currently having a property getter kick out to a suspend function, but not sure on how to launch the coroutine properly in order to avoid the
required Unit found XYZ
error...
val lastUserLatLng: LatLng?
get() {
val location = lastUserLocation
return if (location != null) {
LatLng(location.latitude, location.longitude)
} else {
null
}
}
val lastUserLocation: Location?
get() {
GlobalScope.launch {
return#launch getLastUserLocationAsync() <--- ERROR HERE
}
}
private suspend fun getLastUserLocationAsync() : Location? = withContext(Dispatchers.Main) {
return#withContext if (enabled) fusedLocationClient.lastLocation.await() else null
}
Any thoughts on how to handle this?
Properties can't be asynchronous. In general you should not synchronize asynchronous calls. You'd have to return a Deferred and call await() on it when you need a value.
val lastUserLatLng: Deferredd<LatLng?>
get() = GlobalScope.async {
lastUserLocation.await()?.run {
LatLng(latitude, longitude)
}
}
val lastUserLocation: Deferred<Location?>
get() = GlobalScope.async {
getLastUserLocationAsync()
}
private suspend fun getLastUserLocationAsync() : Location? = withContext(Dispatchers.Main) {
return#withContext if (enabled) fusedLocationClient.lastLocation.await() else null
}
But technically it's possible, though you should not do it. runBlocking() blocks until a value is available and returns it.

proper place to use location api in android mvvm architecture

I have a scenario, I want to show user current weather data for that I am getting his/her current lat/lng and reverse geocoding it to get the city name. Once I have the city name I will make a network call and show the weather data. Apart from this, there are many location operations I need to perform.
So I have created a class named as LocationUtils.kt. I am following MVVM architecture and want to know which is the ideal layer to call the LocationUtils methods, is it the view layer or the viewmodel layer or the data layer. Since FusedLocationProvider needs context and if I use it in ViewModel it will leak. So how to solve this problem?
LocationUtils.kt:
class LocationUtils {
private lateinit var fusedLocationClient: FusedLocationProviderClient
private fun isLocationEnabled(weakContext: Context?): Boolean {
return when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P -> {
// This is new method provided in API 28
val locationManager = weakContext?.getSystemService(Context.LOCATION_SERVICE) as LocationManager
locationManager.isLocationEnabled
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT -> {
// This is Deprecated in API 28
val mode = Settings.Secure.getInt(
weakContext?.contentResolver, Settings.Secure.LOCATION_MODE,
Settings.Secure.LOCATION_MODE_OFF
)
mode != Settings.Secure.LOCATION_MODE_OFF
}
else -> {
val locationProviders = Settings.Secure.getString(weakContext?.contentResolver, Settings.Secure.LOCATION_PROVIDERS_ALLOWED)
return !TextUtils.isEmpty(locationProviders)
}
}
}
#SuppressLint("MissingPermission")
fun getCurrentLocation(
weakContext: WeakReference<Context>,
success: (String?) -> Unit,
error: () -> Unit
) {
if (isLocationEnabled(weakContext.get())) {
weakContext.get()
?.let { context ->
fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
getCurrentCity(context, location, success)
}
}
} else {
error()
}
}
private fun getCurrentCity(
context: Context,
location: Location?,
success: (String?) -> Unit
) {
val city = try {
location?.let {
val geocoder = Geocoder(context, Locale.getDefault())
val address = geocoder.getFromLocation(it.latitude, it.longitude, 1)
address[0].locality
}
} catch (e: Exception) {
"Bangalore"
}
success(city)
}
}
I am also working on the same problem. I also have to deal with showing weather data to user using MVVM architecture. At the moment, I am stuck at the same point where you are right now. The solution seems to be something called 'Dependency Injection (DI)'. Basically, we can inject dependencies like Context to our ViewModel using tools/frameworks like 'Dagger 2'. DI has lower coupling than directly passing Context to ViewModel and results in better compliance with MVVM. So, the actual place of FusedLocationProvider, IMO, will be in ViewModel but after implementing DI. Maybe someone else can better elaborate on my explanation. I will update my answer once I implement Dependency Injection myself.
I put it in my ViewModel.
In order to pass context as an argument to your ViewModel you can extend AndroidViewModel instead of ViewModel. Example:
class CurrentViewModel(application: Application) : AndroidViewModel(application) {
val context = application
val locationResolver = LocationResolver(context)//this one converts latitude and longitude into City name of type String
fun detectCity() {
Log.d(LocationResolver.TAG, "entered detectLocation()")
val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
fusedLocationClient.lastLocation
.addOnSuccessListener { location ->
if (location != null) {
Repository._currentName.value = locationResolver.getLocationFromCoordinates(
location.latitude,
location.longitude
)
Log.d(
LocationResolver.TAG,
"New city name is:" + Repository.currentLocationCity
)
}
}
}
Then you can observe the ouput via DataBinding.

document.exists() returns false from firestore

I'm trying to check whether a document in the firestore exists. I copied an exact location of the document (col3) from the firestore console, so it must correct.
The document.exists() returns false despite the document being saved in the database. I followed Google guide from this site.
I've set the break point and checked the DocumentSnapshot object, but it very hard to follow e.g zza, zzb, zzc...
private fun nameExists(userId: String, colName: String): Boolean{
val nameExists = booleanArrayOf(false)
val docRefA = fbDb!!.document("users/X9ogJzjJyOgGBV0kmzk7brcQXhz1/finalGrades/col3")
val docRefB = fbDb!!.collection("users")
.document(userId)
.collection("finalGrades")
.document(colName)
docRefA.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
val document = task.result
if (document.exists()) {
nameExists[0] = true
}else{
Log.d(TAG, "no such document")
}
} else {
Log.d(TAG, "get failed with ", task.exception)
}
}
return nameExists[0]
}
Thanks to #Frank van Puffelen hints, I was able to debug the code. From what I researched, the following code is a classic approach to solve this type of problem.
Perhaps someone will find it useful.
Step 1.
Define a functional interface with a parameter of the same type as the primitive value or object you want to return from the asynchronous operation.
interface OnMetaReturnedListener{
fun onMetaReturned(colExists: Boolean)
}
Step 2. Create a method passing an interface reference as an argument. This method will be running the synchronous operation. Once the value is retrieved, call the functional interface method and pass the retrieved value/object as the method argument.
private fun nameExists(metaReturnedListener: OnMetaReturnedListener, colName: String){
val userId = fbAuth!!.uid!!
val docRefB: DocumentReference = fbDb!!.collection("users")
.document(userId)
.collection("finalGrades")
.document(colName)
docRefB.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
val doc: DocumentSnapshot = task.result!!
val colExists = doc.exists()
metaReturnedListener.onMetaReturned(colExists)
} else {
Log.d(TAG, "get failed with ", task.exception)
}
}
}
Step 3.
Call the method defined in step 2. Pass an object that you defined in step 1. This object will be invoked from the method in step 2, so the overridden method will receive the value/object as a parameter, which you can then assign to a variable of the same type. You can also pass other parameters of different types, but you have to update the method signature in step 2
fun saveModulesToFirestore(colName: String, saveCode: String) {
var collectionExists = false
if (saveCode == FirebaseHelper.ADD_TO_COLLECTION){
nameExists(object: OnMetaReturnedListener{
override fun onMetaReturned(colExists: Boolean) {
collectionExists = colExists
if (collectionExists){
val dialog = CollectionExistsDialogFragment.newInstance()
dialog.show(fragmentManager, DIALOG_COLLECTION_EXISTS)
return
} else{
addNewCollectionToFirebase(colName)
}
}
}, colName)
}
}

Categories

Resources