morning all, let's say I have this code
internal val canAllowProcess: StateFlow<Boolean> = combine(
_isLoading, _isEligible
) { arg1, arg2 ->
// do something here
}.stateIn(
scope = viewModelScope, // how long this variable should survive
started = SharingStarted.WhileSubscribed(5000),
initialValue = false
)
My question is, will this canAllowProcess emit data only if _isLoading and _isEligible (they are both StateFlow) are both emitting new data? (I thought as long as one flow emit the new data, then canAllowProcess will also be triggered)
Thanks in advance!
If they're StateFlows themselves, they will have initial values, and those values will be taken by combine immediately. Following that, combine will be triggered every time one of the flows emits a new value.
So, for example:
initialValues: isLoading = false, isEligible = false -> combine is triggered with (false, false)
isLoading changes to true -> combine is triggered with (true, false)
isEligible changes to true -> combine is triggered with (true, true)
isLoading changes to false -> cobine is triggered with (false, true)
The flow method that works in pairs is zip. With zip, this same scenario would look like this:
initialValues: isLoading = false, isEligible = false -> zip is triggered with (false, false)
isLoading changes to true -> zip not triggered
isEligible changes to true -> zip is triggered with (true, true)
isLoading changes to false -> zip not triggered
Related
In an Android project, we are currently trying to switch from LiveData to StateFlow in our viewmodels. But for some rare cases, we need to update our state without notifying the collectors about the change. It might sound weird when we think of the working mechanism of flows, but I want to learn if it's a doable thing or not. Any real solution or workaround would be appreciated.
If you don't need to react to the true state anywhere, but only the publicly emitted state, I would store the true state in a property directly instead of a MutableStateFlow.
private var trueState: MyState = MyState(someDefault)
private val _publicState = MutableStateFlow<MyState>()
val publicstate = _publicState.asStateFlow()
fun updateState(newState: MyState, shouldEmitPublicly: Boolean) {
trueState = newState
if (shouldEmitPublicly) {
_publicState.value = newState
}
}
If you do need to react to it, one alternative to a wrapper class and filtering (#broot's solution) would be to simply keep two separate StateFlows.
Instead of exposing the state flow directly, we can expose another flow that filters the items according to our needs.
For example, we can keep the shouldEmit flag inside emitted items. Or use any other filtering logic:
suspend fun main(): Unit = coroutineScope {
launch {
stateFlow.collect {
println("Collected: $it")
}
}
delay(100)
setState(1)
delay(100)
setState(2)
delay(100)
setState(3, shouldEmit = false)
delay(100)
setState(4)
delay(100)
setState(5)
delay(100)
}
private val _stateFlow = MutableStateFlow(EmittableValue(0))
val stateFlow = _stateFlow.filter { it.shouldEmit }
.map { it.value }
fun setState(value: Int, shouldEmit: Boolean = true) {
_stateFlow.value = EmittableValue(value, shouldEmit)
}
private data class EmittableValue<T>(
val value: T,
val shouldEmit: Boolean = true
)
We can also keep the shouldEmit flag in the object and switch it on/off to temporarily disable emissions.
If you need to expose StateFlow and not just Flow, this should also be possible, but you need to decide if ignored emissions should affect its value or not.
I have Unit test function RxJava with timeout but it doesn't subscribe for unit test.
Function on viewModel
fun loadData() {
loadDataUseCase.loadData(true)
.subscribeOn(Schedulers.io())
.timeout(30L, TimeUnit.SECONDS, schedulers)
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
onShowLoading.value = true
onShowError.value = false
onShowContent.value = false
}.subscribe(
{
onConnected.value = true
onShowContent.value = true
onShowError.value = false
onShowLoading.value = false
},
{
onShowError.value = true
onShowLoading.value = false
onShowContent.value = false
}
)
.addTo(compositeDisposable)
}
Function on unit test
#Test
fun `Load data is success`() {
// given
whenever(loadDataUseCase.loadData(true)).thenReturn(Observable.just(true))
// when
viewModel.loadData()
// then
viewModel.onShowError().test().assertValue(false).awaitNextValue().assertValue(false)
}
I try to debug this function but it doesn't invoke subscribe
I am a bit confused, what is onShowError() does it return a LiveData?
If I run the same code the test doesn't even finish (well I use only io dispatchers and postValue), for you it might be finished before the subscription even happens:
Since you rely on Schedulers.io() it is possible that your whole Subscription is finished before you get to even test your LiveData.
An other option is that your LiveData already has a false value: .assertValue(false). then the next .doOnSubscribe setting already triggers .awaitNextValue() and your whole test finishes, before the subscription can even be called.
Your tests should be fixed and not dependent on timing. Or if it is unavoidable then you have to synchronize your test somehow, an example of this is here:
#Timeout(1, unit = TimeUnit.SECONDS)
#Test
fun `Load data is success`() {
// given
whenever(loadDataUseCase.loadData(true)).thenReturn(Observable.just(true)
val testObserver = liveData.test()
testObserver.assertNoValue() // assert in correct state before actions
// when
loadData(liveData, mock)
//then
testObserver.awaitValueHistorySize(2)
.assertValueHistory(false, false)
}
fun <T> TestObserver<T>.awaitValueHistorySize(count: Int, delay: Long = 10): TestObserver<T> {
awaitValue()
while (valueHistory().size < count) {
// we need to recheck and don't block, the value we are trying to wait might already arrived between the while condition and the awaitNextValue in the next line, so we use some delay instead.
awaitNextValue(delay, TimeUnit.MILLISECONDS)
}
return this
}
I have created my DataStore and I have a boolean value in it:
private val DID_REBOOT = booleanPreferencesKey("did_reboot")
val didRebootFlow: Flow<Boolean> = context.dataStore.data
.map { preferences ->
preferences[DID_REBOOT] ?: false
}
suspend fun setDidReboot(didReboot: Boolean) {
context.dataStore.edit { preferences ->
preferences[DID_REBOOT] = didReboot
}
}
Now, on my BroadcastReceiver I need to check this value. Depending on the the value, I need to perform database operations:
GlobalScope.launch {
if(ds.didRebootFlow.first()) {
Log.d("-----------------", "true")
} else
Log.d("-----------------", "false")
}
In some place in my app, I am setting didRebootFlow to true. But my broadcast is printing false (in the second attempt it works fine).
2021-04-27 12:42:24.347 7469-7469/com.test.datastore D/-----------------: false
I am new to suspended functions and Flow and cannot figure it out how to make my code work.
My code logic is strongly dependant on the value of this field. How to get didRebootFlow once (and immediately) here?
I do not want to use .collect { } because I do not want "observe" didRebootFlow continuously. I would like to get its value only once. (Because observing didRebootFlow non-stop means I will be doing lots of database operations).
My question looks like simple but please see below.
stream alphabet : ----------------------(A)----------------------------(B)-----
stream number : ---(1)-----(2)------------------(3)----(4)----(5)----------(6)----
emission : true true false true true false
emit true if there was no alphabet item emission
emit false there was alphabet item emission and it is not consumed yet
emit true if last emitted alphabet item has been consumed
is there any good operator for it..?
If Alphabet (steamA) is a Hot Observable, I think this hackish take/skip based code can answer your problem:
Observable.merge(streamA, Observable.just("init"))
.switchMap(a -> {
if ("init".equals(a)) return streamN.map(n -> true);
return Observable.merge(
streamN.take(1).map(n -> false),
streamN.skip(1).map(n -> true)
)
}
It is unclear to me what "alphabet item emission [...] is not consumed yet" means for you. For this, one needs to know or control the recipient of the alphabet. Also there is a second consumer for the boolean output so there is a correlation between two flows.
Observable<String> alphabetFlow = ...
Observable<Integer> numberFlow = ...
AtomicBoolean consumed = new AtomicBoolean(true);
alphabetFlow
.observeOn(Schedulers.io())
.doOnNext(letter -> consumed.set(false))
.doAfterNext(letter -> consumed.set(true))
.subscribe(letter -> {
System.out.println("I'm consuming" + letter));
Thread.sleep(2000); // simulate processing
System.out.println("I've consumed" + letter));
});
numberFlow
.map(number -> consumed.get())
.subscribe(status -> System.out.println("Status: " + status));
Thread.sleep(100000); // in case you have this in main(String[] args)
I have a State(Enum) that contains (Good, Non-Critical, Critical) values
So requirement is :
should trigger when state goes in non-critical state.
should trigger when state goes in critical state.
should trigger when state stays in critical state for 15 seconds.
Input :
publishSubject.onNext("Good")
publishSubject.onNext("Critcal")
publishSubject.onNext("Critcal")
publishSubject.onNext("NonCritical")
publishSubject.onNext("Critacal")
publishSubject.onNext("Critical")
publishSubject.onNext("Good")
and so on...
See Code Structure for Reference:
var publishSubject = PublishSubject.create<State>()
publishSubject.onNext(stateObject)
publishSubject
/* Business Logic Required Here ?? */
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
AppLogger.printLog("Trigger Success --> ")
}
Please help,
Thanks in Advance,
You can use distinctUntilChanged() to suppress events that don't change the state. Filter out the normal events using filter().
Use the switchMap() operator to create a new subscription when the state changes. When the state is "critical", use the interval() operator to wait out the 15 seconds. If the state changes in that 15 seconds, switchMap() will unsubscribe and re-subscribe to a new observable.
publishSubject
.distinctUntilChanged()
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.filter( state -> state != State.Normal )
.switchMap( state -> {
if (state == State.Critical) {
return Observable.interval(0, 15, TimeUnit.SECONDS) // Note 1
.map(v -> State.Critical); // Note 2
}
return Observable.just( State.Noncritical );
})
.subscribe( ... );
interval() is given an initial value of 0, causing it to emit a value immediately. After 15 seconds, the next value will be emitted, and so on.
The map() operator turns the Long emitted by interval() into
The first two parts of your requirements should be combined into one. You're asking for the chain to be triggered on NonCritical and Critical events, ergo the chain should not be triggered for Good event. Likewise, you only need to trigger an event if the state is different from a previous event. For this two .filter events should suffice:
var lastKnownState: State = null
publishSubject
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.filter(this::checkStateDiffers) // Check we have a new state
.filter { state -> state != State.Good } // Check event is good
.subscribe {
AppLogger.printLog("Trigger Success --> ")
}
...
private fun checkStateDiffers(val state: State): Boolean {
val isDifferent = state != lastKnownState
if (isDifferent) lastKnownState = state // Update known state if changed
return isDifferent
}
The timeout requirement is a bit trickier. RxJava's timeout() operator gives the option of emitting an error when nothing new has been received for a period of time. However I am assuming that you want to keep listening for events even after you receive a timeout. Likewise, if we just send another Critical event it'll be dropped by the first filter. So in this case I'd recommend a second disposable that just has the job of listening for this timeout.
Disposable timeoutDisp = publishSubject
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.timeout(15, TimeUnit.SECONDS)
.onErrorResumeNext(State.Timeout)
.filter { state -> state == State.Timeout }
.filter { state -> lastKnownState == State.Critical }
.subscribe {
AppLogger.printLog("Timeout Success --> ")
}
Also adjust the checkStateDiffers() to not save this Timeout state in the first chain.
private fun checkStateDiffers(val state: State): Boolean {
if (state == State.Timeout) return true
var isDifferent = state != lastKnownState
if (isDifferent) lastKnownState = state // Update known state if changed
return isDifferent
}