Better way to chain rxjava2 calls with conditional operations - android

I have the following code, that does one single call, gets the result of the call, which is a boolean, then makes the second call if the result is false.
private fun linkEmailAndTextTogether(contactPhoneNumber: ContactPhoneNumbers,phoneNumber : PhoneNumber) {
val single = SingleOnSubscribe<Boolean> {
contactPhoneNumber.doesEmailContactExist(phoneNumber)
}
Single.create(single)
.subscribeOn(Schedulers.io())
.subscribeWith(object : SingleObserver<Boolean> {
override fun onSuccess(phoneNumberDoesExist: Boolean) {
if (!phoneNumberDoesExist) {
val completable = CompletableOnSubscribe {
contactPhoneNumber.linkEmailAndTextTogether(phoneNumber)
}
compositeDisposable.add(Completable.create(completable)
.subscribeOn(Schedulers.io())
.subscribe())
}
}
override fun onSubscribe(d: Disposable) {
compositeDisposable.add(d)
}
override fun onError(e: Throwable) {
Timber.e(e,e.localizedMessage)
}
})
}
It seems like there should be a more elegant way to do this in some kind of chain.

you could use the flatMap operator - the downside is that you won't know if the first or the second failed.
Single.just(phoneNumber)
.subscribeOn(Schedulers.io())
.map { it -> contactPhoneNumber.doesEmailContactExist(it) }
.flatMap { it ->
if (it) {
return#flatMap contactPhoneNumber.linkEmailAndTextTogether(phoneNumber)
}
Single.just(it)
}.subscribe({}, Throwable::printStackTrace);

This should help.
val single = SingleOnSubscribe<Boolean> {
getSingle()
}
Single.create(single).map({
if (it){
return#map getCompleteable()
}
return#map Completable.complete()
})

Related

NetworkBoundResource with Kotlin coroutines

