I need to transfer a stream of values from a publisher / producer to one or multiple consumers / subscribers / observers. The exact requirements are:
the "stream" has a default value
consumers receive the last published value when they subscribe (the last value is replayed)
the last published value can also be retrieved via a value property
two consecutive published values are distinct (no duplicates)
all published values need to be received by the consumer (values cannot be conflated)
consumption happens via a FlowCollector
the implementation must be multiplatform (Android, iOS, JS)
MutableStateFlow is meeting almost all of these requirements except item #5:
Updates to the value are always conflated. So a slow collector skips fast updates, but always collects the most recently emitted value
(https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-state-flow/).
So something like this:
val flow = MutableStateFlow(0)
// runs in co-routine 1
flow.collect {
println("Collect: $it")
delay(100)
}
// runs in co-routine 2
repeat(10) {
flow.emit(it + 1)
}
will print 0 and 10 but not the numbers in between because the collector is slow and emitted values are conflated
I could use MutableSharedFlow:
val flow = MutableSharedFlow<Int>(replay = 1, extraBufferCapacity = 1)
MutableSharedFlow doesn't conflate values but 1) has no value property (there's last() but that's a suspend function), 2) allows duplicates and 3) has no initial values.
While it's possible to add these three requirements, adding the value property and the duplicate check isn't trivial.
I could use a BehaviorSubject:
val subject = BehaviorSubject(0)
val flow = subject.asFlow().distinctUntilChanged()
That would work perfectly but I'd have to add https://github.com/badoo/Reaktive just for this.
So here's my question: is there a Kotlin Flow solution that meets all the requirements without having to add the missing pieces "manually" (like with MutableSharedFlow)?
It's not so bad to add the missing pieces you want to MutableSharedFlow to have exactly the syntax you want:
class MutableBehaviorFlow<T : Any?>(
private val initialValue: T,
private val _backingSharedFlow: MutableSharedFlow<T> = MutableSharedFlow(
replay = 1,
extraBufferCapacity = Int.MAX_VALUE,
onBufferOverflow = BufferOverflow.SUSPEND
)
) : MutableSharedFlow<T> by _backingSharedFlow {
init {
tryEmit(initialValue)
}
val value: T
get() = try {
replayCache.last()
} catch (_: NoSuchElementException) {
initialValue
}
override suspend fun emit(value: T) {
if (value != this.value) _backingSharedFlow.emit(value)
}
override fun tryEmit(value: T): Boolean =
if (value != this.value) _backingSharedFlow.tryEmit(value)
else true
}
Related
In my application I want update data with SharedFlow and my application architecture is MVI .
I write below code, but just update one of data!
I have 2 spinners and this spinners data fill in viewmodel.
ViewModel code :
class MyViewModel #Inject constructor(private val repository: DetailRepository) : ViewModel() {
private val _state = MutableStateFlow<MyState>(MyState.Idle)
val state: StateFlow<MyState> get() = _state
fun handleIntent(intent: MyIntent) {
when (intent) {
is MyIntent.CategoriesList -> fetchingCategoriesList()
is MyIntent.PriorityList -> fetchingPrioritiesList()
}
}
private fun fetchingCategoriesList() {
val data = mutableListOf(Car, Animal, Color, Food)
_state.value = DetailState.CategoriesData(data)
}
private fun fetchingPrioritiesList() {
val data = mutableListOf(Low, Normal, High)
_state.value = DetailState.PriorityData(data)
}
}
With below codes I filled spinners in fragment :
lifecycleScope.launch {
//Send
viewModel.handleIntent(MyIntent.CategoriesList)
viewModel.handleIntent(MyIntent.PriorityList)
//Get
viewModel.state.collect { state ->
when (state) {
is DetailState.Idle -> {}
is DetailState.CategoriesData -> {
categoriesList.addAll(state.categoriesData)
categorySpinner.setupListWithAdapter(state.categoriesData) { itItem ->
category = itItem
}
Log.e("DetailLog","1")
}
is DetailState.PriorityData -> {
prioritiesList.addAll(state.prioritiesData)
prioritySpinner.setupListWithAdapter(state.prioritiesData) { itItem ->
priority = itItem
}
Log.e("DetailLog","2")
}
}
When run application not show me number 1 in logcat, just show number 2.
Not call this line : is DetailState.CategoriesData
But when comment this line viewModel.handleIntent(MyIntent.PriorityList) show me number 1 in logcat!
Why when use this code viewModel.handleIntent(MyIntent.CategoriesList) viewModel.handleIntent(MyIntent.PriorityList) not show number 1 and 2 in logcat ?
The problem is that a StateFlow is conflated, meaning if you rapidly change its value faster than collectors can collect it, old values are dropped without ever being collected. Therefore, StateFlow is not suited for an event-like system like this. After all, it’s in the name that it is for states rather than events.
It’s hard to suggest an alternative because your current code looks like you shouldn’t be using Flows at all. You could simply call a function that synchronously returns data that you use synchronously. I don’t know if your current code is a stepping stone towards something more complicated that really would be suitable for flows.
My use case is as follows:
Imagine that there is an Android Fragment that allows users to search for Grocery items in a store. There's a Search View, and as they type, new queries are sent to the Grocery item network service to ask for which items match the query. When successful, the query returns a list of Grocery items that includes the name, price, and nutritional information about the product.
Locally on the Android device, there is a list of known for "items for sale" stored in a raw file. It's in the raw resources directory and is simply a list of grocery item names and nothing else.
The behavior we wish to achieve is that as the user searches for items, they are presented with a list of items matching their query and a visual badge on the items that are "For Sale"
The constraints I am trying to satisfy are the following:
When the user loads the Android Fragment, I want to parse the raw text file asynchronously using a Kotlin coroutine using the IO Dispatcher. Once parsed, the items are inserted into the Room database table for "For Sale Items" which is just a list of names where the name is the primary key. This list could be empty, it could be large (i.e. >10,0000).
Parallel, and independent of #1, as the user types and makes different queries, I want to be sending out network requests to the server to retrieve the Grocery Items that match their query. When the query comes back successfully, these items are inserted into a different table in the Room database for Grocery Items
Finally, I only want to render the list returned from #2 once I know that the text file from #1 has been successfully parsed. Once I know that #1 has been successfully parsed I want to join the tables in the database on name and give that LiveData to my ViewModel to render the list. If either #1 or #2 fail, I want the user to be given an "Error occurred, Retry" button
Where I am struggling right now:
Seems achievable by simply kicking off a coroutine in ViewModel init that uses the IO Dispatcher. This way I only attempt to parse the file once per ViewModel creation (I'm okay with reparsing it if the user kills and reopens the app)
Seems achievable by using another IO Dispatcher coroutine + Retrofit + Room.
Satisfying the "Only give data to ViewModel when both #1 and #2 are complete else show error button" is the tricky part here. How do I expose a LiveData/Flow/something else? from my Repository that satisfies these constraints?
When you launch coroutines, they return a Job object that you can wait for in another coroutine. So you can launch a Job for 1, and 3 can await it before starting its flow that joins tables.
When working with Retrofit and Room, you can define your Room and Retrofit DAOs/interfaces with suspend functions. This causes them to generate implementations that internally use an appropriate thread and suspend (don't return) until the work of inserting/updating/fetching is complete. This means you know that when your coroutine is finished, the data has been written to the database. It also means it doesn't matter which dispatcher you use for 2, because you won't be calling any blocking functions.
For 1, if parsing is a heavy operation, Dispatchers.Default is more appropriate than Dispatchers.IO, because the work will truly be tying up a CPU core.
If you want to be able to see if the Job from 1 had an error, then you actually need to use async instead of launch so any thrown exception is rethrown when you wait for it in a coroutine.
3 can be a Flow from Room (so you'd define the query with the join in your DAO), but you can wrap it in a flow builder that awaits 1. It can return a Result, which contains data or an error, so the UI can show an error state.
2 can operate independently, simply writing to the Room database by having user input call a ViewModel function to do that. The repository flow used by 3 will automatically pick up changes when the database changes.
Here's an example of ViewModel code to achieve this task.
private val parsedTextJob = viewModelScope.async(Dispatchers.Default) {
// read file, parse it and write to a database table
}
val theRenderableList: SharedFlow<Result<List<SomeDataType>>> = flow {
try {
parsedTextJob.await()
} catch (e: Exception) {
emit(Result.failure(e)
return#flow
}
emitAll(
repository.getTheJoinedTableFlowFromDao()
.map { Result.success(it) }
)
}.shareIn(viewModelScope, SharingStarted.WhileSubscribed(5000), replay = 1)
fun onNewUserInput(someTextFromUser: String) {
viewModelScope.launch {
// Do query from Retrofit.
// Parse results and write to database.
}
}
If you prefer LiveData to SharedFlow, you can replace theRenderableList above with:
val theRenderableList: LiveData<Result<List<SomeDataType>>> = liveData {
try {
parsedTextJob.await()
} catch (e: Exception) {
emit(Result.failure(e)
return#liveData
}
emitSource(
repository.getTheJoinedTableFlowFromDao()
.map { Result.success(it) }
.asLiveData()
)
}
You could do this by having the ViewModel monitor when the two tasks are complete and set loading state LiveData variable to indicate that the UI should only update once both tasks are complete. For example:
class MainViewModel : ViewModel() {
private var completedA = false
private var completedB = false
private val dataALiveData = MutableLiveData("")
val dataA: LiveData<String>
get() = dataALiveData
private val dataBLiveData = MutableLiveData("")
val dataB: LiveData<String>
get() = dataBLiveData
private val dataIsReadyLiveData = MutableLiveData(false)
val dataIsReady: LiveData<Boolean>
get() = dataIsReadyLiveData
// You can trigger a reload of some of this data without having to reset
// any flags - the UI will be updated when the task is complete
fun reloadB() {
viewModelScope.launch { doTaskB() }
}
private suspend fun doTaskA() {
// Fake task A - once it's done post relevant data
// (if applicable), indicate that it is completed, and
// check if the app is ready
delay(3200)
dataALiveData.postValue("Data A")
completedA = true
checkForLoaded()
}
private suspend fun doTaskB() {
// Fake task B - once it's done post relevant data
// (if applicable), indicate that it is completed, and
// check if the app is ready
delay(2100)
dataBLiveData.postValue("Data B")
completedB = true
checkForLoaded()
}
private fun checkForLoaded() {
if( completedA && completedB ) {
dataIsReadyLiveData.postValue(true)
}
}
// Launch both coroutines upon creation to start loading
// the two data streams
init {
viewModelScope.launch { doTaskA() }
viewModelScope.launch { doTaskB() }
}
}
The activity or fragment could observe these three sets of LiveData to determine what to show and when, for example to hide the displayed elements and show a progress bar or loading indicator until it is done loading both.
If you wanted to handle error states, you could have the dataIsReady LiveData hold an enum or string to indicate "Loading", "Loaded", or "Error".
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val model: MainViewModel by viewModels()
binding.textA.visibility = View.INVISIBLE
binding.textB.visibility = View.INVISIBLE
binding.progressBar.visibility = View.VISIBLE
model.dataA.observe(this) { data ->
binding.textA.text = data
}
model.dataB.observe(this) { data ->
binding.textB.text = data
}
// Once the data is ready - change the view visibility state
model.dataIsReady.observe(this) { isReady ->
if( isReady ) {
binding.textA.visibility = View.VISIBLE
binding.textB.visibility = View.VISIBLE
binding.progressBar.visibility = View.INVISIBLE
// alternately you could read the data to display here
// by calling methods on the ViewModel directly instead of
// having separate observers for them
}
}
}
Any RxJava experts that can help me figure this one out?
I have a function that returns a Single<ByteArray> based on some position parameter.
Basically, I want to keep calling that function, until it returns an empty ByteArray.
NOTE: The position parameter is supposed to indicate how many bytes where already read. E.g. first time it will be 0, next time it will be how many bytes where retrieved the first time, and so on.
This is what I have now:
fun readBytes(position: Int) : Single<ByteArray>{ ... }
fun readAllLogs() : Single<LogEntry> {
val oldPosition = AtomicInteger(0)
val position = AtomicInteger(0)
Observable.defer {
readBytes(position.get())
}.reduceWith({ LogEntry() }, { log, bytes ->
position.addAndGet(bytes.size)
log += bytes
log
}).repeatUntil {
val canWeStop = position.get() == oldPosition.get()
oldPosition.set(position.get())
canWeStop
}.toObservable()
}
For completeness, here's my LogEntry accumulator
data class LogEntry {
var data: ByteArray = byteArrayOf()
private set
operator fun plusAssign(bytes: ByteArray) {
data += bytes
}
}
Question 1:
How can I exit this stream elegantly? E.g. not keeping track of separate variables outside the stream?
Question 2:
Any optimizations I can make? I'm going from Single to Observable twice to be able to chain these operators. I'm sure that can be more efficient
I have been facing an issue unit testing our business logic. The problem is when I wanted to unit test all the emissions of the livedata. Right now I only get the last emitted value but I wanted to unit test all the values that get emitted one by one.
Here is my rough setup.
Here is how my viewModel onSaveEvent click event looks like
disposable.add(finalSource
.observeOn(schedulerProvider.ui())
.doOnSubscribe {
// show spinner
_myLiveData.value = Loading(toShow = true, loadingMessage = "Loading")
}
.doAfterTerminate {
// remove spinner
_myLiveData.value = Loading(toShow = false)
}
.subscribe { resource ->
// Update either on success or error.
_myLiveData.value = resource
}
)
My view model test looks something like this.
#Test
fun testOnSaveEvent() {
// Other setup code.
Mockito.`when`(myRepo.onSave()).thenReturn(mockSingle)
myViewModel.onSaveEvent(salesTaxCompanyInfo)
testScheduleProvider.ui().triggerActions()
// I see that the value is of type Loading
val firstValue = myViewModel.myLiveData.getOrAwaitValue()
// The next value is also Loading, although I would like to see the success event.
val secondValue = myViewModel.myLiveData.getOrAwaitValue()
}
Ideally, I am looking for something on the lines TestCoroutineDispatcher listed here: https://medium.com/androiddevelopers/testing-two-consecutive-livedata-emissions-in-coroutines-5680b693cbf8
While I understand that LiveData only holds the last value it receives I wanted to know if there is any we can unit test the emissions one by one.
Introduction to the problem:
When using the MediatorLiveData that android has added with android jetpack, I find myself often calling the same function from every source. This can for example be because, whenever a source is updated, I have to check if it has an influence, or if another source is more important. An example in code (Kotlin, but shouldn't matter):
val isHovered = MutableLiveData<Boolean>()
val isSelected = MutableLiveData<Boolean>()
val color = MediatorLiveData<Int>().apply {
addSource(isHovered) { updateColor() }
addSource(isSelected) { updateColor() }
}
fun updateColor() {
if (isHovered.value == true)
color.value = Color.GREEN
else if (isSelected.value == true)
color.value = Color.RED
else
color.value = Color.GRAY
}
The item is green when hovering, red when selected and not hovering and gray otherwise. When isSelected changes to true, I still need to check whether it is hovering, before changing the color to red. Also when isHovering changes to false, I need to check whether it is selected before changing the color to grey. So the easiest is one function that takes all the variables into account and sets the color accordingly.
My problem:
When the MediatorLiveData changes from inactive to active, because the view is moved to the foreground, it can happen that the function updateColor is called multiple times, for each source that changed. This is unnecessary because each call already takes all variables into account. As this function can be quite complex and there can be many sources, is there a way to avoid calling it multiple times for the same states of the source LiveDatas?
I had the same issue and came up with the following solution. First, gather values from all sources to one value:
data class State(val isHovered: Boolean, val isSelected: Boolean)
private val state = MediatorLiveData<State>().apply {
fun update() {
value = State(isHovered.value ?: false, isSelected.value ?: false)
}
addSource(isHovered) { update() }
addSource(isSelected) { update() }
}
Then I created an extension function to emit only distinct values
fun <T> LiveData<T>.distinct(): LiveData<T> = MediatorLiveData<T>().apply {
var wasSet = false
addSource(this#distinct) {
// Update value if it has been changed or it's the first time the method called
// Because value is null by default it will not be set to null again, leaving the live data in [value not set] state
if (value != it || !wasSet) {
value = it
wasSet = true
}
}
}
So when observing state.distinct() you will get updated values only when it's really changed.
Sometimes also another extension can be useful:
fun <T> LiveData<T>.squashUpdates() = MediatorLiveData<T>().apply {
addSource(this#squashUpdates) {
postValue(it)
}
}
Normally, when LiveData value is changed, it notifies all observers immediately in the same stack frame. Let's say you have a MediatorLiveData and change values of multiple sources like:
isHovered.value = true
isSelected.value = true
state value will be changed two times in a row State(true, false) and State(true, true). They both will be dispatched, even when using distinct from above, because values are actually different. squashUpdates helps in this case by delaying dispatch to the end of the stack frame, dispatching last value only.
To avoid multiple calls, you should send only new updates from your LiveData instances. To achieve this, you can use SingleLiveEvent:
A lifecycle-aware observable that sends only new updates after
subscription, used for events like navigation and Snackbar messages.
This avoids a common problem with events: on configuration change
(like rotation) update can be emitted if the observer is active. This
LiveData only calls the observable if there's an explicit call to
setValue() or call().
Note that only one observer is going to be notified of changes.
You probably won't be able to prevent isHovered or isSelected from emitting items, but you can pervent color from emitting multiple items of same type. The idea is to change the color only when the current color is not equal to the new color.
fun updateColor() {
var newColor: Color?
if (isHovered.value == true)
newColor = Color.GREEN
else if (isSelected.value == true)
newColor = Color.RED
else
newColor = Color.GRAY
if (color.value != newColor) {
color.value = newColor!!
}
}