I have a BehaviourSubject as a callback for my Retrofit method.
private val loadCompleted = BehaviourSubject.create<List<String>>()
Inside my retrofit OnResponse/onFailure, I call
loadCompleted.onNext(myList) //inside retrofit onResponse and
loadCompleted.onError("Error") // inside retrofit onFailure
I am subscribed to a function which returns loadCompleted.
fun loadingCompleted() : Observable<List<String>>{return loadCompleted}
and then I subscribe to the loadingCompleted as
loadingCompleted.subscribe{list ->
//only called once
anotherfun(list)
}
The first time my Retrofit function gets called, I am able to get my subscribe called but subsequent calls to the same function doesn't fire the subscribe. I am assuming the calls normally return the same value because it is just a refresh and the data might not have changed. However, I still need to get the subscribe called so I can react accordingly. I have tried both BS and ReplaySubject but the result is the same. How do I use an observable to ensure I always get the subscribe called whenever I call onNext(x)/onComplete(x) even though x might not have changed?
You are probably finishing your BehaviorSubject stream with onComplete/onError. If you don't want to do that, wrap x/error to some kind of Result which is sealed class and has Success and Failure subclasses. Then always use subject.onNext() to emit.
sealed class Result<T> {
class Success<T>(val t: T) : Result<T>()
class Failure<T>(val e: Throwable) : Result<T>()
}
class Test {
val subject: BehaviorSubject<Result<List<String>>> = BehaviorSubject.create()
fun call() {
subject.onNext(Result.Success(listOf("example")))
subject.onNext(Result.Failure(RuntimeException("error")))
subject.subscribe{result->
when(result){
is Result.Success -> print(result.t)
is Result.Failure -> print(result.e.message)
}
}
}
}
Related
I'm trying to chain two reactive calls which return Completable using retrofit on android:
val userRequest = ...
val languageRequest = ...
return userService.updateUser(userRequest)
.andThen { userService.updateMessagingUserLanguages(user.id, languageRequest) }
.doOnComplete { userRepository.updateUser(user) }
which are defined as follow:
#PUT("$BASE_USER_URL")
fun updateUser(#Body user: UserRequest): Completable
#PUT("$BASE_URL/{userId}/languages")
fun updateMessagingUserLanguages(#Path("userId") userId: Long, #Body request: MessagingLanguageDTO): Completable
The first Completable succeed and returns a response with a 200 status. However, the second call is never triggered (it never appears in my log and does not pass my breakpoint).
What am I missing here?
Try:
andThen(userService.updateMessagingUserLanguages(user.id, languageRequest))
IOW, replace the lambda expression as a parameter with the actual Completable that you want to add to the chain.
I am trying to set up retrofit and rxjava for my app to make api calls to a webservice. I am having some trouble subscribing to my observable object in the main activity. The architecture follows the viewmodel/repository pattern, here is my code
Repository
class WisproRepository() {
val request = ServiceBuilder.buildService(JsonPayments::class.java)
val apicall: Observable<Payment> = request.getPostsV2(2, 100, "authorizationcode")
fun getPayments(): Observable<Payment?>? {
return apicall.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
}
}
ViewModel
class PaymentsViewModel : ViewModel() {
var wisproRepository = WisproRepository()
var payments_rx: Observable<Payment?>? = wisproRepository.getTopContributors()
}
MainActivity
class MainActivity : AppCompatActivity() {
var adapter: MyRecyclerViewAdapter? = null
private var textView: TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//Getting payment object and updating view
val view_model: PaymentsViewModel by viewModels()
view_model.payments_rx?.subscribe(....)
}
Now, instead of observing the livedata I would have to subscribe to the Observable and setup recyclerview when the data arrives but I don't understand how to do that in the subscribe method. But I don't understand how to do that inside the subscribe method. Also, how can I access the data of the Payment inside the observable object ?
Also I do not understand these lines right here,
val request = ServiceBuilder.buildService(JsonPayments::class.java)
val apicall: Observable<Payment> = request.getPostsV2(2,100, "authorization code")
apicall.subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread())
Are subscribeOn and observeOn performing the call? And if they are, how does it work (I am used to enqueue method)? Am I missing something? How can I setup onResponse and onFailure callbacks?
If you use retrofit2:adapter-rxjava2 it automatically wrap your API call to Cold Observable and auto-execute API Request when you subscribe.
Are subscribeOn and observeOn performing the call
It doesn't perform call API, API call when you call subscribe it makes your API request run in Background Thread and handles response in Main Thread
How can I setup onResponse and onFailure callbacks?
In subscribe method you can pass Consumer to handle Response or Error
view_model.payments_rx?.subscribe({
// response
}, {
// error
})
An observable is basically a stream of events, each event is of the type that you're observing. In this case Payment. Every time you get a Payment object, the onNext() method of your observable is called. Once the stream is done emitting events onComplete will be called, and in case of an error, onError (both of these are terminal events)
You haven't posted the JsonPayments class so I can't say for sure, but you need to be creating an observable in getPostsV2() and returning it so you can observe it or subscribe to it.
If you want onResponse and onFailure callbacks, you could essentially wrap your Payment object to Result<Payment> where Result has both of those properties. Your final observable will be Observable<Result<Payment>> where each event will be of type Result<Payment>.
Let me start with example code snippets
suspend fun executeLive(result: MutableLiveData<Person>) {
val response = ... //suspend api request
mediatorLiveData.removeSource(response)
mediatorLiveData.addSource(response) {
result.value = sortData(it) // sortData is also suspend function which sortData at Dispatcher.Default
}
}
In this example, sortData can't call under lambda function(in this case addSource).And also I already declare executeLive as suspend, that why suspend api request can start at first. But sortData function show compile time error
Suspend function can only be called from a coroutine body
So how do I change my code structure to solve this problems?
Update: Is there any article about this?
A lambda is generally a callback function. Callback functions are so called because we wrap a block of code in a function, and pass it to someone else (or some place else) to be executed. It is a basic inversion of control where the code is not for you to execute, but someone else to do it (example the framework).
For example when you set a onClickListener on a button, we don't know when it will get called, we pass a lambda for the framework which takes care of the user interaction to call the specified action.
In your case similarly the suspend function is not calling the sortdata, it is passing it to the mediatorLiveData object to call it in its own context. It is not necessary the lambda you passed would be called from a coroutine body, as such this is not allowed.
You can solve this by converting the mediatorLiveData.addSource call into a suspending call itself with suspendCoroutine:
suspend fun executeLive(result: MutableLiveData<Person>) {
val response = ... //suspend api request
mediatorLiveData.removeSource(response)
val data = suspendCoroutine<TypeOfData> { cont ->
mediatorLiveData.addSource(response) { cont.resume(it) }
}
result.value = sortData(data)
}
I've used TypeOfData as a placeholder for whatever the type of data emitted by response is. Note that this will only work if the you're intending for a single emission to happen, though.
If you need to track multiple values, you can experiment with callbackFlow:
suspend fun executeLive(result: MutableLiveData<Person>) {
val response = ... //suspend api request
mediatorLiveData.removeSource(response)
callbackFlow<TypeOfData> {
mediatorLiveData.addSource(response) { offer(it) }
awaitClose { mediatorLiveData.removeSource(response) }
}
.collect { result.value = sortData(it) }
}
I have a Fragment, with a dynamic number of a custom views, consisting in an EditText and a Button. What I do is that every time the user types a price in the EditText and clicks the Button, I make an API request through a ViewModel, and my Fragment observes the LiveData in the ViewModel.
So far so good, when I use the first custom view. The problem comes on the second one (and the third), because the onChanged() method is apparently called even tho the data has not changed, and the second and the third custom views are listening to that data, so they change when they are NOT the ones triggering the data change (they receive the data change from the first one).
When the user clicks on the Button, the way I observe and fetch the price is this:
val observer = Observer<NetworkViewState> { networkViewState ->
processResponse(networkViewState, moneySpent, coin, date)
}
boardingHistoricalPriceViewModel.coinDayAveragePrice.observe(this, observer)
boardingHistoricalPriceViewModel.getDayAveragePrice(coin.symbol,
addedCoinDatePriceView.selectedSpinnerItem, dateInMillis)
and what is happening is that the method processResponse gets called when the second custom view triggered the API request, but the result I receive is the one that coinDayAveragePrice has before the API response arrives (this is the value after the first API response from the first custom view has arrived).
This is part of my ViewModel:
val coinDayAveragePrice: MutableLiveData<NetworkViewState> = MutableLiveData()
fun getDayAveragePrice(symbol: String, currency: String, dateInMillis: Long) {
coinRepository
.getDayAverage(symbol, currency, "MidHighLow", dateInMillis)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe { coinDayAveragePrice.postValue(NetworkViewState.Loading()) }
.subscribeBy(onSuccess = {
coinDayAveragePrice.postValue(NetworkViewState.Success(it))
}, onError = { throwable ->
coinDayAveragePrice.postValue(NetworkViewState.Error(throwable.localizedMessage))
})
}
NetworkViewState is just a sealed class meant as a wrapper for a response of an API request:
sealed class NetworkViewState {
class Loading : NetworkViewState()
class Success<out T>(val item: T) : NetworkViewState()
class Error(val errorMessage: String?) : NetworkViewState()
}
I have also tried to unsubscribe or to set the coinDayAveragePrice to null, but still I have the same problem.
Thanks a lot in advance!
So, without seeing your ViewModel, it's hard to be sure exactly what the problem is, but I think it's what I indicated in my comment. In that case, one solution is to use a different kind of LiveData. I got this basic idea from a blog post (don't remember the link :-/), but here's the class:
private const val TAG = "SingleLiveData"
/**
* A lifecycle-aware observable that sends only new updates after subscription, used for events like
* navigation and Snackbar messages.
*
* This avoids a common problem with events: on configuration change (like rotation) an update
* can be emitted if the observer is active. This LiveData only calls the observable if there's an
* explicit call to setValue() or call().
*
* Note that only one observer is going to be notified of changes.
*/
open class SingleLiveData<T> : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
#MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<T>) {
if (hasActiveObservers()) {
Logger.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner, wrapObserver(observer))
}
#MainThread
override fun observeForever(observer: Observer<T>) {
if (hasActiveObservers()) {
Logger.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
super.observeForever(wrapObserver(observer))
}
private fun wrapObserver(observer: Observer<T>): Observer<T> {
return Observer {
if (pending.compareAndSet(true, false)) {
observer.onChanged(it)
}
}
}
#MainThread
override fun setValue(t: T?) {
pending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
#MainThread
fun call() {
value = null
}
}
Obviously, one problem with this is that it won't permit multiple observers of the same live data. However, if you require that, hopefully this class will give you some ideas.
To make data accessible for offline viewing I have a data layer that first requests the data from database and secondly does a network call to get data from api (and stores it to database).
F.e. say i want to get recycle scores by user id:
Datalayer:
class RecycleScoreRepository{
fun getRecycleScoresByUserId(userId: Int): Observable<RecycleScores> {
return Observable.concatArray(
getRecycleScoresFromDb(userId),
getRecycleScoresFromApi(userId))}
}
object RepositoryManager {
...
fun getRecycleScoresByUserId(userId: Int): Observable<RecycleScores> {
return recycleScoreRepository.getRecycleScoresByUserId(userId)
//Drop DB data if we can fetch item fast enough from the API to avoid UI flickers
.debounce(400, TimeUnit.MILLISECONDS)} ...
Presenter:
RepositoryManager.getRecycleScoresByUserId(userId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
// do something on success
}, {
// do something on error
})
So my presenter is subscribing to the Repository to getRecycleScoresByUserId. I am using the debounce operator to make sure that in case the api call is fast enough that i am not setting returned values twice on ui as to prevent ui flickering. But now what happens is that when the database successfully returns me some recycleScores but for some reason api request response with an error that the subscriber in the presenter only receives an error and not the observable with values from the database.
How can I make sure the database's observable is received by subscribers and not being debounced when api call returns an error?
This may be not the best solution, but you could filter error from your api observable response in this part
fun getRecycleScoresByUserId(userId: Int): Observable<RecycleScores> {
return Observable.concatArray(
getRecycleScoresFromDb(userId),
getRecycleScoresFromApi(userId)
.materialize()
.filter{ !it.isOnError }
.dematerialize<RecycleScores>()
)}
}
then your subscriber will keep getting the result. For your second question to not debounce when getting error, I have no idea how to achieve that.
Edit:
To handle error from your API response, one idea is to wrap api response into another type then you can handle it properly. For example:
sealed class RecycleResponse {
class OK(val score: RecycleScore) : RecycleResponse()
class NotOK(val error: Exception) : RecycleResponse()
}
then you can use it like these:
fun getRecycleScoresByUserId(userId: Int): Observable<RecycleResponse> {
return Observable.concatArray(
getRecycleScoresFromDb(userId),
getRecycleScoresFromApi(userId))
.map<RecycleResponse> { RecycleResponse.OK(it) }
.onErrorReturn { RecycleResponse.NotOK(it) }
}