Do you have any ideas how to implement repository pattern with NetworkBoundResource and Kotlin coroutines? I know we can launch a coroutine withing a GlobalScope, but it may lead to coroutine leak. I would like to pass a viewModelScope as a parameter, but it is a bit tricky, when it comes to implementation (because my repository doesn't know a CoroutineScope of any ViewModel).
abstract class NetworkBoundResource<ResultType, RequestType>
#MainThread constructor(
private val coroutineScope: CoroutineScope
) {
private val result = MediatorLiveData<Resource<ResultType>>()
init {
result.value = Resource.loading(null)
#Suppress("LeakingThis")
val dbSource = loadFromDb()
result.addSource(dbSource) { data ->
result.removeSource(dbSource)
if (shouldFetch(data)) {
fetchFromNetwork(dbSource)
} else {
result.addSource(dbSource) { newData ->
setValue(Resource.success(newData))
}
}
}
}
#MainThread
private fun setValue(newValue: Resource<ResultType>) {
if (result.value != newValue) {
result.value = newValue
}
}
private fun fetchFromNetwork(dbSource: LiveData<ResultType>) {
val apiResponse = createCall()
result.addSource(dbSource) { newData ->
setValue(Resource.loading(newData))
}
result.addSource(apiResponse) { response ->
result.removeSource(apiResponse)
result.removeSource(dbSource)
when (response) {
is ApiSuccessResponse -> {
coroutineScope.launch(Dispatchers.IO) {
saveCallResult(processResponse(response))
withContext(Dispatchers.Main) {
result.addSource(loadFromDb()) { newData ->
setValue(Resource.success(newData))
}
}
}
}
is ApiEmptyResponse -> {
coroutineScope.launch(Dispatchers.Main) {
result.addSource(loadFromDb()) { newData ->
setValue(Resource.success(newData))
}
}
}
is ApiErrorResponse -> {
onFetchFailed()
result.addSource(dbSource) { newData ->
setValue(Resource.error(response.errorMessage, newData))
}
}
}
}
}
}
Update (2020-05-27):
A way which is more idiomatic to the Kotlin language than my previous examples, uses the Flow APIs, and borrows from Juan's answer can be represented as a standalone function like the following:
inline fun <ResultType, RequestType> networkBoundResource(
crossinline query: () -> Flow<ResultType>,
crossinline fetch: suspend () -> RequestType,
crossinline saveFetchResult: suspend (RequestType) -> Unit,
crossinline onFetchFailed: (Throwable) -> Unit = { Unit },
crossinline shouldFetch: (ResultType) -> Boolean = { true }
) = flow<Resource<ResultType>> {
emit(Resource.Loading(null))
val data = query().first()
val flow = if (shouldFetch(data)) {
emit(Resource.Loading(data))
try {
saveFetchResult(fetch())
query().map { Resource.Success(it) }
} catch (throwable: Throwable) {
onFetchFailed(throwable)
query().map { Resource.Error(throwable, it) }
}
} else {
query().map { Resource.Success(it) }
}
emitAll(flow)
}
The above code can be called from a class, e.g. a Repository, like so:
fun getItems(request: MyRequest): Flow<Resource<List<MyItem>>> {
return networkBoundResource(
query = { dao.queryAll() },
fetch = { retrofitService.getItems(request) },
saveFetchResult = { items -> dao.insert(items) }
)
}
Original answer:
This is how I've been doing it using the livedata-ktx artifact; no need to pass in any CoroutineScope. The class also uses just one type instead of two (e.g. ResultType/RequestType) since I always end up using an adapter elsewhere for mapping those.
import androidx.lifecycle.LiveData
import androidx.lifecycle.liveData
import androidx.lifecycle.map
import nihk.core.Resource
// Adapted from: https://developer.android.com/topic/libraries/architecture/coroutines
abstract class NetworkBoundResource<T> {
fun asLiveData() = liveData<Resource<T>> {
emit(Resource.Loading(null))
if (shouldFetch(query())) {
val disposable = emitSource(queryObservable().map { Resource.Loading(it) })
try {
val fetchedData = fetch()
// Stop the previous emission to avoid dispatching the saveCallResult as `Resource.Loading`.
disposable.dispose()
saveFetchResult(fetchedData)
// Re-establish the emission as `Resource.Success`.
emitSource(queryObservable().map { Resource.Success(it) })
} catch (e: Exception) {
onFetchFailed(e)
emitSource(queryObservable().map { Resource.Error(e, it) })
}
} else {
emitSource(queryObservable().map { Resource.Success(it) })
}
}
abstract suspend fun query(): T
abstract fun queryObservable(): LiveData<T>
abstract suspend fun fetch(): T
abstract suspend fun saveFetchResult(data: T)
open fun onFetchFailed(exception: Exception) = Unit
open fun shouldFetch(data: T) = true
}
Like #CommonsWare said in the comments, however, it'd be nicer to just expose a Flow<T>. Here's what I've tried coming up with to do that. Note that I haven't used this code in production, so buyer beware.
import kotlinx.coroutines.flow.*
import nihk.core.Resource
abstract class NetworkBoundResource<T> {
fun asFlow(): Flow<Resource<T>> = flow {
val flow = query()
.onStart { emit(Resource.Loading<T>(null)) }
.flatMapConcat { data ->
if (shouldFetch(data)) {
emit(Resource.Loading(data))
try {
saveFetchResult(fetch())
query().map { Resource.Success(it) }
} catch (throwable: Throwable) {
onFetchFailed(throwable)
query().map { Resource.Error(throwable, it) }
}
} else {
query().map { Resource.Success(it) }
}
}
emitAll(flow)
}
abstract fun query(): Flow<T>
abstract suspend fun fetch(): T
abstract suspend fun saveFetchResult(data: T)
open fun onFetchFailed(throwable: Throwable) = Unit
open fun shouldFetch(data: T) = true
}
#N1hk answer works right, this is just a different implementation that doesn't use the flatMapConcat operator (it is marked as FlowPreview at this moment)
#FlowPreview
#ExperimentalCoroutinesApi
abstract class NetworkBoundResource<ResultType, RequestType> {
fun asFlow() = flow {
emit(Resource.loading(null))
val dbValue = loadFromDb().first()
if (shouldFetch(dbValue)) {
emit(Resource.loading(dbValue))
when (val apiResponse = fetchFromNetwork()) {
is ApiSuccessResponse -> {
saveNetworkResult(processResponse(apiResponse))
emitAll(loadFromDb().map { Resource.success(it) })
}
is ApiErrorResponse -> {
onFetchFailed()
emitAll(loadFromDb().map { Resource.error(apiResponse.errorMessage, it) })
}
}
} else {
emitAll(loadFromDb().map { Resource.success(it) })
}
}
protected open fun onFetchFailed() {
// Implement in sub-classes to handle errors
}
#WorkerThread
protected open fun processResponse(response: ApiSuccessResponse<RequestType>) = response.body
#WorkerThread
protected abstract suspend fun saveNetworkResult(item: RequestType)
#MainThread
protected abstract fun shouldFetch(data: ResultType?): Boolean
#MainThread
protected abstract fun loadFromDb(): Flow<ResultType>
#MainThread
protected abstract suspend fun fetchFromNetwork(): ApiResponse<RequestType>
}
I am new to Kotlin Coroutine. I just come across this problem this week.
I think if you go with the repository pattern as mentioned in the post above, my opinion is feeling free to pass a CoroutineScope into the NetworkBoundResource. The CoroutineScope can be one of the parameters of the function in the Repository, which returns a LiveData, like:
suspend fun getData(scope: CoroutineScope): LiveDate<T>
Pass the build-in scope viewmodelscope as the CoroutineScope when calling getData() in your ViewModel, so NetworkBoundResource will work within the viewmodelscope and be bound with the Viewmodel's lifecycle. The coroutine in the NetworkBoundResource will be cancelled when ViewModel is dead, which would be a benefit.
To use the build-in scope viewmodelscope, don't forget add below in your build.gradle.
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-alpha01'

