in my android repository layer I am holding a reference to the Android RxBleConnection. I also have a read write function that return an observable, my problem is that this function is being called but not it is not responding because the connection observable has stopped emitting, you can check the code bellow, any suggestions ?
class LampRepository(private val lampBleDevice: Observable<RxBleDevice>,
private val bluetoothExecutor: Executor) : IRepository {
companion object {
private val TAG = "LampRepository"
}
val lampConnectionState:Observable<RxBleConnection.RxBleConnectionState>
val lampBleConnection:Observable<RxBleConnection>
init {
lampBleConnection = lampBleDevice.subscribeOn(Schedulers.from(bluetoothExecutor))
.observeOn(AndroidSchedulers.mainThread())
.flatMap {
Log.d(TAG,"Connecting to the lamp GATT server")
it.establishConnection(true) }
.compose(ReplayingShare.instance())
lampConnectionState = lampBleDevice.subscribeOn(Schedulers.from(bluetoothExecutor))
.observeOn(AndroidSchedulers.mainThread())
.flatMap {
Log.d(TAG,"Observing the Lamp GATT server connection state")
it.observeConnectionStateChanges()}
.share()
}
fun getLampLuminosityLevel()
= lampBleConnection.subscribeOn(Schedulers.from(bluetoothExecutor))
.observeOn(AndroidSchedulers.mainThread())
.flatMap {
Log.d(TAG,"Reading the lamp luminosity characteristic")
it.readCharacteristic(UUID.fromString(LampProfile.STATE_CHARACTERISTIC_UUID))
?.toObservable()}
.flatMap {
Observable.just(it[0].toInt()) }
.flatMap { Observable.just(0) }
.flatMap {
when (it) {
0 -> Observable.just(LampProfile.Luminosity.NON)
1 -> Observable.just(LampProfile.Luminosity.LOW)
2 -> Observable.just(LampProfile.Luminosity.MEDIUM)
3 -> Observable.just(LampProfile.Luminosity.HIGH)
4 -> Observable.just(LampProfile.Luminosity.MAX)
else -> Observable.error(Throwable("unknown value ${it}"))
}}
fun getLampPowerState()
= lampBleConnection.subscribeOn(Schedulers.from(bluetoothExecutor))
.observeOn(AndroidSchedulers.mainThread())
.flatMap {
Log.d(TAG,"Reading the lamp power state characteristic")
it.readCharacteristic(UUID.fromString(LampProfile.STATE_CHARACTERISTIC_UUID))
.toObservable()}
.flatMap {
Observable.just(it[0].toInt()) }
.flatMap {
when (it) {
0 -> Observable.just(LampProfile.State.OFF)
1 -> Observable.just(LampProfile.State.ON)
else -> Observable.error(Throwable("unknown value ${it}"))
}}
fun setLampPowerState(state: LampProfile.State)
= lampBleConnection.subscribeOn(Schedulers.from(bluetoothExecutor))
.observeOn(AndroidSchedulers.mainThread())
.flatMap {
Log.d(TAG,"writing to the Characteristic")
it.writeCharacteristic(UUID.fromString(LampProfile.STATE_CHARACTERISTIC_UUID), byteArrayOf(state.value.toByte()))
.toObservable()}
.flatMap {
Observable.just(it[0].toInt()) }
.flatMap { Observable.just(1) }
.flatMap {
Log.d(TAG,"Finish writing")
when (it) {
0 -> Observable.just(LampProfile.State.OFF)
1 -> Observable.just(LampProfile.State.ON)
else -> Observable.error(Throwable("unknown value")) }}
fun setLampLuminosityLevel(level: LampProfile.Luminosity)
=lampBleConnection.subscribeOn(Schedulers.from(bluetoothExecutor))
.flatMap {
Log.d(TAG,"Writing the lamp luminosity characteristic")
it.writeCharacteristic(UUID.fromString(LampProfile.LUMINOSITY_CHARACTERISTIC_UUID), byteArrayOf(level.value.toByte()))
.toObservable()
}
.flatMap {
Observable.just(it[0].toInt())
}
.flatMap { Observable.just(0) }
.flatMap {
when (it) {
0 -> Observable.just(LampProfile.Luminosity.NON)
1 -> Observable.just(LampProfile.Luminosity.LOW)
2 -> Observable.just(LampProfile.Luminosity.MEDIUM)
3 -> Observable.just(LampProfile.Luminosity.HIGH)
4 -> Observable.just(LampProfile.Luminosity.MAX)
else -> Observable.error(Throwable("unknown value"))
}
}
}
class LampViewModel(private val lampRepository:LampRepository):ViewModel(){
companion object {
val TAG = "LampViewModel"
}
private val mLampPowerState:MutableLiveData<LampProfile.State> = MutableLiveData()
private val mLampLuminosityLevel:MutableLiveData<LampProfile.Luminosity> = MutableLiveData()
private val mLampBleConnectionState:MutableLiveData<RxBleConnection.RxBleConnectionState> = MutableLiveData()
private val compositeDisposable = CompositeDisposable()
init {
compositeDisposable.add(lampRepository.lampConnectionState.subscribe(
mLampBleConnectionState::postValue,{
Log.d(TAG,"error is ${it.message}")
}))
compositeDisposable.add(lampRepository.lampBleConnection.subscribe({
},{
}))
}
fun getLampLuminosityLevel():LiveData<LampProfile.Luminosity> = mLampLuminosityLevel
fun getLampPowerState():LiveData<LampProfile.State> = mLampPowerState
fun getLampConnectionState():LiveData<RxBleConnection.RxBleConnectionState> = mLampBleConnectionState
fun setLampPowerState(state: LampProfile.State) {
Log.d(TAG,"Into the function")
compositeDisposable.add(lampRepository.setLampPowerState(state)
.subscribe({
Log.d(TAG,"writing with success $it")
},{
Log.d(TAG,"error while writing ${it.message}")
}))
}
fun setLampLuminosityLevel(level: LampProfile.Luminosity) {
compositeDisposable.add(lampRepository.setLampLuminosityLevel(level)
.subscribe({
},{
Log.d(TAG,"writing error")
}))
}
class DefaultServiceLocator (private val activity: FragmentActivity): ServiceLocator {
companion object {
private val TAG = "DefaultServiceLocator"
}
private var blueToothClient = RxBleClient.create(activity)
private var rxPermissions = RxPermissions(activity)
private val BLUETOOTH_IO = Executors.newFixedThreadPool(2)
private val NETWORK_IO = Executors.newFixedThreadPool(1)
private val bluetoothScan:Observable<ScanResult>
private val bluetoothClientState:Observable<RxBleClient.State>
private val lampBleDevice: Observable<RxBleDevice>
private val broadLinkBleDevice: Observable<RxBleDevice>
private val broadLinkRepository:BroadLinkRepository
private val mConfig = AIConfiguration(
"e87e26ceb2ae4519ace2f3c71abd076e",
ai.api.AIConfiguration.SupportedLanguages.English,
AIConfiguration.RecognitionEngine.System
)
private val AIDataService = AIDataService(mConfig)
init {
bluetoothClientState = blueToothClient.observeStateChanges()
.observeOn(Schedulers.from(getBlueToothExecutor()))
.subscribeOn(AndroidSchedulers.mainThread())
.startWith(Observable.just(blueToothClient.state))
.share()
bluetoothScan = bluetoothClientState.filter{it == RxBleClient.State.READY}
.delay(1000,TimeUnit.MILLISECONDS)
.flatMap {
blueToothClient.scanBleDevices(
ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
.build(),
ScanFilter.Builder().build())}
.share()
broadLinkBleDevice = bluetoothScan.filter { it.bleDevice.macAddress == BroadLinkProfile.DEVICE_MAC_ADDRESS}
.flatMap { result ->
Log.d(TAG, "the device named ${result.bleDevice.name} is found")
Observable.just(result.bleDevice) }
.retry()
.share()
lampBleDevice = bluetoothScan.filter { it.bleDevice.macAddress == LampProfile.DEVICE_MAC_ADDRESS }
.flatMap { result ->
Log.d(TAG, "the device named ${result.bleDevice.name} is found")
Observable.just(result.bleDevice) }
.retry()
.share()
broadLinkRepository = BroadLinkRepository(broadLinkBleDevice,getBlueToothExecutor())
}
override fun getBlueToothExecutor() = BLUETOOTH_IO
override fun getNetworkExecutor() = NETWORK_IO
override fun getBleDevice(key: BleDevices): Observable<RxBleDevice> {
return when (key) {
BleDevices.LAMP -> lampBleDevice
BleDevices.BROAD_LINK -> broadLinkBleDevice
}
}
override fun getRepository(key: Repositories): IRepository {
return when (key) {
Repositories.LAMP_REPOSITORY -> LampRepository(
lampBleDevice = getBleDevice(BleDevices.LAMP),
bluetoothExecutor = getBlueToothExecutor()
)
Repositories.TV_REPOSITORY -> TvRepository(
broadLinkRepository = broadLinkRepository,
bluetoothExecutor = getBlueToothExecutor()
)
Repositories.AIR_CONDITIONER_REPOSITORY -> AirConditionerRepository(
broadLinkRepository = broadLinkRepository,
bluetoothExecutor = getBlueToothExecutor()
)
Repositories.DIALOG_REPOSITORY -> DialogRepository(
AIService = AIDataService,
networkExecutor = getNetworkExecutor()
)
}
}
}
The lampBleConnection will emit only once per connection since you are using both ReplayingShare and .share().
The ReplayingShare shares the upstream Observable subscription and emit's the last event to new subscribers. Using .share() does partially the same thing as it also shares the upstream Observable subscription rendering the ReplayingShare useless as it will be subscribed only once.
Bottom line — remove .share() from the lampBleConnection and from setLampPowerState()
The (original) below code with removed .share() should do the trick:
class LampRepository(private val lampBleDevice: Observable<RxBleDevice>,
private val bluetoothExecutor: Executor) : IRepository {
companion object {
private val TAG = "LampRepository"
}
val lampConnectionState:Observable<RxBleConnection.RxBleConnectionState>
val lampBleConnection:Observable<RxBleConnection>
init {
lampBleConnection = lampBleDevice.subscribeOn(Schedulers.from(bluetoothExecutor))
.observeOn(AndroidSchedulers.mainThread())
.flatMap {
Log.d(TAG,"Connecting to the lamp GATT server")
it.establishConnection(true) }
.compose(ReplayingShare.instance())
lampConnectionState = lampBleDevice.subscribeOn(Schedulers.from(bluetoothExecutor))
.observeOn(AndroidSchedulers.mainThread())
.flatMap {
Log.d(TAG,"Observing the Lamp GATT server connection state")
it.observeConnectionStateChanges()}
.share()
}
fun getLampLuminosityLevel() = lampBleConnection.subscribeOn(Schedulers.from(bluetoothExecutor))
.observeOn(AndroidSchedulers.mainThread())
.flatMap {
Log.d(TAG,"Reading the lamp luminosity characteristic")
it.readCharacteristic(UUID.fromString(LampProfile.STATE_CHARACTERISTIC_UUID))
?.toObservable()
}
.flatMap { Observable.just(it[0].toInt()) }
.flatMap { Observable.just(0) }
.flatMap {
when (it) {
0 -> Observable.just(LampProfile.Luminosity.NON)
1 -> Observable.just(LampProfile.Luminosity.LOW)
2 -> Observable.just(LampProfile.Luminosity.MEDIUM)
3 -> Observable.just(LampProfile.Luminosity.HIGH)
4 -> Observable.just(LampProfile.Luminosity.MAX)
else -> Observable.error(Throwable("unknown value ${it}"))
}
}
fun getLampPowerState() = lampBleConnection.subscribeOn(Schedulers.from(bluetoothExecutor))
.observeOn(AndroidSchedulers.mainThread())
.flatMap {
Log.d(TAG,"Reading the lamp power state characteristic")
it.readCharacteristic(UUID.fromString(LampProfile.STATE_CHARACTERISTIC_UUID))
.toObservable()
}
.flatMap { Observable.just(it[0].toInt()) }
.flatMap { Observable.just(0) }
.flatMap {
when (it) {
0 -> Observable.just(LampProfile.State.OFF)
1 -> Observable.just(LampProfile.State.ON)
else -> Observable.error(Throwable("unknown value ${it}"))
}
}
fun setLampPowerState(state: LampProfile.State) = lampBleConnection.subscribeOn(Schedulers.from(bluetoothExecutor))
.observeOn(AndroidSchedulers.mainThread())
.flatMap {
Log.d(TAG,"writing to the Characteristic")
it.writeCharacteristic(UUID.fromString(LampProfile.STATE_CHARACTERISTIC_UUID), byteArrayOf(state.value.toByte()))
?.toObservable()
}
.flatMap { Observable.just(it[0].toInt()) }
.flatMap {
Log.d(TAG,"Finish writing")
when (it) {
0 -> Observable.just(LampProfile.State.OFF)
1 -> Observable.just(LampProfile.State.ON)
else -> Observable.error(Throwable("unknown value"))
}
}
fun setLampLuminosityLevel(level: LampProfile.Luminosity) = lampBleConnection.subscribeOn(Schedulers.from(bluetoothExecutor))
.flatMap {
Log.d(TAG,"Writing the lamp luminosity characteristic")
it.writeCharacteristic(UUID.fromString(LampProfile.LUMINOSITY_CHARACTERISTIC_UUID), byteArrayOf(level.value.toByte()))
.toObservable()
}
.flatMap { Observable.just(it[0].toInt()) }
.flatMap { Observable.just(0) }
.flatMap {
when (it) {
0 -> Observable.just(LampProfile.Luminosity.NON)
1 -> Observable.just(LampProfile.Luminosity.LOW)
2 -> Observable.just(LampProfile.Luminosity.MEDIUM)
3 -> Observable.just(LampProfile.Luminosity.HIGH)
4 -> Observable.just(LampProfile.Luminosity.MAX)
else -> Observable.error(Throwable("unknown value"))
}
}
}
Related
I want to call 2 retrofit services in parallel and then do an action only when both of them finished, but I don't seem to figuer it out how.
I have a viewModel where I have defined my services:
var config= List<Configuration>
fun getClientProducts() {
getClientClientConfigUseCase
.build(this)
.executeWithError({ config ->
config = config
}, {
})
}
var therapies = List<DtoTherapy>
fun getTherapies() {
getTherapiesUseCase
.build(this)
.executeWithError({ config ->
therapies = it
}, {
})
}
And then I want to call both services in parallel in my fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupUi(view)
loadUserData()
viewModel.getClientProducts()
viewModel.getTherapies()
}
And when both variables config and therapies have the value do an action. But as I said maybe one service take 1 sec to respond and another 4 secs, and I want only to perfom an action when both have finished. Any help with be appreciated.
Here is the class I use to build the use case call:
abstract class SingleUseCase<T> : UseCase() {
private lateinit var single: Single<T>
private lateinit var useCaseInterface: UseCaseInterface
private var withLoader: Boolean = false
private var withErrorMessage: Boolean = false
internal abstract fun buildUseCaseSingle(): Single<T>
fun build(useCaseInterface: UseCaseInterface): SingleUseCase<T> {
this.withLoader = false
this.withErrorMessage = false
this.useCaseInterface = useCaseInterface
this.single = buildUseCaseSingle()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doAfterSuccess { useCaseInterface.onSuccess(it) }
return this
}
fun withLoader(): SingleUseCase<T> {
this.withLoader = true
return this
}
fun withErrorMessage(): SingleUseCase<T> {
this.withErrorMessage = true
return this
}
fun single(): Single<T> {
return this.single
}
fun execute(onSuccess: ((t: T) -> Unit)) {
useCaseInterface.onPrepareRequest(withLoader)
buildObservable(onSuccess)
}
private fun buildObservable(onSuccess: ((t: T) -> Unit)) {
disposeLast()
lastDisposable = single
.doFinally { useCaseInterface.onFinishRequest(this.withLoader) }
.subscribe(
{ onSuccess(it) },
{
useCaseInterface.onError(mapError(it), withErrorMessage)
})
lastDisposable?.let {
compositeDisposable.add(it)
}
}
fun executeWithError(onSuccess: ((success: T) -> Unit), onError: ((error: ApiError ) -> Unit)) {
useCaseInterface.onPrepareRequest(withLoader)
buildObservable(onSuccess, onError)
}
private fun buildObservable(onSuccess: ((success: T) -> Unit), onError: ((error: ApiError ) -> Unit)) {
disposeLast()
lastDisposable = single
.doFinally { useCaseInterface.onFinishRequest(this.withLoader) }
.subscribe(
{ onSuccess(it) },
{
onError(mapError(it))
useCaseInterface.onError(mapError(it), withErrorMessage)
})
lastDisposable?.let {
compositeDisposable.add(it)
}
}
private fun mapError(t: Throwable): ApiError {
return if(t is HttpException) {
val apiError = t.response()?.errorBody()?.string()
try {
ApiError (t.code(), t.response()?.errorBody()?.string(), Gson().fromJson(apiError, GenericError::class.java))
} catch(e: Exception) {
ApiError (-2, "Unkown error")
}
} else ApiError (-1, "Unkown error")
}
}
And this is a specific usecase class:
class GetClientConfigUseCase #Inject constructor(private val repository: UserRepository) :
SingleUseCase<ClientConfigResponse>() {
override fun buildUseCaseSingle(): Single<ClientConfigResponse> {
return repository.getUserConfig()
}
}
I guess you need zip operation. With zip operation you can have a result of two observable in one place when both of them received data.
Observable<List<ClientProducts>> observable1 = ...
Observable<List<DtoTherapy>> observable2 = ...
Observable.zip(observable1, observable2, new BiFunction<List<ClientProducts>, List<DtoTherapy>, Result>() {
#Override
public Result apply(List<ClientProducts> products, List<DtoTherapy> therapies) throws Exception
{
// here you have both of your data
// do operations on products and therapies
// then return the result
return result;
}
});
i want to update RecyclerView when item removed but i dont' have idea to solve, i try to use notifydatasetchanged() but it don't work.
API Interface
interface RetrofitService {
#GET("/")
suspend fun getAllItem() : Response<List<Item>>
#POST("/delete")
suspend fun deleteItem(#Body item :Item) : Response<Item>
class Repository
class ItemRepository(private val retrofitService: RetrofitService) {
suspend fun getAllItem() = retrofitService.getAllItem()
suspend fun deleteItem(item : Item) = retrofitService.deleteItem(item)
}
in ViewModel, i handle result by two function(handleItemResponse and handleListItemResponse)
class ItemViewModel(private val itemRepository: ItemRepository) : ViewModel() {
val itemResponse: MutableLiveData<Resource<Item>> = MutableLiveData()
val listItemResponse : MutableLiveData<Resource<List<Item>>> = MutableLiveData()
fun getItemResponse() = viewModelScope.launch {
val response : Response<List<Item>> = itemRepository.getAllItem()
withContext(Dispatchers.Main) {
listItemResponse.postValue(handleListItemResponse(response))
}
}
fun deleteItem(item: Item) = viewModelScope.launch {
val response = itemRepository.deleteItem(item)
itemResponse.postValue(handleItemResponse(response))
}
private fun handleItemResponse(response: Response<Item>): Resource<Item> {
if (response.isSuccessful) {
response.body()?.let { resultResponse ->
return Resource.Success(resultResponse)
}
}
return Resource.Error(response.message())
}
private fun handleListItemResponse(response: Response<List<Item>>): Resource<List<Item>> {
if (response.isSuccessful) {
response.body()?.let { resultResponse ->
return Resource.Success(resultResponse)
}
}
return Resource.Error(response.message())
}
}
and class Resource to logging result
sealed class Resource<T>(
val data:T?=null,
val messaage : String? = null
) {
class Success<T>(data:T) : Resource<T>(data)
class Error<T>(messaage: String,data:T? = null) : Resource<T>(data,messaage)
class Loading<T> : Resource<T>()
}
in Fragment , i use ViewModel like this
viewModel.listItemResponse.observe(viewLifecycleOwner, Observer {
when (it) {
is Resource.Success -> {
Toast.makeText(context,"All Success",Toast.LENGTH_SHORT).show()
it.data?.let { itemReponse ->
listItem = itemReponse
adapter.setNotes(listItem)
}
}
is Resource.Error -> {
it.messaage?.let { msg ->
Log.e("AAA","ERR:$msg ")
}
}
}
})
viewModel.getItemResponse()
}
in Adapter , i getList by function setNotes
fun setNotes(items:List<Item>){
this.items = items
notifyDataSetChanged()
}
and function to delete item
private val onItemDelete:(Item)->Unit ={ item ->
viewModel.itemResponse.observe(viewLifecycleOwner, Observer {
when (it) {
is Resource.Success -> {
it.data?.let {
Toast.makeText(context,"Delete successfully!",Toast.LENGTH_SHORT).show()
}
}
is Resource.Error -> {
it.messaage?.let { msg ->
Toast.makeText(context," \"ERR:$msg \"",Toast.LENGTH_SHORT).show()
}
}
}
})
viewModel.deleteItem(item)
}
My english not well , so i hope you sympathize and help me, have a nice day,everyone!
I fixed it, i put it for anyone face same my problem.
private val onItemDelete:(Int)->Unit ={ pos ->
viewModel.itemResponse.observe(viewLifecycleOwner, Observer {
when (it) {
is Resource.Success -> {
it.data?.let {
listItem.removeAt(pos)
adapter.notifyItemRemoved(pos)
}
}
is Resource.Error -> {
it.messaage?.let { msg ->
Toast.makeText(context," \"ERR:$msg \"",Toast.LENGTH_SHORT).show()
}
}
}
})
viewModel.deleteItem(listItem[pos])
}
just callback position of item then get it in fragment.
I already did all the competent of the paging library tried to send all data to the UI following this artricle but no luck on getting the data here is my code
When I debugged I found the following:
when it's loading the network state and pagedUserList get notified for the first time and when the request DONE only the network state get notified and the list doesn't get notified with the list itself and I don't know why? any clue?
MainActivity
private fun initAdapter() {
newsListAdapter = NewsListAdapter { mainViewModel.retry() }
binder.rvUsers.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
binder.rvUsers.adapter = newsListAdapter
mainViewModel.getPagedList().observe(this, Observer {
newsListAdapter.submitList(it)
})
}
private fun initState() {
binder.txtError.setOnClickListener { mainViewModel.retry() }
mainViewModel.getState().observe(this, Observer { state ->
binder.progressBar.visibility =
if (mainViewModel.listIsEmpty() && state == State.LOADING) View.VISIBLE else View.GONE
binder.txtError.visibility =
if (mainViewModel.listIsEmpty() && state == State.ERROR) View.VISIBLE else View.GONE
if (!mainViewModel.listIsEmpty()) {
newsListAdapter.setState(state ?: State.DONE)
}
})
}
ViewModel
private var pagedUserList: LiveData<PagedList<User>>
private val compositeDisposable = CompositeDisposable()
private val pageSize = 10
private val usersDataSourceFactory: UsersDataSourceFactory
init {
usersDataSourceFactory = UsersDataSourceFactory(usersService, compositeDisposable)
val config = PagedList.Config.Builder()
.setPageSize(pageSize)
.setInitialLoadSizeHint(pageSize * 3)
.setEnablePlaceholders(false)
.build()
pagedUserList = LivePagedListBuilder<Int, User>(usersDataSourceFactory, config)
.build();
}
fun getState(): LiveData<State> = Transformations.switchMap<UsersDataSource,
State>(usersDataSourceFactory.getUserLiveData(), UsersDataSource::state)
fun retry() {
usersDataSourceFactory.getUserLiveData().value?.retry()
}
fun listIsEmpty(): Boolean {
return pagedUserList.value?.isEmpty() ?: true
}
fun getPagedList(): LiveData<PagedList<User>> {
return pagedUserList
}
DataSourceFactory
class UsersDataSourceFactory(
private val usersService: UsersService,
private val compositeDisposable: CompositeDisposable
) : DataSource.Factory<Int, User>() {
private val usersDataSourceLiveData = MutableLiveData<UsersDataSource>()
override fun create(): DataSource<Int, User> {
val usersDataSource = UsersDataSource(usersService, compositeDisposable)
usersDataSourceLiveData.postValue(usersDataSource)
return usersDataSource
}
fun getUserLiveData() = usersDataSourceLiveData
}
DataSource
class UsersDataSource(
private val usersService: UsersService,
private val compositeDisposable: CompositeDisposable
) : PageKeyedDataSource<Int, User>() {
var state: MutableLiveData<State> = MutableLiveData()
private var retryCompletable: Completable? = null
override fun loadInitial(
params: LoadInitialParams<Int>,
callback: LoadInitialCallback<Int, User>
) {
updateState(State.LOADING)
compositeDisposable.add(
usersService.getUsers(0, 20)
.subscribe({ response: List<User> ->
updateState(State.DONE)
val nextPageKey = response[response.size - 1].id
callback.onResult(
response, null, nextPageKey
)
}, { t: Throwable? ->
updateState(State.ERROR)
setRetry(Action { loadInitial(params, callback) })
})
)
}
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, User>) {
updateState(State.LOADING)
compositeDisposable.add(
usersService.getUsers(params.key + 1, 20)
.subscribe({ response: List<User> ->
updateState(State.DONE)
if (!response.isEmpty()) {
val nextPageKey = response[response.size - 1].id
callback.onResult(response, nextPageKey)
}else {
updateState(State.ERROR)
setRetry(Action { loadAfter(params, callback) })
}
}, { t: Throwable? ->
updateState(State.ERROR)
setRetry(Action { loadAfter(params, callback) })
})
)
}
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, User>) {
}
private fun updateState(state: State) {
this.state.postValue(state)
}
fun retry() {
if (retryCompletable != null) {
compositeDisposable.add(
retryCompletable!!
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe()
)
}
}
private fun setRetry(action: Action?) {
retryCompletable = if (action == null) null else Completable.fromAction(action)
}
}
enum class State {
DONE, LOADING, ERROR
}
I need to execute something in my view after all of the asynchronous fetches has been done , for this I have a home framgnet that fetches 3 different sections in my backend
private fun populateSectionA(){
viewModel.fetchSectionA.observe(viewLifecycleOwner, Observer {
when(it){
is Resource.Loading -> { //Handling loading }
is Resource.Success -> {
//Handles data
}
is Resource.Failure -> { //Error handling }
}
})
}
private fun populateSectionB(){
viewModel.fetchSectionB.observe(viewLifecycleOwner, Observer {
when(it){
is Resource.Loading -> { //Handling loading }
is Resource.Success -> {
//Handles data
}
is Resource.Failure -> { //Error handling }
}
})
}
private fun populateSectionC(){
viewModel.fetchSectionC.observe(viewLifecycleOwner, Observer {
when(it){
is Resource.Loading -> { //Handling loading }
is Resource.Success -> {
//Handles data
}
is Resource.Failure -> { //Error handling }
}
})
}
Here are my observers in my view, what I need to know is when all of these 3 has finished to load my UI
From my viewmodel the fetch is the same for the three sections
val fetchSectionA = liveData(viewModelScope.coroutineContext + Dispatchers.IO) {
emit(Resource.Loading())
try {
emit(repo.fetchSectionA(sectionId))
} catch (e: Exception) {
emit(Resource.Failure(e))
}
}
val fetchSectionB = liveData(viewModelScope.coroutineContext + Dispatchers.IO) {
emit(Resource.Loading())
try {
emit(repo.fetchSectionB(sectionId))
} catch (e: Exception) {
emit(Resource.Failure(e))
}
}
val fetchSectionC = liveData(viewModelScope.coroutineContext + Dispatchers.IO) {
emit(Resource.Loading())
try {
emit(repo.fetchSectionC(sectionId))
} catch (e: Exception) {
emit(Resource.Failure(e))
}
}
Now, how can I know when all of these individual fetches has been done ? I dont want to use a counter in my UI that counts to certain number untill its reached, instead I would love to give a callback to my UI when all these operations finishes
If you have the following code:
fun <T1, T2, T3> combineTuple(f1: LiveData<T1>, f2: LiveData<T2>, f3: LiveData<T3>): LiveData<Triple<T1?, T2?, T3?>> = MediatorLiveData<Triple<T1?, T2?, T3?>>().also { mediator ->
mediator.value = Triple(f1.value, f2.value, f3.value)
mediator.addSource(f1) { t1: T1? ->
val (_, t2, t3) = mediator.value!!
mediator.value = Triple(t1, t2, t3)
}
mediator.addSource(f2) { t2: T2? ->
val (t1, _, t3) = mediator.value!!
mediator.value = Triple(t1, t2, t3)
}
mediator.addSource(f3) { t3: T3? ->
val (t1, t2, _) = mediator.value!!
mediator.value = Triple(t1, t2, t3)
}
}
Then you can do:
combineTuple(fetchSectionA, fetchSectionB, fetchSectionC)
.map { (sectionA, sectionB, sectionC) ->
val sectionA = sectionA.takeIf { it != Resource.Loading } ?: return#map null
val sectionB = sectionB.takeIf { it != Resource.Loading } ?: return#map null
val sectionC = sectionC.takeIf { it != Resource.Loading } ?: return#map null
return Triple(sectionA, sectionB, sectionC)
}
If you need more or less arity combiners for LiveData, check https://github.com/Zhuinden/livedata-combinetuple-kt
I'm trying to use RxJava to solve this problem. I have 3 calls that need to be executed after each other, using the result of the last call for the next call.
For sake of simplicity I've hosted 3 files on my server that are the 3 calls:
http://jimclermonts.nl/booky/step1user
http://jimclermonts.nl/booky/step2cookie
http://jimclermonts.nl/booky/step3token
What is the correct and cleanest way to do this?
build.gradle:
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:converter-moshi:2.3.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0'
implementation 'com.squareup.okhttp3:okhttp:3.9.0'
// reactive extensions
implementation "com.squareup.retrofit2:adapter-rxjava2:2.3.0"
implementation 'io.reactivex.rxjava2:rxjava:2.1.7'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
MainActivity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val result = SomeWebService().getToken()
if (result != null) {
result.doOnComplete {
//how to get the token value?
}
}
}
SomeWebService
class SomeWebService {
lateinit var apiService: ApiService
var tokenResult: Observable<String>? = null
fun getToken() : Observable<String>? {
if (tokenResult == null) {
apiService = ApiService.retrofit.create(ApiService::class.java)
val body = step1ApiUserResponse()
val cookie = step2CookieResponse(body.blockingSingle())
val tokenResult = step3TokenResponse(cookie.blockingSingle())
this.tokenResult = tokenResult
tokenResult.doOnComplete { }
} else {
tokenResult!!.doOnComplete { }
}
return tokenResult
}
fun step1ApiUserResponse(): Observable<String> {
return Observable.create {
apiService.getStep1User()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess { user ->
val body = user.getResponse()
if (body != null) {
it.onNext(body)
}
}
.doOnError {
it.printStackTrace()
}
}
}
fun step2CookieResponse(body: String): Observable<String> {
return Observable.create {
apiService.getStep2Cookie(body)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess { result ->
val bodyResult = result.body().toString()
it.onNext(bodyResult)
}
}
}
fun step3TokenResponse(cookie: String): Observable<String> {
return Observable.create {
apiService.getStep3Token(cookie)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess { result ->
val body = result.getToken()
if (body != null) {
it.onNext(body)
}
}
}
}
}
ApiService:
interface ApiService {
#GET("/booky/step1user")
fun getStep1User(): Single<UserResponse>
#GET("/booky/step2cookie")
fun getStep2Cookie(body: String): Single<Response>
#GET("/booky/step3token")
fun getStep3Token(#Header("Cookie") sessionId: String): Single<TokenResponse>
companion object {
val interceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
val client = OkHttpClient.Builder()
.addInterceptor(interceptor)
.build()
val retrofit = Retrofit.Builder()
.baseUrl("http://jimclermonts.nl")
.addConverterFactory(MoshiConverterFactory.create().asLenient())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(client)
.build()
}
}
TokenResponse:
class TokenResponse {
#Json(name = "Token")
private var token: String? = null
fun getToken(): String? {
return token
}
fun setToken(token: String) {
this.token = token
}
}
UserResponse:
class UserResponse {
#Json(name = "Response")
private var response: String? = null
fun getResponse(): String? {
return response
}
fun setResponse(response: String) {
this.response = response
}
}
MainActivity:
val service = SomeWebService()
service.getToken()
.subscribe(
{ token ->
Log.d("TOKEN", token)
},
{e ->
Log.e("Token error", e.localizedMessage)
}
)
SomeWebService:
class SomeWebService {
lateinit var apiService: ApiService
var tokenResult: Observable<String>? = null
fun getToken(): Observable<String> {
apiService = ApiService.retrofit.create(ApiService::class.java)
return step1ApiUserResponse()
.flatMap { body ->
step2CookieResponse(body)
.flatMap { cookie ->
step3TokenResponse(cookie)
}
}
}
fun step1ApiUserResponse(): Observable<String?> {
return apiService.getStep1User()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map { user ->
Log.d("Step1", user.toString())
user.getResponse()
}
.toObservable()
}
fun step2CookieResponse(body: String): Observable<String?> {
return apiService.getStep2Cookie(body)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map { result ->
result.getCookie()
}
.toObservable()
}
fun step3TokenResponse(cookie: String): Observable<String?> {
return apiService.getStep3Token(cookie)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map { result ->
result.getToken()
}
.toObservable()
}
}