I have to hit 3 API's to update the same screen so for this i think RxJava is the fastest way to do that in parallel. While i was searching for the implementation i came across Observable.zip(...) function as it can perform multiple API hits in parallel.
I am using Retrofit for calling API's and have already created Pojo class with gson annotation.
Sample Pojo classes:
data class ResponseGetFCData(
#SerializedName("End")
val end: String,
#SerializedName("Uni")
val uni: String,
#SerializedName("Y")
val y: Double
)
data class ResponseAK(
#SerializedName("End")
val end: String,
#SerializedName("Manu")
val manu: String,
#SerializedName("Start")
val start: String,
#SerializedName("TY")
val tY: Double
)
Sample Api Interface:
interface Api{
#GET("GetUniPI")
fun getFCdata(#Query("pi") pi: String
, #Query("uni") uni: String): Observable<ResponseGetFCData>
}
Objective : From the response of 2 out of 3 API's I have to compute some mathematical calculation and the third API response will carry data for recycler view. Here i have to compute (y * ty)/100 by taking y from API 1 and ty from API 2 and such similar computations.
MyCode: In activity onCreate(....):
val requests = ArrayList<Observable<*>>()
val backendApi = WinRetrofitHelper.winApiInstance()
requests.add(backendApi.getFCdata("","","",""))
requests.add(backendApi.getAKCountry())
requests.add(backendApi.getRecyclerData("","",""))
Observable
.zip(requests) {
}
)
.subscribe({
Log.e("Exe Summary","******************Success*******************")
}) {
Log.e("Exe Summary",it.stackTrace.toString())
}
So here i am not getting how to fetch the response from these 3 API's and how and where to compute the maths and how will i update the data in recyclerview adapter from 3rd API response.
Please help me to understand this with a better approach.
Or you can give coroutines a try. It has simple syntax easy to understand
fun toDoWorkConcurrent() {
job2 = launch {
try {
val work1 = async { getThingsDone(43) }
val work2 = async { getThingsDoneAgain(123) }
val result = computeResult(work1.await(), work2.await())
withContext(UI) {
tvResult1.text = result.toString()
}
} catch (exception: Exception) {
exception.printStackTrace()
}
}
}
private fun computeResult(await: Int, await1: Int): Int {
return await + await1
}
Edit: Source
Try using below:
Observable.zip(
backendApi.getFCdata("","","",""),
backendApi.getAKCountry(),
backendApi.getRecyclerData("","",""),
Function3<ResponseGetFCData, ResponseAK, List<ResponseMarket>, List<ResponseMarket>> {
fcData, akCountry, recyclerData ->
// Your operation here
return recyclerData
})
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe { /* Loading Start */ }
.doOnTerminate { /* Loading End */ }
.subscribe(
{ /* Successfully Synced */ },
{ /* Having error */ }
)
Please try like this
Observable.zip(yourobservalelist, new Function<Object[], Object>() {
#Override
public Object apply(Object[] objects) throws Exception {
return objects;
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(new Consumer<Disposable>() {
#Override
public void accept(Disposable disposable) throws Exception {
}
})
.doOnTerminate(new Action() {
#Override
public void run() throws Exception {
}
})
.subscribe(new Consumer<Object>() {
#Override
public void accept(Object o) throws Exception {
//Do something on successful completion of allrequests
//}
}
},
// Will be triggered if any error during requests will happen
new Consumer<Throwable>() {
#Override
public void accept(Throwable e) throws Exception {
//Do something on error completion of requests
e.printStackTrace();
}
});
}
Related
PROBLEM STATEMENT
: When i press register button for register new user it show register success response in toast from live data, but when i tried to do same button trigger it show again register success response message from API & then also show phone number exist response from API in toast. It means old response return by live data too. So how can i solve this recursive live data response return issue?
HERE is the problem video link to understand issue
Check here https://drive.google.com/file/d/1-hKGQh9k0EIYJcbInwjD5dB33LXV5GEn/view?usp=sharing
NEED ARGENT HELP
My Api Interface
interface ApiServices {
/*
* USER LOGIN (GENERAL USER)
* */
#POST("authentication.php")
suspend fun loginUser(#Body requestBody: RequestBody): Response<BaseResponse>
}
My Repository Class
class AuthenticationRepository {
var apiServices: ApiServices = ApiClient.client!!.create(ApiServices::class.java)
suspend fun UserLogin(requestBody: RequestBody) = apiServices.loginUser(requestBody)
}
My View Model Class
class RegistrationViewModel : BaseViewModel() {
val respository: AuthenticationRepository = AuthenticationRepository()
private val _registerResponse = MutableLiveData<BaseResponse>()
val registerResponse: LiveData<BaseResponse> get() = _registerResponse
/*
* USER REGISTRATION [GENERAL USER]
* */
internal fun performUserLogin(requestBody: RequestBody, onSuccess: () -> Unit) {
ioScope.launch {
isLoading.postValue(true)
tryCatch({
val response = respository.UserLogin(requestBody)
if (response.isSuccessful) {
mainScope.launch {
onSuccess.invoke()
isLoading.postValue(false)
_registerResponse.postValue(response.body())
}
} else {
isLoading.postValue(false)
}
}, {
isLoading.postValue(false)
hasError.postValue(it)
})
}
}
}
My Registration Activity
class RegistrationActivity : BaseActivity<ActivityRegistrationBinding>() {
override val layoutRes: Int
get() = R.layout.activity_registration
private val viewModel: RegistrationViewModel by viewModels()
override fun onCreated(savedInstance: Bundle?) {
toolbarController()
viewModel.isLoading.observe(this, {
if (it) showLoading(true) else showLoading(false)
})
viewModel.hasError.observe(this, {
showLoading(false)
showMessage(it.message.toString())
})
binding.registerbutton.setOnClickListener {
if (binding.registerCheckbox.isChecked) {
try {
val jsonObject = JSONObject()
jsonObject.put("type", "user_signup")
jsonObject.put("user_name", binding.registerName.text.toString())
jsonObject.put("user_phone", binding.registerPhone.text.toString())
jsonObject.put("user_password", binding.registerPassword.text.toString())
val requestBody = jsonObject.toString()
.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
viewModel.performUserLogin(requestBody) {
viewModel.registerResponse.observe(this){
showMessage(it.message.toString())
//return old reponse here then also new reponse multiple time
}
}
} catch (e: JSONException) {
e.printStackTrace()
}
} else {
showMessage("Please Accept Our Terms & Conditions")
}
}
}
override fun toolbarController() {
binding.backactiontoolbar.menutitletoolbar.text = "Registration"
binding.backactiontoolbar.menuicontoolbar.setOnClickListener { onBackPressed() }
}
override fun processIntentData(data: Uri) {}
}
your registerResponse live data observe inside button click listener, so that's why it's observing two times! your registerResponse live data should observe data out side of button Click listener -
override fun onCreated(savedInstance: Bundle?) {
toolbarController()
viewModel.isLoading.observe(this, {
if (it) showLoading(true) else showLoading(false)
})
viewModel.registerResponse.observe(this){
showMessage(it.message.toString())
}
viewModel.hasError.observe(this, {
showLoading(false)
showMessage(it.message.toString())
})
binding.registerbutton.setOnClickListener {
if (binding.registerCheckbox.isChecked) {
try {
val jsonObject = JSONObject()
jsonObject.put("type", "user_signup")
jsonObject.put("user_name", binding.registerName.text.toString())
jsonObject.put("user_phone", binding.registerPhone.text.toString())
jsonObject.put("user_password", binding.registerPassword.text.toString())
val requestBody = jsonObject.toString()
.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
viewModel.performUserLogin(requestBody) {
}
} catch (e: JSONException) {
e.printStackTrace()
}
} else {
showMessage("Please Accept Our Terms & Conditions")
}
}
}
LiveData is a state holder, it's not really meant to be used as an event stream. There is a number of articles however about the topic like this one which describe the possible solutions, including SingleLiveEvent implementation taken from google samples.
But as of now kotlin coroutines library provides better solutions. In particular, channels are very useful for event streams, because they implement fan-out behaviour, so you can have multiple event consumers, but each event will be handled only once. Channel.receiveAsFlow can be very convenient to expose the stream as flow. Otherwise, SharedFlow is a good candidate for event bus implementation. Just be careful with replay and extraBufferCapacity parameters.
I have a code in my repository which has to call two endpoints. I have used Flowable.zip() but it doesn't seem to return a value. The Call doesn't fail even if there is no network available.
fun fetchRateRemote(): Flowable<ResultWrapper<List<RateModel>>> {
return Flowable.zip<Flowable<CurrenciesDTO>, Flowable<RateDTO>, ResultWrapper<List<RateModel>>>(
{
apiEndpoints.fetchCurrencies(key)
}, {
apiEndpoints.fetchRate(key)
}, { t1, t2 ->
val rateList = mutableListOf<RateModel>()
t2.subscribe { rate->
for((k,v) in rate.quotes ){
val currency = k.removeRange(0,3)
t1.subscribe {cur->
val currencyName = cur.currencies[currency]
if (currencyName != null) {
rateList.add(RateModel("$currencyName ($currency)", v.toString()))
}
}
}
}
ResultWrapper.Success(rateList)
}).subscribeOn(Schedulers.io())
}
I use a wrapper to mimic state and this is what I do in my viewmodel.
private fun fetchRates(){
disposable.add(repository.fetchRateRemote()
.startWith(ResultWrapper.Loading)
.onErrorReturn {
ResultWrapper.Error(it)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableSubscriber<ResultWrapper<List<RateModel>>>() {
override fun onComplete() {}
override fun onNext(rate: ResultWrapper<List<RateModel>>) {
rates.postValue(rate)
}
override fun onError(error: Throwable) {
error.printStackTrace()
}
})
)
}
I then observe rate in my activity via LiveData. The wrapper or the observation isn't the issue. It works with other calls, I do not know why the zip call doesn't work. I'm fairly new to RxJava so If I didn't implement something correctly in my repository please help correct me.
Okay! I made a lot of mistakes with the code in the repository above but I managed to fix it. Here's the solution. The Type arguments for the zip method was wrong! I didn't call the BiFunction argument properly too.
fun fetchRateRemote(): Flowable<ResultWrapper<List<RateModel>>> {
return Flowable.zip<CurrenciesDTO, RateDTO, ResultWrapper<List<RateModel>>>(
apiEndpoints.fetchCurrencies(key), apiEndpoints.fetchRate(key), BiFunction { t1, t2 ->
val rateList = mutableListOf<RateModel>()
for((k,v) in t2.quotes ){
val currencyCode = k.removeRange(0,3)
val currencyName = t1.currencies[currencyCode]
if (currencyName != null) {
rateList.add(RateModel("$currencyName ($currencyCode)", v.toString()))
}
}
ResultWrapper.Success(rateList)
}).subscribeOn(Schedulers.io())
}
Hi I have some usecases which are written in Java which uses rxJava. I have converted them to kotlin files and instead of rxJava I have made them into couroutines suspend functions.
In my rxJava code I am making an api call from the usecase and it returns the result but at the same time onNext it does something and onError it does something.
How can I do the same thing in coroutines
here is my rxjava code
#PerApp
public class StartFuellingUseCase {
#Inject
App app;
#Inject
CurrentOrderStorage orderStorage;
#Inject
FuelOrderRepository repository;
#Inject
StartFuellingUseCase() {
// empty constructor for injection usage
}
public Observable<GenericResponse> execute(Equipment equipment) {
if (orderStorage.getFuelOrder() == null) return null;
DateTime startTime = new DateTime();
TimestampedAction action = new TimestampedAction(
app.getSession().getUser().getId(), null, startTime
);
return repository.startFuelling(orderStorage.getFuelOrder().getId(), action)
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(response -> onSuccess(startTime, equipment))
.doOnError(this::onError);
}
private void onSuccess(DateTime startTime, Equipment equipment) {
if (orderStorage.getFuelOrder() == null) return;
orderStorage.getFuelOrder().setStatus(FuelOrderData.STATUS_FUELLING);
equipment.getTimes().setStart(startTime);
app.saveState();
}
private void onError(Throwable e) {
Timber.e(e, "Error calling started fuelling! %s", e.getMessage());
}
}
I have re written the code in Kotlin using coroutines usecases
#PerApp
class StartFuellingUseCaseCoroutine #Inject constructor(
private val currentOrderStorage: CurrentOrderStorage,
private val fuelOrderRepository: FuelOrderRepository,
private val app: App
): UseCaseCoroutine<GenericResponse, StartFuellingUseCaseCoroutine.Params>() {
override suspend fun run(params: Params): GenericResponse {
val startTime = DateTime()
val action = TimestampedAction(
app.session.user.id, null, startTime
)
return fuelOrderRepository.startFuelling(
currentOrderStorage.fuelOrder!!.id,
action
)
//SHOULD RETURN THE VALUE FROM THE fuelOrderRepository.startFuelling
//AND ALSO
//ON NEXT
//CALL onSuccess PASSING startTime and equipment
//ON ERROR
//CALL onError
}
private fun onSuccess(startTime: DateTime, equipment: Equipment) {
if (currentOrderStorage.getFuelOrder() == null) return
currentOrderStorage.getFuelOrder()!!.setStatus(FuelOrderData.STATUS_FUELLING)
equipment.times.start = startTime
app.saveState()
}
private fun onError(errorMessage: String) {
Timber.e(errorMessage, "Error calling started fuelling! %s", errorMessage)
}
data class Params(val equipment: Equipment)
}
Can you please suggest how can i call onSuccess and onError similar to how we have in rxjava onnext and onError.
could you please suggest how to fix this
thanks
R
You can using Kotlin Flow like converted example below:
RxJava
private fun observable(
value: Int = 1
): Observable<Int> {
return Observable.create { emitter ->
emitter.onNext(value)
emitter.onError(RuntimeException())
}
}
Flow:
private fun myFlow(
value: Int = 1
): Flow<Int> {
return flow {
emit(value)
throw RuntimeException()
}
}
For more detail : https://developer.android.com/kotlin/flow
convert startFuelling to flow using flowOf, you can do below
return flowOf(repository
.startFuelling(orderStorage.getFuelOrder().getId(), action))
.onEach{response -> onSuccess(startTime, equipment)}
.catch{e -> onError(e) }
.flowOn(Dispatchers.IO) //this will make above statements to execute on IO
if you want to collect it on main thread, you can use launchIn
.onEach{ }
.launchIn(mainScope)//could be lifeCycleScope/viewModelScope
//or
CoroutineScope(Dispatchers.Main).launch{
flow.collect{}
}
I am accessing the server in my Android app. I want to get a list of my friends and a list of friend requests in different queries. They have to come at the same time. Then I want to show this data on the screen.
I tried to get data from two queries at using flatMap.
interactor.getColleagues() and interactor.getTest() returns the data type Observable<List<Colleagues>>
private fun loadColleaguesEmployer() {
if (disposable?.isDisposed == true) disposable?.dispose()
//запрос на список друзей
interactor.getColleagues(view.getIdUser() ?: preferences.userId)
.subscribeOn(Schedulers.io())
.flatMap {
interactor.getTest().subscribeOn(Schedulers.io())
.doOnNext {
result-> view.showTest(mapper.map(result))
}
}
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onNext = { result1 ->
//Обработка списка коллег работодателей
view.showColleagues(mapper.map(result1.filter { data -> data.typeFriend == "Работодатель" }))
},
onError = { it.printStackTrace() }
)
}
I want to get and process data from different queries at the same time.
Combining observable results of multiple async http requests with rxjava's Observable.zip.
public class Statistics {
public static void main(String[] args) {
List<Observable<ObservableHttpResponse>> observableRequests = Arrays.asList(
Http.getAsync("http://localhost:3001/stream"),
Http.getAsync("http://localhost:3002/stream"),
Http.getAsync("http://localhost:3003/stream"),
Http.getAsync("http://localhost:3004/stream"));
List<Observable<Stats>> observableStats = observableRequests.stream()
.map(observableRequest ->
observableRequest.flatMap(response ->
response.getContent()
.map(new EventStreamJsonMapper<>(Stats.class))))
.collect(toList());
Observable<List<Stats>> joinedObservables = Observable.zip(
observableStats.get(0),
observableStats.get(1),
observableStats.get(2),
observableStats.get(3),
Arrays::asList);
// This does not work, as FuncN accepts (Object...) https://github.com/Netflix/RxJava/blob/master/rxjava-core/src/main/java/rx/functions/FuncN.java#L19
// Observable<List<Stats>> joinedObservables = Observable.zip(observableStats, Arrays::asList);
joinedObservables
.take(10)
.subscribe(
(List<Stats> statslist) -> {
System.out.println(statslist);
double average = statslist.stream()
.mapToInt(stats -> stats.ongoingRequests)
.average()
.getAsDouble();
System.out.println("avg: " + average);
},
System.err::println,
Http::shutdown);
}
}
you can do it by simple operation zip like
private fun callRxJava() {
RetrofitBase.getClient(context).create(Services::class.java).getApiName()
.subscribeOn(Schedulers.single())
.observeOn(AndroidSchedulers.mainThread())
getObservable()
.flatMap(object : io.reactivex.functions.Function<List<User>, Observable<User>> {
override fun apply(t: List<User>): Observable<User> {
return Observable.fromIterable(t); // returning user one by one from usersList.
} // flatMap - to return users one by one
})
.subscribe(object : Observer<User> {
override fun onSubscribe(d: Disposable) {
showProgressbar()
}
override fun onNext(t: User) {
userList.add(t)
hideProgressBar()
}
override fun onError(e: Throwable) {
Log.e("Error---", e.message)
hideProgressBar()
}
override fun onComplete() {
userAdapter.notifyDataSetChanged()
}
})
}
this function combines your response from 2 queries
private fun getObservable(): Observable<List<User>> {
return Observable.zip(
getCricketFansObservable(),
getFootlaballFansObservable(),
object : BiFunction<List<User>, List<User>, List<User>> {
override fun apply(t1: List<User>, t2: List<User>): List<User> {
val userList = ArrayList<User>()
userList.addAll(t1)
userList.addAll(t2)
return userList
}
})
}
here is example of first observable
fun getCricketFansObservable(): Observable<List<User>> {
return RetrofitBase.getClient(context).create(Services::class.java).getCricketers().subscribeOn(Schedulers.io())
}
If both observables return the same data type and you don't mind mixing of both sources data - consider using Observable.merge()
For example:
Observable.merge(interactor.getColleagues(), interactor.getTest())
.subscribeOn(Schedulers.io())
.subscribe(
(n) -> {/*do on next*/ },
(e) -> { /*do on error*/ });
Note, that .merge() operator doesn't care about emissions order.
Zip combine the emissions of multiple Observables together via a
specified function
You can use Zip (rx Java) http://reactivex.io/documentation/operators/zip.html, some sudo code will be like this -
val firstApiObserver = apIs.hitFirstApiFunction(//api parameters)
val secondApiObserver = apIs.hitSecondApiFunction(//api parameters)
val zip: Single<SubscriptionsZipper>//SubscriptionsZipper is the main model which contains first& second api response model ,
zip = Single.zip(firstApiObserver, secondApiObserver, BiFunction { firstApiResponseModel,secondApiResponseModel -> SubscriptionsZipper(firstApiResponseModelObjectInstance, secondApiResponseModelObjectInstance) })
zip.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(object : SingleObserver<SubscriptionsZipper> {
override fun onSubscribe(d: Disposable) {
compositeDisposable.add(d)
}
override fun onSuccess(subscriptionsZipper: SubscriptionsZipper) {
Utils.hideProgressDialog()
//here you will get both api response together
}
override fun onError(e: Throwable) {
Utils.hideProgressDialog()
}
})
Hope it helps you .
I'm having a hard time making a call to my api. I'm using Reactivex with kotlin and Flowables. My API returns a list of items if the date I passed by the "If-Modified_since" header is less than the last update.
If there is no update I get as an app return android app a 304 error.
I need to do the following procedure.
1-> I make a call to the api
2-> If the call is successful, save the list in Realm and return to the viewmodel
3-> If the error is 304, I perform a cache search (Realm) of the items
4-> If it is another error, I return the error normally for the ViewModel
Here is the code below, but I'm not sure if it's that way.
override fun getTickets(eventId: String): Flowable<List<Ticket>> {
return factory
.retrieveRemoteDataStore()
.getTickets(eventId)
.map {
saveTickets(it)
it
}.onErrorResumeNext { t: Throwable ->
if (t is HttpException && t.response().code() == 304) {
factory.retrieveCacheDataStore().getTickets(eventId)
} else
//Should return error
}
The question is, what is the best way to do this?
Thank you.
I'm going to assume, that you're using Retrofit. If that's the case, then you could wrap your getTickets call in Single<Response<SomeModel>>. This way, on first map you can check the errorcode, something among the lines of:
...getTickets(id)
.map{ response ->
when {
response.isSuccessful && response.body!=null -> {
saveTickets(it)
it
}
!response.isSuccessful && response.errorCode() == 304 -> {
factory.retrieveCacheDataStore().getTickets(eventId)
}
else -> throw IOException()
}
}
This could of course be made pretty using standard/extension functions but wanted to keep it simple for readability purposes.
Hope this helps!
Most of my comments are my explanations.
data class Ticket(val id:Int) {
companion object {
fun toListFrom(jsonObject: JSONObject): TICKETS {
/**do your parsing of data transformation here */
return emptyList()
}
}
}
typealias TICKETS = List<Ticket>
class ExampleViewModel(): ViewModel() {
private var error: BehaviorSubject<Throwable> = BehaviorSubject.create()
private var tickets: BehaviorSubject<TICKETS> = BehaviorSubject.create()
/**public interfaces that your activity or fragment talk to*/
fun error(): Observable<Throwable> = this.error
fun tickets(): Observable<TICKETS> = this.tickets
fun start() {
fetch("http://api.something.com/v1/tickets/")
.subscribeOn(Schedulers.io())
.onErrorResumeNext { t: Throwable ->
if (t.message == "304") {
get(3)
} else {
this.error.onNext(t)
/** this makes the chain completed gracefuly without executing flatMap or any other operations*/
Observable.empty()
}
}
.flatMap(this::insertToRealm)
.subscribe(this.tickets)
}
private fun insertToRealm(tickets: TICKETS) : Observable<TICKETS> {
/**any logic here is mainly to help you save into Realm**/
/** I think realm has the option to ignore items that are already in the db*/
return Observable.empty()
}
private fun get(id: Int): Observable<TICKETS> {
/**any logic here is mainly to help you fetch from your cache**/
return Observable.empty()
}
private fun fetch(apiRoute: String): Observable<TICKETS> {
/**
* boilerplate code
wether you're using Retrofit or Okhttp, that's the logic you
should try to have
* */
val status: Int = 0
val rawResponse = ""
val error: Throwable? = null
val jsonResponse = JSONObject(rawResponse)
return Observable.defer {
if (status == 200) {
Observable.just(Ticket.toListFrom(jsonResponse))
}
else if (status == 304) {
Observable.error<TICKETS>(Throwable("304"))
}
else {
Observable.error<TICKETS>(error)
}
}
}
override fun onCleared() {
super.onCleared()
this.error = BehaviorSubject.create()
this.tickets = BehaviorSubject.create()
}
}