RxJava2 - Subscribing PublishSubject

private val searchSubject = PublishSubject.create<Boolean>()
private val compositeDisposable = CompositeDisposable()
fun textChange(){
searSubject.onNext(true)
}
fun getSubject(){
compositeDisposable += searchSubject
.doOnNext {
if (it) showLoading()
}
.switchMap { searchGithubReposObservable() }
.subscribeWith(object : DisposableObserver<List<GithubRepo>>() {
override fun onNext(t: List<GithubRepo>) {
hideLoading()
adapter.items = t
}
override fun onComplete() {
}
override fun onError(e: Throwable) {
hideLoading()
}
})
}
searchGithubReposObservable is the fucntion that returns the Observable<List<GithubRepo>>
I searched the sample code in the github for studying RxJava.
However, I can't understand the above code.
I know that to receive data from PublishSubject, I need to subscribe it.
In the above code, I thought that subscribeWith subscribes searchGithubReposObservable()'s return Observable , but I could get the data from PublishSubject when the textchange() is called.
Why is it possible?
The start of your RX chain you are listening to the publish subject.
compositeDisposable += searchSubject
.doOnNext {
if (it) showLoading()
}
Each time you call method textChange() you push to searchSubject which fires the RX chain all over again trigging the switchmap.
Yes is it possible you can get the data when textchange() method is getting called i have implemented this type of functionality while i was typing text api got called on textchange and i received data Below code.
I have written please check
autocompletetextview.debounce(500L, TimeUnit.MILLISECONDS)
.distinctUntilChanged()
.filter { it.trim().isNotEmpty() || it.isEmpty() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap {
Observable.just(callapi here )
}
.subscribe({
it.subscribe({ serviceResponse ->
if (serviceResponse.meta.status == KeyUtils.HTTP_SUCCESS ||
serviceResponse.meta.status == KeyUtils.STATUS_META_ERROR) {
setSuccessResponse(serviceResponse, true)
} else {
setSuccessResponse(serviceResponse, false)
}
}, { throwable ->
setErrorResponse(throwable)
}).collect()

How to achieve concurrency with Kotlin coroutines?

I have list of carousels and run over each carousel and based on the carousel query I do fetchAssets() and fetchAssets() is Kotlin suspended function but the problem is each function is called when the previous one is finished I want to achieve concurency?
uiScope.launch {
carousels.mapIndexed { index, carousel ->
when (val assetsResult = assetRepository.fetchAssets(carousel.query)) {
is Response.Success<List<Asset>> -> {
if (assetsResult.data.isNotEmpty()) {
val contentRow = ContentRow(assetsResult.data)
contentRows.add(contentRow)
contentRowsmutableData.postValue(contentRows)
}
}
is Response.Failure -> {
}
}
}
}
override suspend fun fetchAssets(query: String): Response<List<Asset>> {
return suspendCoroutine { cont ->doHttp(assetsEndpoint, JsonHttpCall("GET"),
object : JsonReaderResponseHandler() {
override fun onSuccess(jsonReader: JsonReader) {
val apiAsset = ApiAssetList(jsonReader)
cont.resume(Response.Success(apiAsset.items))
}
override fun onError(error: Error) {
cont.resume(Response.Failure("errorMessage"))
}
})
}
}```
You have to wrap your suspend function in an async block, then wait for all async operations to complete:
uiScope.launch {
val asyncList = carousels.map { carousel ->
async { assetRepository.fetchAssets(carousel.query) }
}
val results = asyncList.awaitAll()
results.forEach { result ->
when (result) {
is Response.Success -> TODO()
is Response.Failure -> TODO()
}
}
}
suspend fun fetchAssets(query: String): Response<List<Asset>>
Edit: if you want to update the UI as each completes, you need to change it like this:
carousels.forEach { carousel ->
uiScope.launch {
val result = fetchAssets(carousel.query)
when (result) {
is Response.Success -> {
if (result.data.isNotEmpty()) {
val contentRow = ContentRow(result.data)
contentRows.add(contentRow)
contentRowsmutableData.postValue(contentRows)
}
}
is Response.Failure -> TODO()
}
}
}
Check this for concurrency with Coroutines.

How can I convert this rxjava/rxkotlin flatMap into lambda expression?

Observable.just(1)
.flatMap(object : Function<Int, Observable<Int>> {
override fun apply(integer: Int): Observable<Int> {
return Observable.just(integer * 10)
}
})
.flatMap(object : Function<Int, Observable<Int>> {
override fun apply(integer: Int): Observable<Int> {
return Observable.just(integer * 20)
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<Int> {
override fun onComplete() {
}
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: Int) {
Log.d("result", "" + t)
}
override fun onError(e: Throwable) {
e.printStackTrace()
}
})
This should do.
Observable.just(1)
.flatMap {
return#flatMap Observable.just(it*10)
}.flatMap {
return#flatMap Observable.just(it*20)
}.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe({
//OnNext
Log.d("result", "" + it)
},{
it.printStackTrace()
//on error
},{
//on complete
})
actually, return#flatMap is not needed, so below works as well. Also, if you don't need all methods for a subscriber actually implemented, there's an overload with just onNext and onError. IDE's hints are of great help in here - when typing in a method, press Ctrl+P and it will show you available overloads. The keyboard shortcut is essentially "show me arguments".
Observable.just(1)
.flatMap { Observable.just(it * 10) }
.flatMap { Observable.just(it * 20) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ Log.d("result", "" + it) },
{ it.printStackTrace() }
)

RXjava2 method in fromCallable not getting exceuted

I am new to using rxjava and I am trying to run a function in background using rxjava2 but the method is not called the code I am using is given below let me know if its the right way to execute a function in background:
Observable.fromCallable<OrderItem>(Callable {
saveToDb(existingQty, newOty, product_id)
}).doOnSubscribe {
object : Observable<OrderItem>() {
override fun subscribeActual(emitter: Observer<in OrderItem>?) {
try {
val orderItem = saveToDb(existingQty, newOty, product_id)
emitter?.onNext(orderItem)
emitter?.onComplete()
} catch (e: Exception) {
emitter?.onError(e)
}
}
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).doOnSubscribe {
object : Observer<OrderItem> {
override fun onComplete() {
}
override fun onNext(t: OrderItem) {
}
override fun onError(e: Throwable) {
}
override fun onSubscribe(d: Disposable) {
}
}
}
You are dong it wrong way. doOnSubscribe() operator is called when observable is subscribed using subscribe() method and you haven't subscribed the observable using subscribe() method.
You have called saveToDb method in callable, then why are you calling it in doOnSubscribe? it doesn't make sense.
You should have written following code:
Observable.fromCallable { saveToDb(existingQty, newOty, product_id) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ orderItem ->
// set values to UI
}, { e ->
// handle exception if any
}, {
// on complete
})
to work with your logic.
DoOnSubscribe means "do when someone subscribe to it". But there is no subscribe in your code. Maybe you want to use subsribe instead of doOnSubscribe

Categories

Resources