Probably a noob question. How do I set a default value to a BehaviourSubject.
I have an enum with 2 different values
enum class WidgetState {
HIDDEN,
VISIBLE
}
And a behaviour subject which emits the states
val widgetStateEmitter: BehaviorSubject<WidgetState> = BehaviorSubject.create()
My emitter starts emitting when the view logic is written. However it's HIDDEN by default. How do I set the default value as WidgetState.HIDDEN to my emitter widgetStateEmitter?
There's a static BehaviorSubject.createDefault(T defaultValue) factory method that allows to set the initial value.
The Javadoc for the defaultValue parameter says:
defaultValue - the item that will be emitted first to any Observer
as long as the BehaviorSubject has not yet observed any items from
its source Observable
So you just have to create your BehaviorSubject as follows:
val widgetStateEmitter: BehaviorSubject<WidgetState> =
BehaviorSubject.createDefault(HIDDEN)
In your constructor or onCreate (or similar) just call widgetStateEmitter.onNext(HIDDEN)
When Subscribing to this Subject you can use Start with Operator
widgetStateEmitter.startWith(HIDDEN)
//continue your chain
Related
I am new to LiveData thing in general and I am having a hard time understanding the difference between LiveData<String>() and LiveData<String?>(). I used them interchangeably and nothing seams to break. I know that LiveData.getValue() is marked with #Nullable in Java, so we end up getting String? anyway. So what makes LiveData<String?>() different from LiveData<String>()?
This ended up a bit long but I hope it covers everything!
A LiveData is meant to be observed. The observer receives data, and the LiveData's type says what type that data is. A LiveData<String> will only supply non-null Strings to its observers. A LiveData<String?> can supply Strings and nulls.
Which of those you want depends on what you're doing! Do you need to supply nulls, e.g. for some kind of missing value or whatever? Should they be part of your data? If not, like in any other situation, avoid making the type nullable unless it needs to be.
When an observer first observes a LiveData, it receives the current value. That way it can immediately handle the current data, update to display the current state, etc. But it's possible for a LiveData to have no value initially:
// non-null
val liveDataWithValue = MutableLiveData<String>("hi")
val emptyLiveData = MutableLiveData<String>()
// nullable
val nullableLiveDataWithValue = MutableLiveData<String?>(null)
val emptyNullableLiveData = MutableLiveData<String?>()
The first one there has an initial value. If you observe it, and that value hasn't been updated, the observer will immediately be called with "hi" for its parameter.
The second one has no value. If you observe that, the observer won't be called until a value is set on it. This is useful when you don't actually have any initial data - you can still set up your observer, and nothing will happen until some data is actually pushed.
The third one is the same as the first - it's a nullable String? but with a value of null. That's still a value so if you observe it, the observer will immediately be called with that null. It's still a piece of data your observer has to react to and process.
The last one is nullable but with no initial value. Like the second one, this means there's nothing for the observer to receive at first - but when it does have a value set on it, it could be a null. null is just another kind of value!
But if you go poking around at the LiveData's value property, instead of interacting with it through observe, then that no value state is represented internally by null. Java (or at least the version Android targets) doesn't really have a representation of no value separate from null, so that's just how they have to do things. It just doesn't publish anything until you explicitly set a value on it.
So for each of these:
val emptyLiveData = MutableLiveData<String>()
val nullableLiveDataWithValue = MutableLiveData<String?>(null)
val emptyNullableLiveData = MutableLiveData<String?>()
if you read their value in this state, it will be null for all of them. One explicitly has a value of null set on it, the others are both empty. This also means that even though emptyLiveData's type is non-null, its value property can be null, just because of this "can be empty" situation which is true for all LiveData objects. The nullability of the type is purely about what gets passed to observers.
You generally shouldn't be reading value anyway, except internally (wherever you're actually setting the value). Everything else should be interacting with that LiveData by observing it and reacting to the values that are published, and those values will be whatever type (nullable or not) that you specified
LiveData<String?>() meens that livedata can store null, read please this article to be fully informed: https://kotlinlang.org/docs/null-safety.html
I have an object where I wish to create hot StateFlow objects from a filtered cold SharedFlow. The intent is that the SharedFlow is an event channel of data changes, but all data can be retrieved to get the current state. This means for a given field, I can find the current state, and then monitor the SharedFlow to get state changes.
I would like to provide an API that (as an example) converts the SharedFlow into a StateFlow in a manner as follows:
var myVariable = DEFAULT_VALUE
val mySharedFlow = MutableSharedFlow<Int>()
val myStateFlow = mySharedFlow
.filter { it < 42 }
.asStateFlow(myVariable)// <- Convert to a StateFlow given a default value
This is obviously an overly simplified example, but my situation is more complex, and currently I have to invoke a function when ever a field changes, but currently I do the following:
myObj.onChange.collect(handler)
handler(myObj.getCurrentValue)
fun handler(data: Int) {
// Handle data change
}
But I would prefer to use a Hot StateFlow and remove the need for the second function call. Especially since many consumers of this are small bits of code (mostly just a single expression) that do not need to be in their own function context, and should just be simple lambdas.
Tenfour04 answered my question in his comment. The function I needed is called stateIn().
I'm using the Preference APIs that return flows.
There are cases when I need to combine values from different preferences. I know I can use
flow.combine.
Since the method where I need to combine is a suspend function and the value from the flows do not need to flow anywhere anymore, I could also define something like this:
suspend fun <T> Flow<T>.currentValue(): T {
var res: T? = null
this.collect {
res = it
}
return res!!
}
and then just read one value at at time:
val val1 = flow1.currentValue()
val val2 = flow2.currentValue()
val val3 = flow3.currentValue()
Is there a better way to do it? Or should I just use the combine method?
You don't need to define a currentValue function. There is already a Flow.first() function that returns the first value of the Flow. Since these Flows are designed to always emit an initial value with the current value of the preference, this will work fine.
Side note, your currentValue() function will never return because collect doesn't return until the Flow is complete.
Whether you want to individually call first() on each Flow or combine all of them and call first() on the combined Flow is up to you. I don't think it's any cleaner either way.
I've got a question about Flowables. I already have a few solutions for this issue, but I would like to double-check if these are the best possible solutions or not.
Context
I have an Interactor that is supposed to bookmark recipes on the DB. It looks like this:
/**
* This Interactor marks a recipe as "bookmarked" on the DB. The Interactor actually switches
* the isBookmarked value of the related recipeId. If it was marked as true, it switches its value
* to false. If it was false, then it switches its value to true.
*/
class BookmarkRecipeInteractorImpl(
private val recipesCacheRepository: RecipesCacheRepository
) : BookmarkRecipeInteractor {
override fun execute(recipeId: Int, callback: BookmarkRecipeInteractor.Callback) {
// Fetches the recipe from DB. The getRecipeById(recipeId) function returns a Flowable.
// Internally, within the RecipesCacheRepository, I'm using room.
recipesCacheRepository.getRecipeById(recipeId).flatMap { originalRecipe ->
// Switches the isBookmarked value
val updatedRecipe = originalRecipe.copy(
isBookmarked = !originalRecipe.isBookmarked
)
// Update the DB
recipesCacheRepository.updateRecipe(updatedRecipe)
// Here's the issue, since I'm updating a DB record and the getRecipeById returns
// a Flowable, as soon as I update the DB, the getRecipeById is going to get triggered
// again, and switch the value again, and again, and again...
}
.subscribe(
{
callback.onSuccessfullyBookmarkedRecipe(it.response)
},
{
callback.onErrorFetchingRecipes()
}
)
}
}
So, if you follow the code, the error is pretty straightforward. I get stuck on a loop, where I constantly change the recipe record.
Possible solutions
1) Have two different functions on my DAO, one called getRecipeByIdFlowable(id) that returns a Flowable, and another called getRecipeByIdSingle(id) that returns a rx.Single. That way I can expose the getRecipeByIdSingle(id) through the Repository and use it instead of the function that returns the Flowable. That way I cut the loop.
Pro: It works.
Con: I don't like having functions like this on my DAO.
2) Save the Disposable on a lateinit property and dispose it as soon as the subscriber triggers the onNext().
Pro: It works.
Con: I don't like having to do something like this, feels hacky.
3) Using ...getRecipeById(recipeId).take(1).flatMap... so it only handles the first emitted object.
Pro: It works, it looks tidy.
Con: I'm not sure if there's a better way to do it.
Question
Ideally, I would like to call some function that just allows me to disable the Flowable behavior and prevent it from emitting more items if the DB changes. So far the solution that I like the most is #3, but I'm not really sure if this is the right way to do it.
Thanks!
Edit 1
I'm just adding a bit more of information about the use case here. I need an Interactor that given a recipeId changes the isBookmarked value on DB to its oposite.
The DB records look like:
data class DbRecipeDto(
#PrimaryKey
val id: Int,
val name: String,
val ingredients: List<String>,
val isBookmarked: Boolean = false
)
I know that maybe there's some other ways in which I could tackle this issue differently. Maybe I could pass the recipeId arg and a bookmark (Boolean) argument and just run the update query.
But this use case it is totally made up, just an example; The thing that I'm trying to figure out how to prevent a Flowable from emitting more items if something changes on the DB.
You should probably call .take(1).singleOrError() on the end of getRecipeById(recipeId).
This will take the first item (or the error) emitted by the Flowable retrieved by calling getRecipeById and wrap it in a Single. In my opinion this correctly matches the semantics of what you want to achieve.
In addition, if I recall correctly, because you will be subscribing on a Single by doing this, your Flowable will not continue to do work after the first item is consumed by the downstream call to singleOrError.
I'm learning Data Binding by reading up on the official docs. Everything makes sense expect the possible infinite loops in the two-way binding. As per the official docs on two-way binding:
Be careful not to introduce infinite loops when using two-way data binding. When the user changes an attribute, the method annotated using #InverseBindingAdapter is called, and the value is assigned to the backing property. This, in turn, would call the method annotated using #BindingAdapter, which would trigger another call to the method annotated using #InverseBindingAdapter, and so on.
I understand first part of the statement that the method annotate with #InverseBindingAdapter will be called if the attribute is changed and new value is assigned to the backing property.
But what I don't understand is why #InverseBindingAdapter method is called again when #BindingAdapter method is called in this process and how it leads to infinite loops?
Better late than never I guess :) The reason why an infinite loop can happen is InverseBindingAdapter is a basically an observer for changes. So when a user changed something the onChanged observer in InverseBindingAdapter is triggered and executes some logic. So then BindingAdapter also reacts to the change in the field and updates value again so the change listener in InverseBindingAdapter is triggered again and not we are in a loop.
So here is some visual for that
User -> Types in their name "Joe"
InverseBindingAdapter -> triggered by the update
ObservableField/LiveData -> also updated with 2 way binding and now contains value "Joe"
As ObservableField/LiveData was updated BindingAdapter is triggered to set the new value into the filed.
InverseBindingAdapter -> detected another change in the field and got triggered.
step 3, 4, 5 on repeat....
Check my article on Medium on advanced DataBinding it actually describes this case with the ViewPager and 2 way binding example. (Yes, shameless self-plug disclaimer)
This issue can be resolved by checking the old and new values before setting the new value to the target view.
Example:
#BindingAdapter("android:text")
fun setText(editText: EditText, value: Int) {
val newVal = if (value == 0) "" else value.toString()
val oldVal = editText.text.toString()
if (oldVal == newVal) {
return
}
editText.setText(newVal)
if (newVal.isNotEmpty()) {
editText.setSelection(newVal.length)
}
}