Coroutines - Function vs Procedure - android

When transforming (async function that needs to do some backgorund work to return a value) to sync function with coroutines one can use suspendCoroutine, as in the following extension i did to FusedLocationProviderClient :
private suspend fun FusedLocationProviderClient.colastLocation(): Location = suspendCoroutine {
lastLocation.addOnSuccessListener { l -> l?.run { it.resume(l) } ?: run{ it.resumeWithException(Exception("location is null"))} }.addOnFailureListener { e -> it.resumeWithException(e) }
}
But what if i want to make an async procedure run sync? (procedure = function that i dont care of its returned value, i just want to wait for it to finish)
I can still use suspendCoroutine as i did for addGeofence here:
private suspend fun GeofencingClient.coaddGeofences(geofencingRequest: GeofencingRequest, broadcast: PendingIntent?) : Void = suspendCoroutine {
addGeofences(geofencingRequest,broadcast).addOnSuccessListener { v -> it.resume(v) }.addOnFailureListener { e -> it.resumeWithException(e) }
}
But it feels a little hacky working with that Void object, maybe there is a more natural way to do that? e.g run async job and make it sync with coroutines when i dont care for the value (if returned - its a success, else it would throw an exception).
Theoretically i would expect doing something like that :
private suspend fun GeofencingClient.coaddGeofences(geofencingRequest: GeofencingRequest, broadcast: PendingIntent?) = suspendCoroutine {
addGeofences(geofencingRequest,broadcast).addOnSuccessListener { v -> it.resume() }.addOnFailureListener { e -> it.throwException(e) }
}

As Moira has mentioned, you can just use Kotlin's Unit object. From Unit `s Javadoc:
The type with only one value: the Unit object. This type corresponds to the void type in Java.
So your procedure code will look like:
private suspend fun GeofencingClient.coaddGeofences(geofencingRequest: GeofencingRequest, broadcast: PendingIntent?) = suspendCoroutine<Unit> {
addGeofences(geofencingRequest,broadcast).addOnSuccessListener { it.resume(Unit) }.addOnFailureListener { e -> it.resumeWithException(e) }
}

Related

How to properly use firebase-firestore async calls

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() }
}
}
}

Kotlin Multiplatform Mobile: Ktor - how to cancel active coroutine (network request, background work) in Kotlin Native (iOS)?

In my project I write View and ViewModel natively and share Repository, Db, networking.
When user navigates from one screen to another, I want to cancel all network requests or other heavy background operations that are currently running in the first screen.
Example function in Repository class:
#Throws(Throwable::class)
suspend fun fetchData(): List<String>
In Android's ViewModel I can use viewModelScope to automatically cancel all active coroutines. But how to cancel those tasks in iOS app?
Lets suppose that the object session is a URLSession instance, you can cancel it by:
session.invalidateAndCancel()
I didn't find any first party information about this or any good solution, so I came up with my own. Shortly, it will require turning repository suspend functions to regular functions with return type of custom interface that has cancel() member function. Function will take action lambda as parameter. On implementation side, coroutine will be launched and reference for Job will be kept so later when it is required to stop background work interface cancel() function will cancel job.
In addition, because it is very hard to read type of error (in case it happens) from NSError, I wrapped return data with custom class which will hold error message and type. Earlier I asked related question but got no good answer for my case where ViewModel is written natively in each platform.
If you find any problems with this approach or have any ideas please share.
Custom return data wrapper:
class Result<T>(
val status: Status,
val value: T? = null,
val error: KError? = null
)
enum class Status {
SUCCESS, FAIL
}
data class KError(
val type: ErrorType,
val message: String? = null,
)
enum class ErrorType {
UNAUTHORIZED, CANCELED, OTHER
}
Custom interface
interface Cancelable {
fun cancel()
}
Repository interface:
//Convert this code inside of Repository interface:
#Throws(Throwable::class)
suspend fun fetchData(): List<String>
//To this:
fun fetchData(action: (Result<List<String>>) -> Unit): Cancelable
Repository implementation:
override fun fetchData(action: (Result<List<String>>) -> Unit): Cancelable = runInsideOfCancelableCoroutine {
val result = executeAndHandleExceptions {
val data = networkExample()
// do mapping, db operations, etc.
data
}
action.invoke(result)
}
// example of doing heavy background work
private suspend fun networkExample(): List<String> {
// delay, thread sleep
return listOf("data 1", "data 2", "data 3")
}
// generic function for reuse
private fun runInsideOfCancelableCoroutine(task: suspend () -> Unit): Cancelable {
val job = Job()
CoroutineScope(Dispatchers.Main + job).launch {
ensureActive()
task.invoke()
}
return object : Cancelable {
override fun cancel() {
job.cancel()
}
}
}
// generic function for reuse
private suspend fun <T> executeAndHandleExceptions(action: suspend () -> T?): Result<T> {
return try {
val data = action.invoke()
Result(status = Status.SUCCESS, value = data, error = null)
} catch (t: Throwable) {
Result(status = Status.FAIL, value = null, error = ErrorHandler.getError(t))
}
}
ErrorHandler:
object ErrorHandler {
fun getError(t: Throwable): KError {
when (t) {
is ClientRequestException -> {
try {
when (t.response.status.value) {
401 -> return KError(ErrorType.UNAUTHORIZED)
}
} catch (t: Throwable) {
}
}
is CancellationException -> {
return KError(ErrorType.CANCELED)
}
}
return KError(ErrorType.OTHER, t.stackTraceToString())
}
}
You probably have 3 options:
If you're using a some sort of reactive set up iOS side (e.g. MVVM) you could just choose to ignore cancellation. Cancellation will only save a minimal amount of work.
Wrap your iOS calls to shared code in an iOS reactive framework (e.g. combine) and handle cancellation using the iOS framework. The shared work would still be done, but the view won't be updated as your iOS framework is handling cancellation when leaving the screen.
Use Flow with this closable helper

