I used this class to get current location of device for my map app. I'm using this with GooglePlayServices and its working fine, but I recently switched to HMS for Huawei devices if GooglePlayServices are not available on device. I replaced all GooglePlayServices classes with mirror objects from HMS imported lib and it compiled without errors. But as I call for current location, it will not return anything. No exception, no success or failure.
I did not receive callback to onLocationResult() or catch() block.
According to debugger last row called is val task = lp.requestLocationUpdates(lr, this, Looper.getMainLooper())
Anyone has this problem? This is clearly new issue. Testing this on Huawei P40 where GooglePlayServices are not available.
Also HuaweiMap is not working in release mode. getMapAsync() will not return onMapReady() callback. It got stuck there. But if I switch debug mode, it is working correctly.
UDPATE:
HuaweiMap is working now. Updated proguard. But Location is still not working. It is not working even in debug mode.
Code:
private inner class LocationCbHua(val lp: com.huawei.hms.location.FusedLocationProviderClient,
val onFailure: (()->Unit)? = null,
val onSuccess: (GpsLocation)->Unit)
: com.huawei.hms.location.LocationCallback() {
init {
val lr = com.huawei.hms.location.LocationRequest.create().apply {
priority = com.huawei.hms.location.LocationRequest.PRIORITY_HIGH_ACCURACY
interval = 200
}
val lsr = com.huawei.hms.location.LocationSettingsRequest.Builder().run {
// setAlwaysShow(true) // TEST
addLocationRequest(lr)
build()
}
val check = com.huawei.hms.location.LocationServices.getSettingsClient(activity!!).checkLocationSettings(lsr)
check.addOnCompleteListener {
try {
check.getResultThrowException(com.huawei.hms.common.ApiException::class.java)
val task = lp.requestLocationUpdates(lr, this, Looper.getMainLooper())
task.addOnFailureListener {
onFailure?.invoke()
}
} catch (e: com.huawei.hms.common.ApiException) {
when (e.statusCode) {
com.huawei.hms.location.LocationSettingsStatusCodes.RESOLUTION_REQUIRED-> if(!locationResolutionAsked){
// Location settings are not satisfied. But could be fixed by showing the user a dialog.
try {
// Cast to a resolvable exception.
val re = e as com.huawei.hms.common.ResolvableApiException
// Show the dialog by calling startResolutionForResult(), and check the result in onActivityResult().
re.startResolutionForResult(mainActivity, MainActivity.REQUEST_LOCATION_SETTINGS)
locationResolutionAsked = true
} catch (e: Exception) {
e.printStackTrace()
}
}
com.huawei.hms.location.LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE->{
// Location settings are not satisfied. However, we have no way to fix the settings so we won't show the dialog.
App.warn("Location is not available")
onFailure?.invoke()
}
}
}
}
}
fun cancel(){
lp.removeLocationUpdates(this)
currLocCb = null
}
override fun onLocationResult(lr: com.huawei.hms.location.LocationResult) {
cancel()
val ll = lr.lastLocation
onSuccess(GpsLocation(ll.longitude, ll.latitude))
}
}
The possible cause is as follows:
After checkLocationSettings code was executed, an exception was catched during execution of code check.getResultThrowException. However, the catched error code is not 6 (RESOULTION_REQUIRED).
Therefore, code com.huawei.hms.location.LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE was directly executed to report Location is not available after code com.huawei.hms.location.LocationSettingsStatusCodes.RESOLUTION_REQUIRED-> if(!locationResolutionAsked) was executed.
As a result, neither exception nor location result was obtained. You are advised to add a code line at when (e.statusCode) to record error logs, and then continue error analysis.
Use OnSuccessListener instead of OnCompleteListener
val check = com.huawei.hms.location.LocationServices.getSettingsClient(activity!!).checkLocationSettings(lsr)
check.addOnSuccessListener{
lp.requestLocationUpdates(lr, this, Looper.getMainLooper())
}
You can also check this post:
https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0201272177441270079&fid=0101187876626530001
Related
I was having a problem implementing the Firebase anonymous sign-in function with Kotlin coroutine.
Following is the code for that:
Repository.kt
suspend fun getUserId(){
firebaseHelper.getUserId().collect{
if (it == "Successful"){
emit(it)
} else {
emit("Task unsuccessful")
}
}
}
FirebaseHelper.kt
fun getUserId() = flow {
val firebaseLoginAsync = Firebase.auth.signInAnonymously().await()
if (firebaseLoginAsync.user != null && !firebaseLoginAsync.user?.uid.isNullOrEmpty()) {
emit("Successful")
} else {
emit("Failed")
}
}
It works fine when the android device is connected to the internet.
But when I test this code without the internet it never completes, that is, the execution never reaches the if else block of FirebaseHelper.kt.
I was unable to find any resource that would help me understand the cause of this problem and any possible solution.
One idea that I can think of on the solution side is to forcefully cancel the await() functions execution after some time but I can't find anything related to implementation.
It works fine when the android device is connected to the internet.
Since an authentication operation requires an internet connection, then that's the expected behavior.
But when I test this code without the internet it never completes.
Without the internet, there is no way you can reach Firebase servers, hence that behavior. However, according to the official documentation of await() function:
This suspending function is cancellable. If the Job of the current coroutine is canceled or completed while this suspending function is waiting, this function immediately resumes with CancellationException.
Or you can simply check if the user is connected to the internet before performing the authentication.
The way that I made it work is with help of try catch block and withTimeout() function in FirebaseHelper.kt file. Following is the code of solution:
fun getUserID() = flow {
try {
val signInTask = Firebase.auth.signInAnonymously()
kotlinx.coroutines.withTimeout(5000) {
signInTask.await()
}
if (signInTask.isSuccessful){
emit("Successful")
} else {
emit("Failed")
}
} catch (e: Exception){
emit("Can't connect to the server\nPlease check your internet connection and retry")
}
}
withTimeout(timeMillis: Long, block: suspend CoroutineScope.() -> T) runs the given suspend block for timeMillis milliseconds and throws TimeoutCancellationException if the timeout was exceeded.
I want to get the current GPS location of the device using LocationManager.requestSingleUpdate(). The following code is used to wrap the call to LocationManager in a kotlin suspending function:
private var locationManager =
context.getSystemService(LOCATION_SERVICE) as LocationManager
#RequiresPermission("android.permission.ACCESS_FINE_LOCATION")
suspend fun getCurrentLocationPreS(): Coordinate? = suspendCoroutine {
val handlerThread = HandlerThread("getCurrentLocation() HandlerThread")
handlerThread.start()
try {
// Use of deprecated function is ok because we are pre android S
locationManager.requestSingleUpdate(
LocationManager.GPS_PROVIDER,
{ location ->
handlerThread.quit()
it.resume(
Coordinate(
location.latitude,
location.longitude
)
)
},
handlerThread.looper
)
}
catch (ex: Exception) {
ex.printStackTrace()
it.resumeWithException(ex)
}
}
As you can see, I use suspendCoroutine to make the asynchronous location call. This implementation works for some devices but I have problems on other devices. Sometimes the supending function never returns and waits forever because the location update callback is not called. The app also has the needed permissions and GPS is enabled.
What edge case leads to a state that the function never returns?
Logcat does not indicate any exception or other error. The app also does not crash. The only symptom is that the getCurrentLocationPreS() never returns.
Just because GPS is enabled doesn't mean that it is working properly. You might have a poor signal when being indoors or in areas packed with tall buildings. If you look at the implementation of requestSingleUpdate you will see it uses a timeout of 30s, so if the timeout expires, your callback will never be executed and your coroutine gets stuck indefinitely.
I would suggest to either to use a timeout for this call as well or consider using FusedLocationProviderClient which allows you to get the last known location in a safer way.
I would also suggest using Looper.getMainLooper(), the runtime overhead from temporarily switching to the main thread is negligible compared to the effort of making sure you are properly managing the HandlerThread
So my take on this would look something like this:
suspend fun getCurrentLocationPreS(): Coordinate? = withTimeoutOrNull(30.seconds){
suspendCoroutine { cont ->
try {
// Use of deprecated function is ok because we are pre android S
locationManager.requestSingleUpdate(
LocationManager.GPS_PROVIDER,
{ location ->
cont.resume(
Coordinate(
location.latitude,
location.longitude
)
)
},
Looper.getMainLooper()
)
}
catch (ex: Exception) {
ex.printStackTrace()
cont.resumeWithException(ex)
}
}
}
I am trying to run a test in which I want to wait till higher order function executes. As of now I am not able to figure out any ways to do it. Following is my code.
#Test
fun `test execute routine error`() = runBlocking(coroutineDispatcher) {
val observer = mock<Observer<String>>()
baseViewModel.error.observeForever(observer)
val httpException = HttpException(Response.error<String>(402, mock(ResponseBody::class.java)))
val function = baseViewModel.executeRoutine {
throw httpException
}
verify(observer).onChanged("Something went wrong. Please try again")
}
The problem with above snippet is that it jumps to the last line i.e. verify() before throwing an http exception for executeRoutine.
Update: Execute routine definition
fun executeRoutine(requestType: RequestType = RequestType.POST_LOGIN, execute: suspend () -> Unit) {
viewModelScope.launch {
withContext(Dispatchers.IO) {
_spinner.postValue(true)
try {
execute()
} catch (ex: HttpException) {
val errorHandler = errorHandlerFactory.create(requestType)
_error.postValue(errorHandler.getErrorMessageFrom(ex))
} catch (ex: Exception) {
_error.postValue(ex.localizedMessage)
Timber.e(ex)
} finally {
_spinner.postValue(false)
}
}
}
}
The problem is that the higher order function does execute, it just doesn't do what you think it does -- its execution is launching the task, not waiting for it to complete.
You will have to solve the problem another way, by either having your test wait until the change is observed, or having the callback complete a barrier to allow the test to proceed (e.g. completableJob.complete() at the end of the call back, and completableJob.join() waiting before proceeding with the test).
It might also be desirable to rearchitect your code so you don't have to do anything special, e.g. by making executeRoutine a suspend function executing the code rather than launching the code in another scope.
I've noticed that some of the users have issues to use flexible in-app update, the JobCancellationException: Job was cancelled is thrown with incomprehensible stack trace:
at dalvik.system.VMStack.getThreadStackTrace(VMStack.java)
at java.lang.Thread.getStackTrace(Thread.java:1538)
at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:1068)
at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:1063)
at java.lang.Thread.dispatchUncaughtException(Thread.java:1955)
Unfortunately, I don't which part of the code is causing the issue. This is the only coroutine related code, staying in MyViewModel:
init {
viewModelScope.launch {
try {
appUpdateManager.requestUpdateFlow().collect { appUpdateResult ->
// Do something with result.
}
} catch (e: InstallException) {
// Do something with an error.
}
}
}
fun requestUpdate(fragment: Fragment) {
viewModelScope.launch {
try {
val appUpdateInfo = appUpdateManager.requestAppUpdateInfo()
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.FLEXIBLE,
fragment,
REQUEST_CODE
)
} catch (e: IntentSender.SendIntentException) {
}
}
}
I suspect that code inside requestUpdateFlow() is calling offer while the coroutine job is already cancelled and I can't see the exact stacktrace, because Play Core library is obfuscated?
I'm using following versions of the libraries:
"com.google.android.play:core:1.7.2"
"com.google.android.play:core-ktx:1.7.0"
JobCancellationException: Job was cancelled is thrown in almost case is job in coroutine scope is cancelled.
Example: User go to a screen a in which call api to get something. But user press back to close this screen while api not complete. Thus, when receive response, job cancelled before -> exception.
To more handle JobCancellationException you can using suspendCancellableCoroutine.
More detail : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/suspend-cancellable-coroutine.html
Basically I have to make a network request using OkHttp in parallel to various addresses. I only care about the result of the first one that succeeds. Can I do this with Flow on Kotlin?
I've been looking around but I'm struggling with getting the requests to run in parallel, the always run in sequence.
The code basically takes a list of addresses and should return the only address that worked or null if none worked.
Thanks.
Edit: I should mention I plan on using this on Android. I can probably do it with RX but wanted to learn Flow. Also trying to limit the libraries I add to the app.
Edit: I have marked an answer as correct however that isn't how I did but it took me very close to how I did it but since I'm new to Flow I have no idea if how I did it is correct though I'm pretty sure it works after my testing.
I have a function that throws NoSuchElementException when not found. It calls searchForIPAsync which is a suspend function that does all the OkHttp work and returns true|false.
#Throws(NoSuchElementException::class)
private suspend fun findWorkingIP(ipsToTest: MutableList<String>): String? = ipsToTest
.asFlow()
.flatMapMerge(ipsToTest.size)
{ impl ->
flow<String?> {
val res = connectionHelper.searchForIPAsync(getURLToTest(impl))
if (res) {
emit(impl)
} else {
}
}
}.first()
Then I call this and catch the exception in case nothing is found:
try {
val ipFound = findWorkingIP(ipsToTest)
Log.w(TAG, "find: Got something " + ipFound);
return ipFound
} catch (ex: NoSuchElementException) {
Log.w(TAG, "find: not found");
}
Although the Flow-based solution in another answer is a close match to what you need, unfortunately as of Kotlin 1.3.2 the Flow implementation has a bug that breaks it. The bug already has a proposed fix so this should be resolved with the next patch release of Kotlin. In the meantime, here's a similar solution that uses async and Channel instead:
suspend fun getShortUrl(urls: List<String>): String = coroutineScope {
val chan = Channel<String?>()
urls.forEach { url ->
launch {
try {
fetchUrl(url)
} catch (e: Exception) {
null
}.also { chan.send(it) }
}
}
try {
(1..urls.size).forEach { _ ->
chan.receive()?.also { return#coroutineScope it }
}
throw Exception("All services failed")
} finally {
coroutineContext[Job]!!.cancelChildren()
}
}