I would like my app users to be able to cancel file upload.
My coroutine upload job in ViewModel looks like this
private var uploadImageJob: Job? = null
private val _uploadResult = MutableLiveData<Result<Image>>()
val uploadResult: LiveData<Result<Image>>
get() = _uploadResult
fun uploadImage(filePath: String, listener: ProgressRequestBody.UploadCallbacks) {
//...
uploadImageJob = viewModelScope.launch {
_uploadResult.value = withContext(Dispatchers.IO) {
repository.uploadImage(filePart)
}
}
}
fun cancelImageUpload() {
uploadImageJob?.cancel()
}
Then in the repository the Retrofit 2 request is handled like this
suspend fun uploadImage(file: MultipartBody.Part): Result<Image> {
return try {
val response = webservice.uploadImage(file).awaitResponse()
if (response.isSuccessful) {
Result.Success(response.body()!!)
} else {
Result.Error(response.message(), null)
}
} catch (e: Exception) {
Result.Error(e.message.orEmpty(), e)
}
}
When cancelImageUpload() it called the job gets cancelled and the exception gets caught in the repository but the result won't get assigned to uploadResult.value.
Any ideas please how to make this work?
PS: There is a similar question Cancel file upload (retrofit) started from coroutine kotlin android but it suggests using coroutines call adapter which is depricated now.
Have finally managed to make it work by moving withContext one level up like this
uploadImageJob = viewModelScope.launch {
withContext(Dispatchers.IO) {
_uploadResult.postValue(repository.uploadImage(filePart))
}
}
Related
I am working on my Final Year Project and I am really stuck on the decision should I use callbacks or coroutines of Kotlin. I created separate Module for the firebase where all its operations are done there weather its data retrieval or any other functionalities.
the problem is that whenever I return the user from the function it return null due than I understand it due to the async calls and after that I used call back for it like this:
fun getUserAsModel(callback: (User) -> Unit) {
FirebaseAuth.getInstance().uid?.let {
firestore.collection(Constants.FireCollections.USERS)
.document(it)
.get()
.addOnSuccessListener { it1 ->
val user = it1.toObject(User::class.java)?.let { it2 ->
callback(it2)
}
}
.addOnFailureListener {
Log.e(TAG, "In userModel()->", it)
it.stackTrace
}
}
}
But I see in many forms that I should I use coroutines and now I am using this approach but it does not work:
fun getUser () : User? {
var user:User? = null
val collection = firestore.collection(Constants.FireCollections.USERS)
val document = collection.document(FirebaseAuthRepository().getCurrentUserId())
try {
scope.launch {
val snapshot = document.get().await()
user = snapshot.toObject(User::class.java)
}
} catch (e:FirebaseFirestoreException) {
Log.e(TAG, "In getUser() -> " ,e)
e.stackTrace
}
return user
}
I am still stuck because every time I use getUser() I need to launch the scope of coroutines and this is really makes the code juncky.
I would like to know about your solution how should I properly implement this. Thanks
You're recreating the same problem you had with the asynchronous call, since a coroutine is launched asynchronously. The correct way to do it with a coroutine is to make it a suspend function and directly return the user without launching another coroutine inside this function.
The function should look like this:
suspend fun getUser () : User? {
val collection = firestore.collection(Constants.FireCollections.USERS)
val document = collection.document(FirebaseAuthRepository().getCurrentUserId())
return try {
val snapshot = document.get().await()
snapshot.toObject(User::class.java)
} catch (e: FirebaseFirestoreException) {
Log.e(TAG, "In getUser() -> ", e)
null
}
}
Callbacks versus coroutines is a matter of preference. Coroutines are not trivial to learn, but once you do, your code will be cleaner-looking and easier to follow.
You can also use callbackflow
fun getUserAsModel():Flow<User?> {
return callbackFlow {
FirebaseAuth.getInstance().uid?.let {
firestore.collection(Constants.FireCollections.USERS)
.document(it)
.get()
.addOnSuccessListener { it1 ->
val user = it1.toObject(User::class.java)
trySend(user)
}
.addOnFailureListener {
Log.e(TAG, "In userModel()->", it)
it.stackTrace
cancel(it)
}
awaitClose { close() }
}
}
}
I have a scenario where my code has to send an api call and move on with its work (which contains another api call) without waiting for the result of the first call.
Right now I do this in my viewmodel
fun showItem(id:Int) {
launch{
repo.markItemRead(id)
}
launch {
try {
val item = repo.getItemById(id).getOrThrow
commands.postValue(ShowItemCommand(item))
} catch (t:Throwable) {
commands.postValue(ShowError(R.string.error_retrieve_item))
repo.logError(t)
}
}
}
this calls the repository which has these two functions
suspend fun markItemRead(id) {
try {
service.markItemAsRead(id)
} catch(ignored:Throwable) {
}
}
suspend fun getItemById(id) : Result<ItemData> {
return try {
val response : ItemEntity = service.getItemById(id)
val item = response.toData()
Result.Success(item)
} catch (t:Throwable) {
Result.Failure(t)
}
}
I would prefer it if the repository did all those jobs because one has to follow the other every time.
Unfortunatelly when I try to do something like this in my repository:
suspend fun getItemById(id:Int) : Result<ItemData> {
try {
service.markItemAsRead(id)
} catch(ignored:Throwable) {
}
return try {
val response : ItemEntity = service.getItemById(id)
val item = response.toData()
Result.Success(item)
} catch (t:Throwable) {
Result.Failure(t)
}
}
It waits for the markItemAsRead function to finish before moving on
Other than defining a scope for the repository and putting the markItemAsRead call inside a launch (which I have read is incorrect to do inside a suspending function) is there another way of doing this inside the repository?
You can use coroutineScope or supervisorScope in the repository, depending on your needs. Both functions are designed for parallel decomposition of work. These functions return as soon as the given block and all its children coroutines are completed.
When any child coroutine in coroutineScope fails, this scope fails and all the rest of the children are cancelled. Unlike coroutineScope, a failure of a child coroutine in supervisorScope does not cause this scope to fail and does not affect its other children, so a custom policy for handling failures of its children can be implemented.
Please choose what best suits your needs. Example of usage:
suspend fun getItemByIdAndMarkRead(id: Int) : Result<ItemData> = supervisorScope {
launch {
try {
service.markItemAsRead(id)
} catch(ignored:Throwable) { }
}
return#supervisorScope withContext(Dispatchers.Default) {
try {
val response : ItemEntity = service.getItemById(id)
val item = response.toData()
Result.Success(item)
} catch (t: Throwable) {
Result.Failure(t)
}
}
}
service.markItemAsRead(id) and service.getItemById(id) will execute in parallel.
is there another way of doing this inside the repository?
No there isn't
(which I have read is incorrect to do inside a suspending function)
It is perfectly okay to use launch in a suspend function for a fire and forget task
See if the coroutineScope function does what you want:
Creates a CoroutineScope and calls the specified suspend block with this scope. The provided scope inherits its coroutineContext from the outer scope, but overrides the context's Job.
suspend fun getItemById(id:Int) : Result<ItemData> {
coroutineScope {
launch {
try {
service.markItemAsRead(id)
} catch(ignored:Throwable) { }
}
}
return try {
val response : ItemEntity = service.getItemById(id)
val item = response.toData()
Result.Success(item)
} catch (t:Throwable) {
Result.Failure(t)
}
}
you want to do multiple tasks in parallel but return function when all tasks done. if i'm right.
you can use async/await. inside suspend function
val d1 = async { t1() }
val d2 = async { t2() }
d1.await()
d2.await()
// all tasks done
t1 and t2 will be run in parallel. when t1.await() calls it's gonna wait for result but t2 still running.
in your function you can change it like this:
suspend fun getItemById(id:Int) : Result<ItemData> = coroutineScope {
val t1 = async {
try {
service.markItemAsRead(id)
} catch(ignored:Throwable) {
null
}
}
val t2 = async {
try {
val response : ItemEntity = service.getItemById(id)
val item = response.toData()
Result.Success(item)
} catch (t:Throwable) {
Result.Failure(t)
}
}
t1.await()
return#coroutineScope t2.await()
}
i just created an app where my function getdata() call every second to fetch new data from server and updateui() function will update view in UI i don't use any asynctask or coroutine in my app i wants to do this please tell me how i can do that.
here's my code...
private fun getdata(){
try {
val api = RetroClient.getApiService()
call = api.myJSON
call!!.enqueue(object : Callback<ProductResponse> {
override fun onResponse(
call: Call<ProductResponse>,
response: Response<ProductResponse>
) {
if (response.isSuccessful) {
productList = response.body()!!.data
for (list in productList) {
if (list.BB.equals("AAA")) {
aProductList.add(list)
}
}
if (recyclerView.adapter != null) {
eAdapter!!.updatedata(aProductList)
}
updateui()
}
}
override fun onFailure(call: Call<ProductResponse>, t: Throwable) {
println("error")
}
})
} catch (ex: Exception) {
} catch (ex: OutOfMemoryError) {
}
Handler().postDelayed({
getdata()
}, 1000)
}
private fun updateui() {
try {
//some code to handel ui
} catch (e: NumberFormatException) {
} catch (e: ArithmeticException) {
} catch (e: NullPointerException) {
} catch (e: Exception) {
}
}
To run a function every second with coroutines:
val scope = MainScope() // could also use an other scope such as viewModelScope if available
var job: Job? = null
fun startUpdates() {
stopUpdates()
job = scope.launch {
while(true) {
getData() // the function that should be ran every second
delay(1000)
}
}
}
fun stopUpdates() {
job?.cancel()
job = null
}
However, if getData() only starts a network request and doesn't wait for its completion, this might not be a very good idea. The function will be called a second after it finished, but because the network request is done asynchronously it may be scheduled way too much.
For example if the network request takes 5 seconds, it will have been started 4 more times before the first one even finished!
To fix this, you should find a way to suspend the coroutine until the network request is done.
This could be done by using a blocking api, then pass Dispatchers.IO to the launch function to make sure it's done on a background thread.
Alternatively you could use suspendCoroutine to convert a callback-based api to a suspending one.
Update - Lifecycle scope
Inside a component with a Android Lifecycle you could use the following code to automate repeating ui updates:
fun startUpdates() {
val lifecycle = this // in Activity
val lifecycle = viewLifecycleOwner // in Fragment
lifecycle.lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
// this block is automatically executed when moving into
// the started state, and cancelled when stopping.
while (true) {
getData() // the function to repeat
delay(1000)
}
}
}
}
This code requires the current androidx.lifecycle:lifecycle-runtime-ktx dependency.
The above remark about async, blocking or suspending code inside getData() still applies.
it's not advisable to hit the server every second. if you need to get data continuously try the socket. Because some times your server takes more than a few seconds to respond to your request. Then all your requests will be in a queue..if you still need to try with this.
fun repeatFun(): Job {
return coroutineScope.launch {
while(isActive) {
//do your network request here
delay(1000)
}
}
}
//start the loop
val repeatFun = repeatRequest()
//Cancel the loop
repeatFun.cancel()
For those who are new to Coroutine
add Coroutine in Build.gradle
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2'
To create a repeating Job
/**
* start Job
* val job = startRepeatingJob()
* cancels the job and waits for its completion
* job.cancelAndJoin()
* Params
* timeInterval: time milliSeconds
*/
private fun startRepeatingJob(timeInterval: Long): Job {
return CoroutineScope(Dispatchers.Default).launch {
while (NonCancellable.isActive) {
// add your task here
doSomething()
delay(timeInterval)
}
}
}
To start:
Job myJob = startRepeatingJob(1000L)
To Stop:
myJob .cancel()
I ended up doing like this with an extension function:
fun CoroutineScope.launchPeriodicAsync(repeatMillis: Long, action: () -> Unit) = this.async {
while (isActive) {
action()
delay(repeatMillis)
}
}
then call it like:
val fetchDatesTimer = CoroutineScope(Dispatchers.IO)
.launchPeriodicAsync(TimeUnit.MINUTES.toMillis(1)) {
viewModel.fetchDeliveryDates()
}
and cancel it like:
fetchDatesTimer.cancel()
My solution in Kotlin inside MainViewModel
fun apiCall() {
viewModelScope.launch(Dispatchers.IO) {
while(isActive) {
when(val response = repository.getServerData()) {
is NetworkState.Success -> {
getAllData.postValue(response.data)
}
is NetworkState.Error -> this#MainViewModel.isActive = false
}
delay(1000)
}
}
}
sealed class NetworkState<out R> {
data class Success<out T>(val data: T): NetworkState<T>()
data class Error(val exception: String): NetworkState<Nothing>()
object Loading: NetworkState<Nothing>()
}
My solution for one time running a code after check for something is successful and checking for that periodically, function is:
fun CoroutineScope.launchPeriodic(repeatMillis: Long, action: () -> Unit) : Job {
return launch {
while (!enabled) {
action()
delay(repeatMillis)
}
}
}
and start periodic function here (in which action runs every 2 seconds), which automatically ends up when something is enabled and some code run:
CoroutineScope(Dispatchers.IO).launchPeriodic(TimeUnit.SECONDS.toMillis(2)) {
if(checkIfSomethingIsEnabledCodeIsHere) {
enabled = true
//some code here to run when it is enabled
}
}
I'm trying to use the Android MVVM pattern with a repository class and Retrofit for network calls. I have the common problem that I can't get the coroutine to wait for the network response to return.
This method is in my ViewModel class:
private fun loadConfigModel() {
val model = runBlocking {
withContext(Dispatchers.IO) {
configModelRepository.getConfigFile()
}
}
configModel.value = model
}
In ConfigModelRepository, I have this:
suspend fun getConfigFile(): ConfigModel {
val configString = prefs.getString(
ConfigViewModel.CONFIG_SHARED_PREF_KEY, "") ?: ""
return if (configString.isEmpty() || isCacheExpired()) {
runBlocking { fetchConfig() }
} else {
postFromLocalCache(configString)
}
}
private suspend fun fetchConfig(): ConfigModel {
return suspendCoroutine { cont ->
dataService
.config() // <-- LAST LINE CALLED
.enqueue(object : Callback<ConfigModel> {
override fun onResponse(call: Call<ConfigModel>, response: Response<ConfigModel>) {
if (response.isSuccessful) {
response.body()?.let {
saveConfigResponseInSharedPreferences(it)
cont.resume(it)
}
} else {
cont.resume(ConfigModel(listOf(), listOf()))
}
}
override fun onFailure(call: Call<ConfigModel>, t: Throwable) {
Timber.e(t, "config fetch failed")
cont.resume(ConfigModel(listOf(), listOf()))
}
})
}
}
My code runs as far as dataService.config(). It never enters onResponse or onFailure. The network call goes and and returns properly (I can see this using Charles), but the coroutine doesn't seem to be listening for the callback.
So, my question is the usual one. How can I get the coroutines to block such that they wait for this callback from Retrofit? Thanks.
The problem must be that response.body() returns null since that is the only case that is missing a call to cont.resume(). Make sure to call cont.resume() also in that case and your code should at least not get stuck.
But like CommonsWare points out, even better would be to upgrade to Retrofit 2.6.0 or later and use native suspend support instead of rolling your own suspendCoroutine logic.
You should also stop using runBlocking completely. In the first case, launch(Dispatchers.Main) a coroutine instead and move configModel.value = model inside of it. In the second case you can just remove runBlocking and call fetchConfig() directly.
I am building an app based off of the Android Clean Architecture Kotlin version (https://github.com/android10/Android-CleanArchitecture-Kotlin).
Using this architecture, each time you want to invoke a use case, a Kotlin coroutine is launched and the result is posted in the main thread. This is achieved by this code:
abstract class UseCase<out Type, in Params> where Type : Any {
abstract suspend fun run(params: Params): Either<Failure, Type>
fun execute(onResult: (Either<Failure, Type>) -> Unit, params: Params) {
val job = async(CommonPool) { run(params) }
launch(UI) { onResult.invoke(job.await()) }
}
In his example architecture, Mr. Android10 uses Retrofit to make a synchronous api call inside the kotlin couroutine. For example:
override fun movies(): Either<Failure, List<Movie>> {
return when (networkHandler.isConnected) {
true -> request(service.movies(), { it.map { it.toMovie() } }, emptyList())
false, null -> Left(NetworkConnection())
}
}
private fun <T, R> request(call: Call<T>, transform: (T) -> R, default: T): Either<Failure, R> {
return try {
val response = call.execute()
when (response.isSuccessful) {
true -> Right(transform((response.body() ?: default)))
false -> Left(ServerError())
}
} catch (exception: Throwable) {
Left(ServerError())
}
}
'Either' represents a disjoint type, meaning the result will either be a Failure or the object of type T you want.
His service.movies() method is implemented like so (using retrofit)
#GET(MOVIES) fun movies(): Call<List<MovieEntity>>
Now here is my question. I am replacing retrofit with Google Cloud Firestore. I know that currently, Firebase/Firestore is an all async library. I want to know if anyone knows of a method more elegant way of making a synchronous API call to Firebase.
I implemented my own version of Call:
interface Call<T: Any> {
fun execute(): Response<T>
data class Response<T>(var isSuccessful: Boolean, var body: T?, var failure: Failure?)
}
and my API call is implemented here
override fun movieList(): Call<List<MovieEntity>> = object : Call<List<MovieEntity>> {
override fun execute(): Call.Response<List<MovieEntity>> {
return movieListResponse()
}
}
private fun movieListResponse(): Call.Response<List<MovieEntity>> {
var response: Call.Response<List<MovieEntity>>? = null
FirebaseFirestore.getInstance().collection(DataConfig.databasePath + MOVIES_PATH).get().addOnCompleteListener { task ->
response = when {
!task.isSuccessful -> Call.Response(false, null, Failure.ServerError())
task.result.isEmpty -> Call.Response(false, null, MovieFailure.ListNotAvailable())
else -> Call.Response(true, task.result.mapTo(ArrayList()) { MovieEntity.fromSnapshot(it) }, null)
}
}
while (response == null)
Thread.sleep(50)
return response as Call.Response<List<MovieEntity>>
}
Of course, the while loop at the end bothers me. Is there any other, more elegant ways, to wait for the response to be assigned before returning from the movieListResponse method?
I tried calling await() on the Task that is returned from the Firebase get() method, but the movieListResponse method would return immediately anyway. Thanks for the help!
So I found what I was looking for in the Google Tasks API: "If your program is already executing in a background thread you can block a task to get the result synchronously and avoid callbacks" https://developers.google.com/android/guides/tasks#blocking
So my previous problematic code becomes:
private fun movieListResponse(): Call.Response<List<MovieEntity>> {
return try {
val taskResult = Tasks.await(FirebaseFirestore.getInstance().
collection(DataConfig.databasePath + MOVIES_PATH).get(), 2, TimeUnit.SECONDS)
Call.Response(true, taskResult.mapTo(ArrayList()) { MovieEntity.fromSnapshot(it) }, null)
} catch (e: ExecutionException) {
Call.Response(false, null, Failure.ServerError())
} catch (e: InterruptedException) {
Call.Response(false, null, Failure.InterruptedError())
} catch (e: TimeoutException) {
Call.Response(false, null, Failure.TimeoutError())
}
}
Note I no longer need my Thread.sleep while loop.
This code should only be run in a background thread/kotlin coroutine.
This is overengineered, there are several layers trying to do the same thing. I suggest you go back a few steps, undo the abstractions and get into the mood of using coroutines directly. Implement a suspend fun according to this template. You don't need the crutches of Either, handle exceptions in the most natural way: a try-catch around a suspend fun call.
You should end up with a signature as follows:
suspend fun movieList(): List<MovieEntity>
Call site:
launch(UI) {
try {
val list = movieList()
...
} catch (e: FireException) {
// handle
}
}
That's is not the way how firebase works. Firebase is based on callback.
I recommend architecture component's livedata.
Please check the following example.
here is a link: https://android.jlelse.eu/android-architecture-components-with-firebase-907b7699f6a0