How to avoid default value in MutableStateFlow kotlin - android

I am using MutableStateFlow in my project. When we initialise the MutableStateFlow object we need to give default value.
val topics = MutableStateFlow<List<String>>(emptyList())
when I collect this value
[null, "Hello", "world"]
I want to pass this list in adapter . So is there a way we can remove the null object before passing in adapter or Is there any better way ?
viewModel.topics.collect { topicsList ->
println(topicsList) // [null, "Hello", "world"]
adapter.submitList(topicsList)
}

If you don't want it to have an enforced initial value, use MutableSharedFlow instead. If you give it replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST, and distinctUntilChanged(), it's basically the same thing as a MutableStateFlow without the enforced value. And if onBufferOverflow is not BufferOverflow.SUSPEND, tryEmit will always succeed so you can use tryEmit() instead of value = .
private val _topics = MutableSharedFlow<List<String>>(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
val topics: Flow<List<String>> = _topics.distinctUntilChanged()
// emitting to the shared flow:
_topics.tryEmit(newValue)

If you want to ignore initial value of StateFlow, set initial value null or anything you want. Then you can use filter function on flow.
For example initial value is null
launch {
val topicsState = MutableStateFlow<List<String?>?>(null)
topicsState.filterNotNull().map { topics -> topics.filterNotNull() }.onEach { topics ->
println(topics)
}.launchIn(this)
launch {
delay(1000)
topicsState.update { listOf(null, "Hello", "world") }
}
}
Output
[Hello, world]

Since it emits a list of strings you could try to initialise the StateFlow with a null like so
val topics = MutableStateFlow<List<String>?>(null)
And when you collect you can check if the emitted value is null or not
viewModel.topics.collect { topicsList ->
topicsList?.let { safeTopics ->
adapter.submitList(safeTopics)
}
}

If we have given a common generic type sealed class.
Common Sealed Class
sealed class Resource<T>(val data: T? = null, val error: String? = null) {
class Loading<T> : Resource<T>()
class Success<T>(data: T) : Resource<T>(data = data)
class Error<T>(error: String) : Resource<T>(error = error)
}
In that case, we can set the initial value like this.
private val _mutableStateFlow: MutableStateFlow<Resource<List<PackageModel>>?> = MutableStateFlow(null)
PackageModel is Model/Pojo class

I think what you need is this:
val sampleList = listOf(null, "Hello", "world")
val topics = MutableStateFlow<List<String>>(sampleList.filer { it != null })

Related

Boolean flows sync

I have few StateFlow fields in the ViewModel class. It's add/edit form screen where each StateFlow is validation property for each editable field on the screen.
I would like to write some class FormValidation with StateFlow property for validation state of whole form. Value of this field based on the values of validation state of all fields and emit true when all field is valid and false when any field is invalid.
Something like this:
class FormValidation(initValue: Boolean, vararg fieldIsValid: StateFlow<Boolean>) {
private val _isValid = MutableStateFlow(initValue)
val isValid: StateFlow<Boolean> = _isValid
init {
// todo: how to combine, subscribe and sync values of all fieldIsValid flows?
}
}
I know how to do it with LiveData<Boolean> and MediatorLiveData but i can't understand how to make it with flows.
Solution based on the answer of #tenfour04
class BooleanFlowMediator(scope: CoroutineScope, initValue: Boolean, vararg flows: Flow<Boolean>) {
val sync: StateFlow<Boolean> = combine(*flows) { values ->
values.all { it }
}.stateIn(scope, SharingStarted.Eagerly, initValue)
}
Demo code with StateFlow and ViewModel
class SyncViewModel : ViewModel() {
companion object {
private const val DEFAULT_VALUE: Boolean = false
}
private val values: List<List<Boolean>> = listOf(
listOf(false, false, false),
listOf(true, false, false),
listOf(false, true, true),
listOf(true, true, true)
)
private var index: Int = 0
private val _flow1 = MutableStateFlow(DEFAULT_VALUE)
val flow1: StateFlow<Boolean> = _flow1
private val _flow2 = MutableStateFlow(DEFAULT_VALUE)
val flow2: StateFlow<Boolean> = _flow2
private val _flow3 = MutableStateFlow(DEFAULT_VALUE)
val flow3: StateFlow<Boolean> = _flow3
val mediator = BooleanFlowMediator(viewModelScope, DEFAULT_VALUE,
flow1, flow2, flow3)
fun generateValues() {
val idx = (index + 1).mod(values.size).also { index = it }
val row = values[idx]
_flow1.value = row[0]
_flow2.value = row[1]
_flow3.value = row[2]
}
}
I think you can do this using combine. It returns a new Flow that emits each time any of the source Flows emits, using the latest values of each in a lambda to determine its emitted value.
There are also overloads of combine for up to five input Flows of different types, and one for an arbitrary number of Flows of the same type, which is what we want here.
Since Flow operators return basic cold Flows, but if you want to have a StateFlow so you can determine the initial value, you need to use stateIn to convert it back to a StateFlow with an initial value. And for that you'll need a CoroutineScope for it to run the flow in. I'll leave it to you to determine the best scope to use. Maybe it should be passed in from an owning class (like passing viewModelScope to it if the class instance is "owned" by the ViewModel). If you're not using a passed in scope, you will have to manually cancel the scope when this class instance is done with, or else the flow will leak.
I didn't test this code, but I think this should do it.
class FormValidation(initValue: Boolean, vararg fieldIsValid: StateFlow<Boolean>) {
private val scope = MainScope()
val isValid: StateFlow<Boolean> =
combine(*fieldIsValid) { values -> values.all { it } }
.stateIn(scope, SharingStarted.Eagerly, initValue)
}
However, if you don't need to synchronously inspect the most recent value of the Flow (StateFlow.value), then you don't need a StateFlow at all, and you can just expose a cold Flow. The instant the cold Flow is collected, it will start collecting its source StateFlows, so it will immediately emit its first value based on the current values of all the sources.
class FormValidation(initValue: Boolean, vararg fieldIsValid: StateFlow<Boolean>) {
val isValid: Flow<Boolean> = when {
fieldIsValid.isEmpty() -> flowOf(initValue) // ensure at least one value emitted
else -> combine(*fieldIsValid) { values -> values.all { it } }
.distinctUntilChanged()
}
}

get the value of a parameter from a data class in kotlin

So, i'm pretty new to kotlin and still learning stuff, I have a data class named Country with 4 parameters
County(name:String, policePhone:String, ambulancePhone:String,
firebrigadePhone:String)
, a listOf Country with 27 objects in it and a var nameC1 taken from the MainActivity.
I've called the list method forEach and I want to confront every name in the list with the variable nameC and when a match is found execute some code.
data class Country(val name: String, val police:String, val ambulance:String,val firefighter:String) {
}
var nameC1 = (activity as MainActivity).nameC
val numberList= listOf<Country>(
Country("Austria","133","144","122"),
Country("Belgium","101","100","100"),
Country("Bulgaria","166","150","160"),
Country("Croatia","192","194","193"),
Country("Cyprus","199","199","199"),
Country("Czech Republic","158","155","150"),
Country("Denmark","112","112","112"),
Country("Estonia","112","112","112"),
Country("Finland","112","112","112"),
Country("France","17","15","18"),
Country("Germany","110","112","112"),
Country("Greece","100","166","199"),
Country("Hungary","107","104","105"),
Country("Ireland","112","112","112"),
Country("Italy","113","118","115"),
Country("Latvia","112","112","112"),
Country("Lithuania","02","03","01"),
Country("Luxembourg","113","112","112"),
Country("Malta","112","112","112"),
Country("Netherlands","112","112","112"),
Country("Poland","997","999","998"),
Country("Portugal","112","112","112"),
Country("Romania","112","112","112"),
Country("Slovakia","158","155","150"),
Country("Slovenia","113","112","112"),
Country("Spain","092","061","080"),
Country("Sweden","112","112","112")
)
numberList.forEach { if (Country.name==nameC1 ) }
// i'm expecting String1==String2 but i'm
//stuck here because it says name is an unresolved reference
}
I'd use a getName() but i know in kotlin getter/setter are automated ( I'm not used to it) and ihaven't found anything useful on the kotlin doc. site,
I've seen on this site that someone suggested to implement Kotlin-reflection but I don't understand how I'm not supposed to get a parameter from a class by default.
forEach creates a lambda for each of the element in the collection. The default name for the element inside the lambda is it. But you can rename it to something else too. Refer to the doc
Here is a working example of your code
data class Country(val name: String, val police:String, val ambulance:String,val firefighter:String)
fun doThis(nameC1: String) {
val numberList= listOf<Country>(
Country("Austria","133","144","122"),
Country("Belgium","101","100","100"),
Country("Bulgaria","166","150","160"),
Country("Croatia","192","194","193"),
Country("Cyprus","199","199","199"),
Country("Czech Republic","158","155","150"),
Country("Denmark","112","112","112"),
Country("Estonia","112","112","112"),
Country("Finland","112","112","112"),
Country("France","17","15","18"),
Country("Germany","110","112","112"),
Country("Greece","100","166","199"),
Country("Hungary","107","104","105"),
Country("Ireland","112","112","112"),
Country("Italy","113","118","115"),
Country("Latvia","112","112","112"),
Country("Lithuania","02","03","01"),
Country("Luxembourg","113","112","112"),
Country("Malta","112","112","112"),
Country("Netherlands","112","112","112"),
Country("Poland","997","999","998"),
Country("Portugal","112","112","112"),
Country("Romania","112","112","112"),
Country("Slovakia","158","155","150"),
Country("Slovenia","113","112","112"),
Country("Spain","092","061","080"),
Country("Sweden","112","112","112") )
numberList.forEach {
if (it.name == nameC1) {
println("Match")
}
}
}
fun main() {
doThis("Slovenia")
}
Try it for yourself on play.kotlinlang.org - Link
The above code will execute the println function when the condition is true.
In the forEach loop you have to use it to access the name parameter.
like this
numberList.forEach { if (it.name==nameC1 )}
Try with the following code. You can apply filter on list
//if you want iterate your list try with below code
numberList.forEach {
val name = it.name
val police = it.police
}
//If you want apply filter on list take reference from below code
private var countryList: ArrayList<Country> = arrayListOf(
Country("Austria", "133", "144", "122"),
Country("Belgium", "101", "100", "100")
)
val searchList = countryList.filter { country-> country.name == nameC1}

