I have asynchronous functions with firebase and other APIs that depend on each other. So, to start task B has to finish task A.
The async functions are retuning MyResult which can be a success or failure. Now I’m doing it in that way
when(val resullt1 = function1UseCase.getresult1()){
is MyResult.Success ->{
when(val result2 = function2UseCase.getResult2()){
is MyResult.Succes ->{
//Do something or call another async function
}
is MyResult.Failure ->{
//Do something or call another async function
}
}
}
is MyResult.Failure ->{
//Do something or call another async function
}
}
Is there a better way to do it? Because when I have more nested tasks the code doesn’t look very well.
Thanks!
You can create a simple extension. Something like that
inline fun <F, R> MyResult<F>.then(function: (F) -> MyResult<R>) = when (this) {
is MyResult.Succes -> {
try {
function(result)
} catch (throwable: Throwable) {
MyResult.Failure(your_error_handling_here)
}
}
is MyResult.Failure -> this
}
Annnd then it will be like that
when(val result = function1UseCase.getresult1().then { function2UseCase.getResult2() }) {
is MyResult.Success -> {
}
is MyResult.Failure -> {
//Do something or call another async function
}
}
You can slightly modify this if you need to have both results at the very end ;)
Related
I have the following function that can take an activity as an argument and when I call it from an Activity it works perfectly. Now that I want to call this function from a fragment but I can see there is an error in the editor saying 'Incompatible types: CargoFragment and Activity'. I tried replacing activity: Activity with context: Context.
The error I have is at 'is CargoFragment'
fun getProductList(activity: Activity) {
mFireStore.collection("abc")
.get()
.addOnSuccessListener {
.....
.....
.....
productList.add(product)
}
when (activity) {
is CargoActivity -> {
activity.success(productList)
}
is CheckoutActivity -> {
activity.success(productList)
}
is CargoFragment -> {
activity.success(productList)
}
}
}
.addOnFailureListener { e ->
Log.d("CheckTag", e.message!!)
when (activity) {
is CargoActivity -> {
activity.hideProgressDialog()
}
is CheckoutActivity -> {
activity.hideProgressDialog()
}
}
}
}
Don't pass activity to getProductList. As far as I understand, you are passing activity to execute some code when you get a response (success or failure). A better way to implement this is to expose callback lambdas.
Consider this approach:
fun getProductList(onSuccess: (List<Product>) -> Unit, onFailure:() -> Unit) {
mFireStore.collection("abc")
.get()
.addOnSuccessListener {
...
productList.add(product)
}
onSuccess(productList)
}
.addOnFailureListener { e ->
...
onFailure()
}
}
Usage (in your activity and fragment):
getProductList(
onSuccess = { list ->
success(list) // whatever you want to do on success
},
onFailure = {
hideProgressBar() // whatever you want to do on failure
}
)
if this is your viewModel, then u should NEVER have refernce to any context/activity/fragment.
Best approach would be to to have a liveData to hold progress states and let UI (activity or fragment) observe this.
No need to change anything in function parameter.
Call it from activity
getProductList(this)
and call it from fragment
getProductList(getActivity()).
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()
}
I've created rx function to call a network call from view-model in android, it parses network on main thread function.
I just change few line of code it worked. but i need to know the reason for this because its use same builder pattern to create a rx-call.
once I tried with changing .doOnSubscribe() ,doOnComplete () , .applySchedulers() after the flatmap call it worked? how is this happened?
fun loadjobs(var countryID:String){
subscription.add(
repository.getMainJobsFromLocal(countryID)
.doOnSubscribe { postProgress(StatusModel(Status.IN_PROGRESS))}
.doOnComplete { postProgress(StatusModel(Status.COMPLETED)) }
.applySchedulers()
.flatMap {
if (it.isNullOrEmpty()) {
repository.getMainJobsFromServer(countryID)
} else {
Flowable.just(Response.success(it))
}
}
.subscribe({
if (it.isResponseOk()) {
postProgress(StatusModel(Status.SUCCESS))
mainJobResponse.postValue(it.body())
} else {
postProgress(StatusModel(Status.FAILED))
mainJobResponse.postValue(null)
}
}, {
postProgress(StatusModel(Status.FAILED))
mainJobResponse.postValue(null)
}))
}
fun loadjobs(var countryID){
subscription.add(
repository.getMainJobsFromLocal(countryID)
.flatMap {
if (it.isNullOrEmpty()) {
repository.getMainJobsFromServer(countryID).flatMap {
Flowable.just(it)
}
} else {
Flowable.just(Response.success(it))
}
}.doOnSubscribe { postProgress(StatusModel(Status.IN_PROGRESS)) }
.doOnComplete { postProgress(StatusModel(Status.COMPLETED)) }
.applySchedulers()
.subscribe({
if (it.isResponseOk()) {
postProgress(StatusModel(Status.SUCCESS))
mainJobResponse.postValue(it.body())
} else {
postProgress(StatusModel(Status.FAILED))
mainJobResponse.postValue(null)
}
}, {
postProgress(StatusModel(Status.FAILED))
mainJobResponse.postValue(null)
}))
}
applySchedulers() after the flatmap call it worked? how is this happened?
observeOn() affects everything downstream. If you have a flatMap() after observeOn(), it gets executed on that scheduler.
Similarly subscribeOn() affects the upstream chain.
For these reasons, for most use cases you'd want to have the schedulers applied at the end of your rx chain and not in the middle.
Add subscribeOn(Schedulers.io()) and observeOn(AndroidSchedulers.mainThread()) to your Observable.
This is what I want:
Check if I have data about products in database.
If I have data I run Single to get data from DB.
If not I run Single for get data from backend
If I get response I want to save data in DB using Completable.
After saving data I want to map values from step 2 or 3 to view model
In result I want to send data to activity.
This is what I have now:
checkProductsInDBUseCase.run()
.flatMap {
if (it) {
getProductsFromDBUseCase.run()
} else {
getProductsUseCase.run(3)
}
}.map {
it.products.map { item -> item.toViewModel() }
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onSuccess = {
view.showBikes(it)
},
onError = {
view.showBikesError(it.message.toString())
}
).addTo(disposables)
Between flat map and map I need to run saveDataUseCase(it), but I don't know how to pass itfrom completable to map. Any ideas?
If your saveDataUseCase() is Completable then you can do this
checkProductsInDBUseCase.run()
.flatMap {
if (it) {
getProductsFromDBUseCase.run()
} else {
getProductsUseCase.run(3)
}
}
.faltMap {
saveDataUseCase(it).toSingleDefault(it)
}
.map {
it.products.map { item -> item.toViewModel() }
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onSuccess = {
view.showBikes(it)
},
onError = {
view.showBikesError(it.message.toString())
}
).addTo(disposables)
But if you change return type of saveDataUseCase() to Unit, you can use Fred's answer. It would be better
Here I'd use doOnSuccess. This seems ideal especially because you're creating a side effect, which we usually use the doOnXXX methods for.
checkProductsInDBUseCase.run()
.flatMap {
if (it) {
getProductsFromDBUseCase.run()
} else {
getProductsUseCase.run(3)
}
}
.doOnSuccess {
saveDataUseCase(it)
}
.map {
it.products.map { item -> item.toViewModel() }
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onSuccess = {
view.showBikes(it)
},
onError = {
view.showBikesError(it.message.toString())
}
).addTo(disposables)
The method will not change the result of the flatMap so you will still get the correct object inside the map function.
I have problem. Solution can be easy but my head is so overheat...
I want to call method ONCE after forEach loop will finish job.
Thanks for any example solution!
override fun saveWorkers(workers: ArrayList<Worker>): Single<Boolean> {
LogMgr.d(TAG, "saveWorkers() : $workers")
// remove old workers for current Event Planner and save new
workers.forEach {
deleteOldWorkers(it.event_planner_id!!)
.subscribeOn(getSubscriptionSchedulerForSave())
.subscribe({ status ->
}, { error ->
})
}
return Single.create({ emitter ->
RXModelAdapter.from(Worker::class.java)
.saveAll(workers)
.subscribeOn(getSubscriptionSchedulerForSave())
.subscribe({
LogMgr.d(TAG, "saveWorkers() onComplete")
emitter.onSuccess(true)
}, {
LogMgr.e(TAG, "saveWorkers() onError ", it)
emitter.onError(it)
})
})
}
Proposed sulution
Observable.merge(workers.map {
deleteOldWorkers(it.event_planner_id!!)
})
Observable.merge(workers.map { deleteOldWorkers(it.event_planner_id!!) }).doOnComplete() perhaps?