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
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.
Hello I tried to make Coroutines Flow (using callbackFlow) and tried to convert it to live data, but it seems it's not updating.
You can see my code below:
#ExperimentalCoroutinesApi
suspend fun checkInDanger(): Flow<NetworkStatus<List<UserFire>>> = callbackFlow {
val check = fs.collection(UserFire.COLLECTION).whereEqualTo(UserFire.DANGER, true)
.addSnapshotListener { value, e ->
if (e != null) {
trySend(NetworkStatus.Failed("Error occurred\n${e.code}"))
return#addSnapshotListener
}
if (value == null || value.isEmpty) trySend(NetworkStatus.Empty)
else {
val users = value.map { it.toObject(UserFire::class.java) }
trySend(NetworkStatus.Success(users))
}
}
awaitClose { }
}.flowOn(Dispatchers.IO)
On my repositories:
#ExperimentalCoroutinesApi
override suspend fun checkInDanger(): Flow<Status<List<User>>> = flow {
when (val result = network.checkInDanger().first()) {
is NetworkStatus.Success -> emit(Status.Success(result.data.map {
MapVal.userFireToDom(it)
}))
is NetworkStatus.Empty -> emit(Status.Success(listOf<User>()))
is NetworkStatus.Failed -> emit(Status.Error(null, result.error))
}
}
In my ViewModel:
val checkInDanger = liveData(Dispatchers.IO) {
try {
useCase.checkInDanger().collectLatest {
emit(it)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
But when I changed the value in my Firebase, it's not fetching new data...
Anyone know why it's not fixed
I kind of find the way, but it's with callback, if we used callback, it can fetch data update even without live-data, but for my case I tried to push the callback to live data again,
so the code will be like this:
fun checkInDanger(networkStatus: (NetworkStatus<List<UserFire>>) -> Unit) {
fs.collection(UserFire.COLLECTION).whereEqualTo(UserFire.DANGER, true)
.addSnapshotListener { value, e ->
if (e != null) {
networkStatus(NetworkStatus.Failed("Error occurred\n${e.code}"))
return#addSnapshotListener
}
if (value == null || value.isEmpty) networkStatus(NetworkStatus.Empty)
else {
val users = value.map { it.toObject(UserFire::class.java) }
networkStatus(NetworkStatus.Success(users))
}
}
}
In my repositories:
override fun checkInDanger(callback: (Status<List<User>>) -> Unit) {
network.checkInDanger { result ->
when (result) {
is NetworkStatus.Success -> callback(Status.Success(result.data.map {
MapVal.userFireToDom(it)
}))
is NetworkStatus.Empty -> callback(Status.Success(listOf<User>()))
is NetworkStatus.Failed -> callback(Status.Error(null, result.error))
}
}
}
In my ViewModel:
fun checkInDanger(callback: (Status<List<User>>) -> Unit) = useCase.checkInDanger { callback(it) }
val setUsers = MutableLiveData<List<User>>().apply { this.value = listOf() }
val users: LiveData<List<User>> = setUsers
In my UI Class (Fragment Main):
val inDangerCallback: (Status<List<User>>) -> Unit = {
if (relative != null) {
when (it) {
is Status.Success ->
viewModel.setUsers.value =
it.data?.filter { user -> user.username in relative!!.pure }
else -> requireView().createSnackBar(it.error!!, 1000)
}
}
}
viewModel.checkInDanger(inDangerCallback)
viewModel.users.observe(viewLifecycleOwner) { users ->
println(users.size})
users?.forEach { user -> println(user.username) }
}
And the code can run perfectly and update automatically...
This question already has answers here:
Collect from several stateflows
(4 answers)
Closed 1 year ago.
This code invokes two methods on the same viewmodel and listens for updates. But only the first method completes, the second does not event trigger.
private fun initData() {
lifecycleScope.launchWhenStarted {
viewModel.incrementCount().collect {
info { "Count: $it" }
}
viewModel.getAllTeams().collect {
when (it) {
is State.Success -> {
info { "Got teams with size: ${it.result}.size" }
}
is State.Error -> {
info { "Error getting teams: ${it.message}" }
}
State.Loading -> {
info { "Loading all teams" }
}
}
}
}
}
ViewModel
class DashboardViewModel : ViewModel(), com.droid.common.Logger {
fun incrementCount(): MutableStateFlow<Int> {
val countState = MutableStateFlow(0)
viewModelScope.launch {
repeat(5) {
countState.value = (it)
delay(1000)
}
}
return countState
}
fun getAllTeams(): MutableStateFlow<State> {
val state = MutableStateFlow<State>(State.None)
state.value = State.Loading
viewModelScope.launch {
try {
val allTeams = FootballAPIClient.apiService.getAllTeams()
state.value = State.Success(allTeams)
} catch (exception: Exception) {
error { "Error getting all teams: ${exception.message}" }
state.value = State.Error(exception.message.toString())
}
}
return state
}
However, calling them with separate lifecycleScope works
private fun initData() {
lifecycleScope.launchWhenStarted {
viewModel.incrementCount().collect {
info { "Count: $it" }
}
}
lifecycleScope.launchWhenStarted {
viewModel.getAllTeams().collect {
when (it) {
is State.Success -> {
info { "Got teams with size: ${it.result}.size" }
}
is State.Error -> {
info { "Error getting teams: ${it.message}" }
}
State.Loading -> {
info { "Loading all teams" }
}
}
}
}
}
I can't seem to understand this behavior, anybody knows why?
You will need different coroutines, since collect() is a suspending function that suspends until your Flow terminates.
The problem with launchWhenStarted is that while your newly emitted items will not be processed your producer will still run in the background.
For collecting multiple flows the currently recommended way is:
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
viewModel.incrementCount().collect { ... }
}
launch {
viewModel.getAllTeams().collect { ... }
}
}
}
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"))
}
}
}