Context: My current application uses normal retrofit calls in order to get data from the api. I really wanted to introduce RX into the calls but I don't have much experience with that. I read some things online and none of them show me a simple way to do this. I'll show you what I have.
Purpose: To turn what I have into RXJava
This is my code :
My generic perform call method that I want to convert into RXJava:
fun <T> performCall(call: Call<T>?, callback: OnRequestCallback<T>) {
call?.enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
when (response.code()) {
200, 201 -> {
callback.onSuccess(response.body())
}
else -> {
callback.onError(response.errorBody())
}
}
return
}
override fun onFailure(call: Call<T>, t: Throwable) {
callback.onError(null)
Log.d("Network Manager Failure", t.localizedMessage)
}
})
}
Then to have a context from my activity I use this method that calls the perform call method:
fun <T> BaseActivity.callAPI(call: Observable<T>?, onSucceed: (T?) -> Unit, onError: (errorCode: String) -> Unit) {
NetworkManager.instance.performCall(call,
object : NetworkManager.OnRequestCallback<T> {
override fun onSuccess(body: T?) {
onSucceed(body)
}
override fun onError(errorResponseBody: ResponseBody?) {
JSONObject(errorResponseBody?.string()).let {
val errorCode = it.getJSONObject("meta").getString("errorCode")
when (errorCode) {
Errors.DEPRECATED_API_VERSION.name ->
onAppUpdateNeeded()
else -> onError(errorResponseBody)
}
}
}
})
}
Then the BaseActivitt.callApi() is used in every activity that needs api information, I now the use of view models + dagger is better but for now is what I have and I have to keep it.
Can someone show me how to turn this into an RXJava/Kotlin thing?
To be honest I don't like the idea of having these generic handlers. I had to work on a project that was written like that and it wasn't a nice experience: what happens if, for example, you want to handle the Errors.DEPRECATED_API_VERSION in a different way for a call?
Anyway, I would do something like this: from Retrofit return the Observable and then in the place where you need to make the call subscribe to the Observable.
val disposable = service
.yourApi()
.map { value ->
MyCommand.SuccessCommand(value)
}
.onErrorResumeNext { ex: Throwable -> YourObservableThatEmitsTheErrorCommandOrTheOnAppUpdateNeededCommand() }
.subscribe { command: MyCommand ->
when (command) {
is MyCommand.SuccessCommand -> {
}
is MyCommand.ErrorCommand -> {
}
is MyCommand.AppUpdateNeededCommand -> {
}
}
}
The command can be implemented something like
sealed class MyCommand {
class SuccessCommand<T> (value: T): MyCommand()
class ErrorCommand (val ex: Exception): MyCommand()
object AppUpdateNeededCommand: MyCommand()
}
Related
I'm new to kotlin coroutines. I've been trying to run multiple API calls in parallel and then when all the calls are done update my UI and dismiss the loader, but with no success. This is my code
private fun getScoreForType() {
val job = CoroutineScope(Dispatchers.IO).launch {
types.forEach { type ->
getScore(type)
}
}
runBlocking {
job.join()
// do some ui work
dismissLoader()
}
}
private fun getScore(type: String) {
val call = MyApi.getScores(type)
call.enqueue(object : Callback<Score> {
override fun onResponse(call: Call<Score>, response: Response<Score>) {
setScore(response)
}
override fun onFailure(call: Call<Score>, t: Throwable) {
}
})
}
I've also tried using async and awaitAll but couldn't make it work either. The loader is always dismissed before all the calls are done. Any help on how I could make this work would be much appreciated
Use Flow and collectData it will works as LiveData.
For example:
val myIntFlow = MutableStateFlow(-1)
Try something like;
in ViewModelMethods
private fun getScoreForType() {
It goes first:
CoroutineScope(Dispatchers.IO).launch {
types.forEach { type ->
getScore(type)
}
// it means to change value of flow
myIntFlow.value = 1
}
// Now collect data in fragment to change UI
}
// in fragment like:
CoroutineScope(Dispatchers.Main).launch {
// flow will be triggered, on every changed value
viewModel.myIntFlow.collect {
viewModel.methodFromViewModelToChangeUI()
dissmisloader()
myIntFlow.value = -1
}
}
// try the same here as you wish
private fun getScore(type: String) {
val call = MyApi.getScores(type)
call.enqueue(object : Callback<Score> {
override fun onResponse(call: Call<Score>, response: Response<Score>) {
setScore(response)
}
override fun onFailure(call: Call<Score>, t: Throwable) {
}
})
}
Context
I started working on a new project and I've decided to move from RxJava to Kotlin Coroutines. I'm using an MVVM clean architecture, meaning that my ViewModels communicate to UseCases classes, and these UseCases classes use one or many Repositories to fetch data from network.
Let me give you an example. Let's say we have a screen that is supposed to show the user profile information. So we have the UserProfileViewModel:
#HiltViewModel
class UserProfileViewModel #Inject constructor(
private val getUserProfileUseCase: GetUserProfileUseCase
) : ViewModel() {
sealed class State {
data SuccessfullyFetchedUser(
user: ExampleUser
) : State()
}
// ...
val state = SingleLiveEvent<UserProfileViewModel.State>()
// ...
fun fetchUserProfile() {
viewModelScope.launch {
// ⚠️ We trigger the use case to fetch the user profile info
getUserProfileUseCase()
.collect {
when (it) {
is GetUserProfileUseCase.Result.UserProfileFetched -> {
state.postValue(State.SuccessfullyFetchedUser(it.user))
}
is GetUserProfileUseCase.Result.ErrorFetchingUserProfile -> {
// ...
}
}
}
}
}
}
The GetUserProfileUseCase use case would look like this:
interface GetUserProfileUseCase {
sealed class Result {
object ErrorFetchingUserProfile : Result()
data class UserProfileFetched(
val user: ExampleUser
) : Result()
}
suspend operator fun invoke(email: String): Flow<Result>
}
class GetUserProfileUseCaseImpl(
private val userRepository: UserRepository
) : GetUserProfileUseCase {
override suspend fun invoke(email: String): Flow<GetUserProfileUseCase.Result> {
// ⚠️ Hit the repository to fetch the info. Notice that if we have more
// complex scenarios, we might require zipping repository calls together, or
// flatmap responses.
return userRepository.getUserProfile().flatMapMerge {
when (it) {
is ResultData.Success -> {
flow { emit(GetUserProfileUseCase.Result.UserProfileFetched(it.data.toUserExampleModel())) }
}
is ResultData.Error -> {
flow { emit(GetUserProfileUseCase.Result.ErrorFetchingUserProfile) }
}
}
}
}
}
The UserRepository repository would look like this:
interface UserRepository {
fun getUserProfile(): Flow<ResultData<ApiUserProfileResponse>>
}
class UserRepositoryImpl(
private val retrofitApi: RetrofitApi
) : UserRepository {
override fun getUserProfile(): Flow<ResultData<ApiUserProfileResponse>> {
return flow {
val response = retrofitApi.getUserProfileFromApi()
if (response.isSuccessful) {
emit(ResultData.Success(response.body()!!))
} else {
emit(ResultData.Error(RetrofitNetworkError(response.code())))
}
}
}
}
And finally, the RetrofitApi and the response class to model the backend API response would look like this:
data class ApiUserProfileResponse(
#SerializedName("user_name") val userName: String
// ...
)
interface RetrofitApi {
#GET("api/user/profile")
suspend fun getUserProfileFromApi(): Response<ApiUserProfileResponse>
}
Everything has been working fine so far, but I've started to run into some issues when implementing more complex features.
For example, there's a use case where I need to (1) post to a POST /send_email_link endpoint when the user first signs in, this endpoint will check if the email that I send in the body already exists, if it doesn't it will return a 404 error code, and (2) if everything goes okay, I'm supposed to hit a POST /peek endpoint that will return some info about the user account.
This is what I've implemented so far for this UserAccountVerificationUseCase:
interface UserAccountVerificationUseCase {
sealed class Result {
object ErrorVerifyingUserEmail : Result()
object ErrorEmailDoesNotExist : Result()
data class UserEmailVerifiedSuccessfully(
val canSignIn: Boolean
) : Result()
}
suspend operator fun invoke(email: String): Flow<Result>
}
class UserAccountVerificationUseCaseImpl(
private val userRepository: UserRepository
) : UserAccountVerificationUseCase {
override suspend fun invoke(email: String): Flow<UserAccountVerificationUseCase.Result> {
return userRepository.postSendEmailLink().flatMapMerge {
when (it) {
is ResultData.Success -> {
userRepository.postPeek().flatMapMerge {
when (it) {
is ResultData.Success -> {
val canSignIn = it.data?.userName == "Something"
flow { emit(UserAccountVerificationUseCase.Result.UserEmailVerifiedSuccessfully(canSignIn)) }
} else {
flow { emit(UserAccountVerificationUseCase.Result.ErrorVerifyingUserEmail) }
}
}
}
}
is ResultData.Error -> {
if (it.exception is RetrofitNetworkError) {
if (it.exception.errorCode == 404) {
flow { emit(UserAccountVerificationUseCase.Result.ErrorEmailDoesNotExist) }
} else {
flow { emit(UserAccountVerificationUseCase.Result.ErrorVerifyingUserEmail) }
}
} else {
flow { emit(UserAccountVerificationUseCase.Result.ErrorVerifyingUserEmail) }
}
}
}
}
}
}
Issue
The above solution is working as expected, if the first API call to the POST /send_email_link ever returns a 404, the use case will behave as expected and return the ErrorEmailDoesNotExist response so the ViewModel can pass that back to the UI and show the expected UX.
The problem as you can see is that this solution requires a ton of boilerplate code, I thought using Kotlin Coroutines would make things simpler than with RxJava, but it hasn't turned out like that yet. I'm quite sure that this is because I'm missing something or I haven't quite learned how to use Flow properly.
What I've tried so far
I've tried to change the way I emit the elements from the repositories, from this:
...
override fun getUserProfile(): Flow<ResultData<ApiUserProfileResponse>> {
return flow {
val response = retrofitApi.getUserProfileFromApi()
if (response.isSuccessful) {
emit(ResultData.Success(response.body()!!))
} else {
emit(ResultData.Error(RetrofitNetworkError(response.code())))
}
}
}
...
To something like this:
...
override fun getUserProfile(): Flow<ResultData<ApiUserProfileResponse>> {
return flow {
val response = retrofitApi.getUserProfileFromApi()
if (response.isSuccessful) {
emit(ResultData.Success(response.body()!!))
} else {
error(RetrofitNetworkError(response.code()))
}
}
}
..
So I can use the catch() function like I'd with RxJava's onErrorResume():
class UserAccountVerificationUseCaseImpl(
private val userRepository: UserRepository
) : UserAccountVerificationUseCase {
override suspend fun invoke(email: String): Flow<UserAccountVerificationUseCase.Result> {
return userRepository.postSendEmailLink()
.catch { e ->
if (e is RetrofitNetworkError) {
if (e.errorCode == 404) {
flow { emit(UserAccountVerificationUseCase.Result.ErrorEmailDoesNotExist) }
} else {
flow { emit(UserAccountVerificationUseCase.Result.ErrorVerifyingUserEmail) }
}
} else {
flow { emit(UserAccountVerificationUseCase.Result.ErrorVerifyingUserEmail) }
}
}
.flatMapMerge {
userRepository.postPeek().flatMapMerge {
when (it) {
is ResultData.Success -> {
val canSignIn = it.data?.userName == "Something"
flow { emit(UserAccountVerificationUseCase.Result.UserEmailVerifiedSuccessfully(canSignIn)) }
} else -> {
flow { emit(UserAccountVerificationUseCase.Result.ErrorVerifyingUserEmail) }
}
}
}
}
}
}
}
This does reduce the boilerplate code a bit, but I haven't been able to get it working because as soon as I try to run the use case like this I start getting errors saying that I shouldn't emit items in the catch().
Even if I could get this working, still, there's way too much boilerplate code here. I though doing things like this with Kotlin Coroutines would mean having much more simple, and readable, use cases. Something like:
...
class UserAccountVerificationUseCaseImpl(
private val userRepository: AuthRepository
) : UserAccountVerificationUseCase {
override suspend fun invoke(email: String): Flow<UserAccountVerificationUseCase.Result> {
return flow {
coroutineScope {
val sendLinksResponse = userRepository.postSendEmailLink()
if (sendLinksResponse is ResultData.Success) {
val peekAccount = userRepository.postPeek()
if (peekAccount is ResultData.Success) {
emit(UserAccountVerificationUseCase.Result.UserEmailVerifiedSuccessfully())
} else {
emit(UserAccountVerificationUseCase.Result.ErrorVerifyingUserEmail)
}
} else {
if (sendLinksResponse is ResultData.Error) {
if (sendLinksResponse.error == 404) {
emit(UserAccountVerificationUseCase.Result.ErrorEmailDoesNotExist)
} else {
emit(UserAccountVerificationUseCase.Result.ErrorVerifyingUserEmail)
}
} else {
emit(UserAccountVerificationUseCase.Result.ErrorVerifyingUserEmail)
}
}
}
}
}
}
...
This is what I had pictured about working with Kotlin Coroutines. Ditching RxJava's zip(), contact(), delayError(), onErrorResume() and all those Observable functions in favor of something more readable.
Question
How can I reduce the amount of boilerplate code and make my use cases look more Coroutine-like?
Notes
I know some people just call the repositories directly from the ViewModel layer, but I like having this UseCase layer in the middle so I can contain all the code related to switching streams and handling errors here.
Any feedback is appreciated! Thanks!
Edit #1
Based on #Joffrey response, I've changed the code so it works like this:
The Retrofit API layer keeps returning suspendable function.
data class ApiUserProfileResponse(
#SerializedName("user_name") val userName: String
// ...
)
interface RetrofitApi {
#GET("api/user/profile")
suspend fun getUserProfileFromApi(): Response<ApiUserProfileResponse>
}
The repository now returns a suspendable function and I've removed the Flow wrapper:
interface UserRepository {
suspend fun getUserProfile(): ResultData<ApiUserProfileResponse>
}
class UserRepositoryImpl(
private val retrofitApi: RetrofitApi
) : UserRepository {
override suspend fun getUserProfile(): ResultData<ApiUserProfileResponse> {
val response = retrofitApi.getUserProfileFromApi()
return if (response.isSuccessful) {
ResultData.Success(response.body()!!)
} else {
ResultData.Error(RetrofitNetworkError(response.code()))
}
}
}
The use case keeps returning a Flow since I might also plug calls to a Room DB here:
interface GetUserProfileUseCase {
sealed class Result {
object ErrorFetchingUserProfile : Result()
data class UserProfileFetched(
val user: ExampleUser
) : Result()
}
suspend operator fun invoke(email: String): Flow<Result>
}
class GetUserProfileUseCaseImpl(
private val userRepository: UserRepository
) : GetUserProfileUseCase {
override suspend fun invoke(email: String): Flow<GetUserProfileUseCase.Result> {
return flow {
val userProfileResponse = userRepository.getUserProfile()
when (userProfileResponse) {
is ResultData.Success -> {
emit(GetUserProfileUseCase.Result.UserProfileFetched(it.toUserModel()))
}
is ResultData.Error -> {
emit(GetUserProfileUseCase.Result.ErrorFetchingUserProfile)
}
}
}
}
}
This looks much more clean. Now, applying the same thing to the UserAccountVerificationUseCase:
interface UserAccountVerificationUseCase {
sealed class Result {
object ErrorVerifyingUserEmail : Result()
object ErrorEmailDoesNotExist : Result()
data class UserEmailVerifiedSuccessfully(
val canSignIn: Boolean
) : Result()
}
suspend operator fun invoke(email: String): Flow<Result>
}
class UserAccountVerificationUseCaseImpl(
private val userRepository: UserRepository
) : UserAccountVerificationUseCase {
override suspend fun invoke(email: String): Flow<UserAccountVerificationUseCase.Result> {
return flow {
val sendEmailLinkResponse = userRepository.postSendEmailLink()
when (sendEmailLinkResponse) {
is ResultData.Success -> {
val peekResponse = userRepository.postPeek()
when (peekResponse) {
is ResultData.Success -> {
val canSignIn = peekResponse.data?.userName == "Something"
emit(UserAccountVerificationUseCase.Result.UserEmailVerifiedSuccessfully(canSignIn)
}
else -> {
emit(UserAccountVerificationUseCase.Result.ErrorVerifyingUserEmail)
}
}
}
is ResultData.Error -> {
if (sendEmailLinkResponse.isNetworkError(404)) {
emit(UserAccountVerificationUseCase.Result.ErrorEmailDoesNotExist)
} else {
emit(UserAccountVerificationUseCase.Result.ErrorVerifyingUserEmail)
}
}
}
}
}
}
This looks much more clean and it works perfectly. I still wonder if there's any more room for improvement here.
The most obvious problem I see here is that you're using Flow for single values instead of suspend functions.
Coroutines makes the single-value use case much simpler by using suspend functions that return plain values or throw exceptions. You can of course also make them return Result-like classes to encapsulate errors instead of actually using exceptions, but the important part is that with suspend functions you are exposing a seemingly synchronous (thus convenient) API while still benefitting from asynchronous runtime.
In the provided examples you're not subscribing for updates anywhere, all flows actually just give a single element and complete, so there is no real reason to use flows and it complicates the code. It also makes it harder to read for people used to coroutines because it looks like multiple values are coming, and potentially collect being infinite, but it's not the case.
Each time you write flow { emit(x) } it should just be x.
Following the above, you're sometimes using flatMapMerge and in the lambda you create flows with a single element. Unless you're looking for parallelization of the computation, you should simply go for .map { ... } instead. So replace this:
val resultingFlow = sourceFlow.flatMapMerge {
if (something) {
flow { emit(x) }
} else {
flow { emit(y) }
}
}
With this:
val resultingFlow = sourceFlow.map { if (something) x else y }
I would like to implement a feature like loadStateFlow in Paging 3.
I do not use pagination in my implementation and it is not necessary in my case.
Could I make it another way?
I have found something like LoadingStateAdapter library
https://developer.android.com/reference/kotlin/androidx/paging/LoadStateAdapter
For now I get a list using method in the fragment:
private fun collectNotificationItems() {
vm.notificationData.collectWith(viewLifecycleOwner) {
notificationAdapter.items = it
}
}
This is implementation I would like to achieve, example is in paging3:
private fun collectItems() {
vm.items.collectWith(viewLifecycleOwner, adapter::submitData)
adapter.loadStateFlow.collectWith(viewLifecycleOwner) { loadState ->
vm.setLoadingState(loadState.refresh is LoadState.Loading)
val isEmpty =
loadState.source.refresh is LoadState.NotLoading && loadState.append.endOfPaginationReached && archiveAdapter.itemCount < 1
vm.setEmptyStateVisible(isEmpty)
}
}
Where methods are:
in ViewModel
fun setLoadingState(isLoading: Boolean) {
_areShimmersVisible.value = isLoading && !_isSwipingToRefresh.value
if (!isLoading) _isSwipingToRefresh.value = false
}
areShimmers and isSwiping are MutableStateFlow
Could you recommend any other options?
EDIT:
I have the whole implementation a little bit different.
I have use case to make it
class GetListItemDetailsUseCase #Inject constructor(private val dao: Dao): BaseFlowUseCase<Unit, List<ItemData>>() {
override fun create(params: Unit): Flow<List<ItemData>> {
return flow{
emit(dao.readAllData())
}
}
}
For now it looks like the code above.
How to use DateState in that case?
EDIT2:
class GetNotificationListItemDetailsUseCase #Inject constructor(private val notificationDao: NotificationDao): BaseFlowUseCase<Unit, DataState<List<NotificationItemsResponse.NotificationItemData>>>() {
override fun create(params: Unit): Flow<DataState<List<NotificationItemsResponse.NotificationItemData>>> {
return flow{
emit(DataState.Loading)
try {
emit(DataState.Success(notificationDao.readAllDataState()))
} catch(e: Exception) {
emit(DataState.Error(e)) // error, and send the exception
}
}
}
}
DAO
#Query("SELECT * FROM notification_list ORDER BY id ASC")
abstract suspend fun readAllDataState(): DataState<List<NotificationItemsResponse.NotificationItemData>>
/\ error beacause of it:
error: Not sure how to convert a Cursor to this method's return type
fragment
private suspend fun collectNotificationItems() {
vm.notificationData.collectLatest { dataState ->
when(dataState) {
is DataState.Error -> {
collectErrorState()
Log.d("collectNotificationItems", "Collect ErrorState")
}
DataState.Loading -> {
Log.d("collectNotificationItems", "Collect Loading")
}
is DataState.Success<*> -> {
vm.notificationData.collectWith(viewLifecycleOwner) {
notificationAdapter.items = it
notificationAdapter.notifyDataSetChanged()
Log.d("collectNotificationItems", "Collect Sucess")
}
}
}
}
You could use a utility class (usually called DataState or something like that).
sealed class DataState<out T> {
data class Success<out T>(val data: T) : DataState<T>()
data class Error(val exception: Exception) : DataState<Nothing>()
object Loading : DataState<Nothing>()
}
Then, you change your flow's return type from Flow<YourObject> to Flow<DataState<YourObject>> and emit the DataStates within a flow {} or channelFlow {} block.
val notificationsFlow: Flow<DataState<YourObject>> get() = flow {
emit(DataState.Loading) // when you collect, you will receive this DataState telling you that it's loading
try {
// networking/database stuff
emit(DataState.Success(yourResultObject))
} catch(e: Exception) {
emit(DataState.Error(e)) // error, and send the exception
}
}
Finally, just change your collect {} to be like:
notificationsFlow.collectLatest { dataState ->
when(dataState) {
is DataState.Error -> { } // error occurred, deal with it here
DataState.Loading -> { } // it's loading, show progress bar or something
is DataState.Success -> { } // data received from the flow, access it with dataState.data
}
}
For more information on this regard, check this out.
My MainRepository is what fetches data from the API and inserts into the database, then displaying on the UI.
override fun fetchAll() {
Observable.fromCallable { local.fetchPosts() }
.doOnNext {
remote.fetchPosts().concatMap { posts ->
local.insert(*posts.toTypedArray())
Observable.just(posts)
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ outcome.success(it) },
{ error: Throwable -> outcome.failed(error) }
).addTo(compositeDisposable)
}
The outcome variable is a PublishObject of type Response, that is Loading, Success, or Failure.
override val outcome = PublishSubject.create<Response<List<Post>>>()
[...]
sealed class Response<T> {
companion object {
fun <T> loading(loading: Boolean): Response<T> = Progress(loading)
fun <T> success(data: T): Response<T> = Success(data)
fun <T> failure(e: Throwable): Response<T> = Failure(e)
}
data class Progress<T>(var loading: Boolean) : Response<T>()
data class Success<T>(var data: T) : Response<T>()
data class Failure<T>(var e: Throwable) : Response<T>()
}
It executes a method local.fetchPosts(), which is a function responsible to access DAO functions.
fun fetchPosts() = database.postDao().fetchAll()
[...]
#Query("SELECT * FROM posts ORDER BY createdAt DESC")
fun fetchAll(): List<Post>
The addTo is an extension of Disposable:
fun Disposable.addTo(compositeDisposable: CompositeDisposable) {
compositeDisposable.add(this)
}
I've tried using concatMap right after the Observable.fromCallable, but it will display data from the API directly while doOnNext will show from the database but it will not update the list, removing what has been removed from the remote server.
First you have to understand that doOnNext is a Side Effect Operator, along with it's family, side effect operators only anticipate the emiisions for doing a minor action (like logging for example), they don't affect the stream in anyway.
So, with that you're doing the observable
remote.fetchPosts().concatMap { posts ->
local.insert(*posts.toTypedArray())
Observable.just(posts)
}
Never gets to work, because it's not being subscribed to.
Now you have to make a decision depending on the behavior you want, I'll assume that your use case is:
Try to get data from API.
if successful cache it, if not query DAO
Display it
then something like this will work:
remote.fetchPosts()
// cache the data from remote.
.doOnNext(posts -> local.insert(*posts.toTypedArray()))
// if an error happens, use the posts in the DAO.
.onErrorResumeNext { Observable.fromCallable { local.fetchPosts() } }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
[...]
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