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.
Related
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 am trying to write a simple app in Android Studio using Kotlin. It is a very steep learning curve for me, but I am almost there. My final problem is getting the app to wait for the APIs to complete before moving the next Intent.
I have three calls each uploading data via my API. They are called from a button and only when the three are uploaded, should the button send the user to the next intent/screen.
My API calls are working and I can see the data in the database. However, since enqueue is asynchronous the calls are firing and the code is moving on the start the next intent before the data is present.
The code below is executed 3 times (once for each upload). I realise this is probably not the best way to do it, but I'm trying to get it working before I finesse the code.
I thought that perhaps I could have a variable, UploadedReadCount, that I increment in the onResponse, but this doesn't seem to be working properly.
Could someone offer some advice as to how I should be pausing the code until the APIs complete? For example, is there an enqueue methos that isn't async?
ReadInterface.create().AddRead("new", rFuel, rRegister, rReadDate, rRead)
.enqueue(object : Callback<UploadedRead> {
override fun onFailure(call: Call<UploadedRead>, t: Throwable) {
Log.d("Err: ", t.localizedMessage!!)
t.printStackTrace()
}
override fun onResponse(call: Call<UploadedRead>, response: Response<UploadedRead>) {
Log.d("Response: ", response.body().toString())
val p = response.body()?.APIResult!![0]
msgShow("Gas read " + rRead.toString() + " uploaded")
UploadedReadCount += 1
}
})
while ( UploadedReadCount < 3) {
Log.d("Waiting ", UploadedReadCount.toString() + " reads uploaded...")
}
val intent = Intent(this, Billing::class.java).apply {
putExtra("ReadDate", txtReadDate.text.toString())
}
startActivity(intent)
In most cases you don't want to pause execution while API call returns, Instead you want to follow the reactive model, that is when you call API you specify some callbacks (onResponse, onFailure), and once these callbacks are invoked then you react.
code is moving on the start the next intent before the data is
present.
Move all of your code that depends on data received from API in onResponse or onFailure methods (callbacks), When API is ready with some response one of those callbacks will be invoked and then depending on the data that you receive from API you can continue your work.
is there an enqueue methos that isn't async?
There are options available to call an API in blocking manner but I don't think that is good idea. Instead of doing a blocking API call, you should try to do reactive programming that is as soon as any callback (onResponse, onFailure) is called only then you continue.
There is an alternative to enqueue that is suspending instead of async, so you can call your code sequentially without blocking the main thread in a coroutine. The function is await() and it returns the successful result or throws an HttpException on failure.
But to run three requests in parallel, you need to use the async coroutine builder. This can be done by mapping a list of Calls to async calls that await the individual results, and then using awaitAll() on the list of Deferreds to wait for all three. So, it's more complicated than just running sequential code in a coroutine, but I think this is still easier than trying to run and wait for three parallel calls using callbacks.
I'm not exactly sure what your other two calls are so I'll just make up some and assume this function already has all the data it needs to make the calls. I also don't know how you want to handle failure, so I'm just making it stop early if any of the three calls fail.
lifecycleScope.launch {
val requests: List<Call<UploadedRead>> = listOf(
ReadInterface.create().AddRead("new", rFuel, rRegister, rReadDate, rRead),
ReadInterface.create().AddRead("new2", rFuel, rRegister, rReadDate, rRead),
ReadInterface.create().AddRead("new3", rFuel, rRegister, rReadDate, rRead)
)
val responses: List<UploadedRead> = try {
coroutineScope { // any failure in this block cancels them all
requests.map { async { it.await() } } // run them simultaneously with async
.awaitAll()
}
} catch (e: HttpException) {
Log.d("Err: ", e.localizedMessage.toString())
printStackTrace(e)
return#launch
}
// Do something with the list of three UploadedReads here.
}
I just duplicated the functionality of your code above, but it doesn't look like you're using the response for anything and you have an unused variable p.
Edit: If this is a pattern you use frequently, this helper function might be useful. I didn't check this thoroughly or test it.
/**
* Await the result of all the Calls in parallel. Any exception thrown by any item
* in the list will cancel all unfinished calls and be rethrown.
*/
suspend fun <T: Any> Iterable<Call<T>>.awaitAll(): List<T> =
coroutineScope { map { async { it.await } }.awaitAll() }
//...
lifecycleScope.launch {
val requests: List<Call<UploadedRead>> = listOf(
//...
)
val responses: List<UploadedRead> = try {
requests.awaitAll()
} catch (e: HttpException) {
//...
return#launch
}
//...
}
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()
}
}
I have a problem with freezing of my application.
So:
I have intent service (it is mean all already in another thread)
I have a list of users.
I should download photos for each user and push them to another cloud service (with face-recognition).
Now we are using a trial version of this service. So it can sends only 10 requests in minute. I want sequential execution of the program (simple version)
But application freeze when it is trying to download user's photos. Only after finishing, application starts work. I hope you understand my explanation.
Here is simple code (I'm using corotinues for it):
private val ioScope = CoroutineScope(Dispatchers.IO + Job())
onCreate method of IntentService
override fun onCreate() {
super.onCreate()
ioScope.launch {
Repository.getUserList().observeOnce(Observer { users ->
users?.forEach { user ->
addedNewUser(user)
}
})
}
}
addedNewUser
private suspend fun addedNewUser(user: User) = withTimeoutOrNull(TWO_MINUTES) {
user.mail ?: return#withTimeoutOrNull
launch {
try {
// withContext is freez place
val file = withContext(ioScope.coroutineContext) { getUserAvatar(applicationContext, user.mail) }
// do something.......
file.delete()
} catch (e: ClientException) { // in free price mode face api allow only 20 requests in minutes
delay(ONE_MINUTE)
}
}.join()
}
Do you have any ideas? Why withContext(Dispatchers.Default) is freeze?
Also I have tried withContext(this.coroutineContext), but it doesn't wait when file will be downloaded.
Thx, for your time!
UPDATE (answer)
Thx everybody, who tried to help me! I think we found the problem. Repository.getUserList() - return a livedata. So when suspend or runBlocking function started in observer, I watched a freezing. If wraped it in corotinues or new thread it works correctly:
Repository.getUserList().observeOnce(Observer { users ->
ioScope.launch {
users?.forEach { user ->
addedNewUser(user)
}
}.start()
})
Unfortunately I don't know all details how it work under hood, but seems, that observer return value in main thread. So suspend function just stopped main thread.
I'm trying out coroutines instead of RxJava on basic network calls for the fist time to see what it's like and running into some issues with lag/threading
In the below code, I'm doing a network call userRepo.Login() and if an exception happens I show an error message and stop the progress animation that I started at the start of the function.
If I leave everything on the CommonPool (or don't add any pool) it crashes saying the animation must be done on a looper thread if an exception happens. In other circumstances I've received errors saying this must be done on the UI thread as well, same problem, different thread requirements.
I can't launch the whole coroutine on the UI thread though, because the login call will block since it's on the UI thread and messes up my animation (which makes sense).
The only way I can see to resolve this, is the launch a new coroutine on the UI thread from within the existing coroutine, which works, but seems weird.
Is this the proper way to do things, or am I missing something?
override fun loginButtonPressed(email: String, password: String) {
view.showSignInProgressAnimation()
launch(CommonPool) {
try {
val user = userRepo.login(email, password)
if (user != null) {
view.launchMainActivity()
}
} catch (exception: AuthException) {
launch(UI) {
view.showErrorMessage(exception.message, exception.code)
view.stopSignInProgressAnimation()
}
}
}
}
You should start from the opposite end: launch a UI-based coroutine, from which you hand off heavy operations to an external pool. The tool of choice is withContext():
override fun loginButtonPressed(email: String, password: String) {
view.showSignInProgressAnimation()
// assuming `this` is a CoroutineScope with dispatcher = Main...
this.launch {
try {
val user = withContext(IO) {
userRepo.login(email, password)
}
if (user != null) {
view.launchMainActivity()
}
} catch (exception: AuthException) {
view.showErrorMessage(exception.message, exception.code)
view.stopSignInProgressAnimation()
}
}
}
This way you keep your natural Android programming model, which assumes the GUI thread.