How to programically trigger notify on MutableLiveData change

I have a LiveData property for login form state like this
private val _authFormState = MutableLiveData<AuthFormState>(AuthFormState())
val authFormState: LiveData<AuthFormState>
get() =_authFormState
The AuthFormState data class has child data objects for each field
data class AuthFormState (
var email: FieldState = FieldState(),
var password: FieldState = FieldState()
)
and the FieldState class looks like so
data class FieldState(
var error: Int? = null,
var isValid: Boolean = false
)
When user types in some value into a field the respective FieldState object gets updated and assigned to the parent AuthFormState object
fun validateEmail(text: String) {
_authFormState.value!!.email = //validation result
}
The problem is that the authFormState observer is not notified in this case.
Is it possible to trigger the notification programically?
Maybe you can do:
fun validateEmail(text: String) {
val newO = _authFormState.value!!
newO.email = //validation result
_authFormState.setValue(newO)
}
You have to set the value to itself, like this: _authFormState.value = _authFormState.value to trigger the refresh. You could write an extension method to make this cleaner:
fun <T> MutableLiveData<T>.notifyValueModified() {
value = value
}
For such a simple data class, I would recommend immutability to avoid issues like this altogether (replaces all those vars with vals). Replace validateEmail() with something like this:
fun validateEmail(email: String) = //some modified version of email
When validating fields, you can construct a new data object and set it to the live data.
fun validateFields() = _authFormState.value?.let {
_authFormState.value = AuthFormState(
validateEmail(it.email),
validatePassword(it.password)
)
}