How to handle database call errors using Flows

Usually I'm returning from my dao suspend function:
#Dao
interface DataDao {
#Query("SELECT * FROM data")
fun getAllData(): List<Data>
}
And handle the call within the repository:
class DataRepository(
private val dataDao: DataDao
) {
fun getAllData(): Flow<DataState> = flow {
val cacheResult = safeDatabaseCall(dispatcher = Dispatchers.IO) { dataDao.getAllData() }
//handle cacheResult, convert to DataState, emit DataState values
}.flowOn(Dispatchers.IO)
}
With generic fun:
suspend fun <T> safeDatabaseCall(
dispatcher: CoroutineDispatcher,
cacheCall: suspend () -> T?
): CacheResult<T?> {
return withContext(dispatcher) {
try {
withTimeout(10000L) {
CacheResult.Success(cacheCall.invoke())
}
} catch (t: Throwable) {
when (t) {
is TimeoutCancellationException -> {
CacheResult.Error("Timeout error")
}
else -> {
CacheResult.Error("Unknown error")
}
}
}
}
}
The problem is that I want return fun getAllData(): Flow<List<Data>> instead of fun getAllData(): List<Data> In order to get immediate updates, But if I'm returning Flow from the Dao, I can't handle the call with safe call and catch errors.
I thought about collecting the data, but if i'm collecting the data the call already done without error handling
Basically I need the cache result return CacheResult<Data> and not CacheResult<Flow<Data>>
How can I solve the problem And make a generic safeDatabaseCall while returning Flow from Dao?
So if I understand correctly you just want to handle the query and return of information safely in a flow. My only question is around the types. I can sorta assume Data DataState and CacheResult are not the same types so I use a "magic" function that converts the intermediary values to the correct one. You will need to adjust accordingly
class DataRepository(
private val dataDao: DataDao
) {
fun getAllData(): Flow<DataState> = flow {
val result = safeDatabaseCall(dispatcher = Dispatchers.IO) {
dataDao.getAllData()
}
// Emit the result
emit(result)
}.catch { t : Throwable ->
// Do our transformation like before
val result = when (t) {
is TimeoutCancellationException -> {
CacheResult.Error("Timeout error")
}
else -> {
CacheResult.Error("Unknown error")
}
}
// And because catch is actually extending a FlowCollector
// We can emit the result in the stream
emit(result)
}.map { cacheResult ->
convertToDataOrDataState(cacheResult)
}
You shouldn't need flowOn with a dispatcher here since the work inside this flow doesn't require thread dispatching
to Dispatcher.IO. The code we are putting in our flow, is purely exception handling and invoking a function. The only place that seems to require any manual dispatch changing is, safeDatabaseCall(). I am not familiar with this function but if it does exist and takes a dispatcher for the result of actualing making the db calls on an IO thread, then all should be good without flowOn. Otherwise you will be switching dispatchers from original dispatcher -> IO and then to IO again. It's not much but the extra no-op context switch doesn't add anything other than confusion later on.
The flow itself traps any upstream issues and you then make them part of the resulting flow

Abstract coroutines structure

With RxJava we can do something like this:
BaseViewModel
protected void subscribe(Completable completable, MutableLiveData<Response> response) {
mDisposable.add(
completable.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.doOnSubscribe(disposable -> response.setValue(Response.loading()))
.doFinally(() -> response.setValue(Response.idle()))
.subscribe(
() -> response.setValue(Response.success(true)),
e -> response.setValue(Response.error(e))
)
);
}
protected <T> void subscribe(Single<T> single, MutableLiveData<Response> response) {
mDisposable.add(
single.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.doOnSubscribe(disposable -> response.setValue(Response.loading()))
.doFinally(() -> response.setValue(Response.idle()))
.subscribe(
result -> response.setValue(Response.success(result)),
e -> response.setValue(Response.error(e))
)
);
}
Then, from repository we getting Single/Complete and pass it to our custom subscribe(), then we get generic Result with data(optional), very easy way to work with asynchronous requests.
How we can abstract coroutines with similar structure, instead of write Launch in every method in ViewModel and try/catch error manually?
Instead of closely following the code you already have with minimal adaptations, I suggest you review your design altogether when migrating to coroutines.
One important principle embedded into coroutines is structured concurrency. This isn't just about the coroutine scopes and cancellation, it is also about the use of futures by any name (be it CompletionStage, Deferred, Task, Single or any other). According to structured concurrency, a future is basically equivalent to a live thread that has no defined scope. You should avoid them.
Instead you should have clearly delineated places in the code that launch new concurrent work contained within a single top-level block of code provided at the launch site.
So far, that implies that you do have a launch block at each entry point into your code from the Android framework, and that's a lot of places due to the nature of the callback-oriented programming model.
However, everything within that block should be coded according to structured concurrency. If you have just one network call to make, your code is entirely sequential: make the call, get the response, process it. The network calls themselves become suspend functions that complete with the result of the call and do not accept callbacks. All the traditional design patterns from the world of blocking calls apply here.
See here for an intro to using coroutines with LiveData, it may help you map your design to the coroutine-oriented one:
https://developer.android.com/topic/libraries/architecture/coroutines#livedata
You are probably looking for something like this
CoroutineWrapper
fun <T> ViewModel.apiCx(context: CoroutineContext = Dispatchers.Default, init: suspend CxWrapper<T>.() -> Unit) {
val wrap = CxWrapper<T>(context)
wrap.launch {
try {
init.invoke(wrap)
callCx(wrap)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private fun <T> callCx(wrap: CxWrapper<T>) {
val response: Response<T>? = wrap.request
response?.let {
if (it.isSuccessful) {
wrap.success(it.body())
} else {
wrap.fail(Pair(it.code(), it.message()))
}
}
}
class CxWrapper<T>(override val coroutineContext: CoroutineContext) : CoroutineScope {
var request: Response<T>? = null
internal var success: (T?) -> Unit = {}
internal var fail: (Pair<Int, String?>) -> Unit = {}
fun success(onSuccess: (T?) -> Unit) {
success = onSuccess
}
fun error(onError: (Pair<Int, String?>) -> Unit) {
fail = onError
}
}
you can have this as a separate helper class and to use this from your ViewModel
apiCx<YourModelClass> {
request = yourApiCall()
success { yourModelClass ->
Log.d(TAG, "success")
}
error {
Log.e(TAG, "error")
}
}
You would just do the same, just adapted to coroutines. Just replace the different stream types with the suspension methods you need.
protected inline fun <T> MutableLiveData<Response>.subscribe(single: suspend () -> T) {
viewModelScope.launch {
try {
value = Response.loading()
value = withContext(Dispatchers.IO) {
Response.success(single())
}
} catch(e: Throwable) {
value = Response.error(e)
} finally {
value = Response.idle()
}
}
To use it just call with the livedata as receiver
responseLiveData.subscribe<T> {
singleFromRepo()
}
responseLiveData.subscribe<Unit> {
completableFromRepo()
}

How do you call a suspend function inside a SAM?

I'm trying to create a Flow that needs to emit values from a callback but I can't call the emit function since the SAM is a normal function
Here's the class with the SAM from a library that I can't really modify it the way I need it to be.
class ValueClass {
fun registerListener(listener: Listener) {
...
}
interface Listener {
fun onNewValue(): String
}
}
And here's my take on creating the Flow object
class MyClass(private val valueClass: ValueClass) {
fun listenToValue = flow<String> {
valueClass.registerListener { value ->
emit(value) // Suspension functions can only be called on coroutine body
}
}
}
I guess it would've been simple if I could change the ValueClass but in this case, I can't. I've been wrapping my head around this and trying to look for implementations.
At least from what I know so far, one solution would be to use GlobalScope like this
class MyClass(private val valueClass: ValueClass) {
fun listenToValue = flow<String> {
valueClass.registerListener { value ->
GlobalScope.launch {
emit(value)
}
}
}
}
Now, this works but I don't want to use GlobalScope since I'll be using viewModelScope to tie it to my app's lifecycle.
Is there any way to work around this?
Thanks in advance. Any help would be greatly appreciated!
You can use callbackFlow to create a Flow from the callback. It will look something like:
fun listenToValue(): Flow<String> = callbackFlow {
valueClass.registerListener { value ->
trySend(value)
channel.close() // close channel if no more values are expected
}
awaitClose { /*unregister listener*/ }
}
Or if only one value is expected from the callback, you can use suspendCoroutine or suspendCancellableCoroutine. It this case listenToValue() function must be suspend and later called from a coroutine(e.g. someScope.launch):
suspend fun listenToValue(): String = suspendCoroutine { continuation ->
valueClass.registerListener { value ->
continuation.resumeWith(value)
}
}

Categories

Resources