Java Generics: passing multiple types to diamond

In android there is a class called MediatorLiveData<T> which acts as a ... mediator for LiveData.
There are situations like when we need to validate multiple live data objects and get a boolean value from them; we make a CombinedLiveData<T,U,S> and extend MediatorLiveData<S> so we can pass a higer-order function which accepts t:T, u:U and returns an S.
Imagine when we have to pass unlimited amount of Ts to this class and return a value of S: We have to make a CLD_12<T1,T2,...T12,S> and another one for 13 inputs, very frustrating.
So I am asking if there is a way to say CLD<T1..., S> : MLD<S>() or another way make this happen.
edit: adding the code
class CombinedLiveData2<T, K, S>(
source1: LiveData<T>,
source2: LiveData<K>,
private val combine: (data1: T?, data2: K?) -> S
) : MediatorLiveData<S>() {
private var data1: T? = null
private var data2: K? = null
init {
super.addSource(source1) {
data1 = it
value = combine(data1, data2)
}
super.addSource(source2) {
data2 = it
value = combine(data1, data2)
}
}
}

Transformations.map LiveData

I have following code :
val liveData = MutableLiveData<String>()
liveData.value = "Ali"
val res = map(liveData) { post(it) }
textview.text = res.value.toString()
fun post(name: String): String {
return "$name $name"
}
I expect it to print Ali Ali but it prints a null value. What am I missing?
You are missing a null check.
res.value.toString()
Imagine the case when res.value is null you are doing this.
null.toString() which the result is the string "null"
And the other hand, when you use LiveData the right approach is to observe all changes like zsmb13 suggested.
res.observe(this, Observer { name ->
textview.text = name
})
LiveData works asynchronously. The value you've set for it isn't immediately available in the transformed LiveData. Instead of trying to read that value directly, you should observe its changes, then you'll get the latest values:
res.observe(this, Observer { name ->
textview.text = name
})
(This code sample assumes that this is a LifecyleOwner, such as an AppCompatActivity.)

Categories

Resources