Can I pass a MutableStateFlow object to a StateFlow variable directly? - kotlin

The Code A is from offical sample code here.
I think I can pass _uiState to uiState directly, so I write Code B, it seems that Code B can work well.
Can I pass a MutableStateFlow object to a StateFlow variable directly?
Code A
class InterestsViewModel(
private val interestsRepository: InterestsRepository
) : ViewModel() {
// UI state exposed to the UI
private val _uiState = MutableStateFlow(InterestsUiState(loading = true))
val uiState: StateFlow<InterestsUiState> = _uiState.asStateFlow()
...
}
Code B
class InterestsViewModel(
private val interestsRepository: InterestsRepository
) : ViewModel() {
// UI state exposed to the UI
private val _uiState = MutableStateFlow(InterestsUiState(loading = true))
val uiState: StateFlow<InterestsUiState> = _uiState
...
}

Yes, this is fine. There is one small functional advantage to asStateFlow(). It does not simply upcast to a read-only StateFlow, but also wraps it in a read-only StateFlow, such that a receiver cannot cast it to a MutableStateFlow and use its publishing functions. However, this should be considered something that no reasonable code should ever do, because it is asking for trouble to forcibly mutate something that is declared as read-only.
There is no equivalent asList() for MutableList(), so I'm not sure why they felt Flows need this feature and Collections don't. I can't find any discussion of it. It appeared in the same commit that first introduced StateFlow and SharedFlow.
A secondary feature of asStateFlow() is that it permits writing this kind of code more concisely.
val uiState = _uiState.asStateFlow()
is easier and faster to write than
val uiState: StateFlow<InterestsUiState> = _uiState
or
val uiState = _uiState as StateFlow<InterestsUiState>
especially if the type is long or complicated, such as StateFlow<List<SomeApi.Subtype>>, for example.

Related

Best practice when using mutableState with jetpack compose inside viewmodel with exposing mutable state

I have the following ViewModel
#HiltViewModel
class ShareViewModel #Inject constructor(
private val taskRepository: TaskRepository
): ViewModel() {
private val searchAppBarStateMutableState: MutableState<SearchAppBarState> = mutableStateOf(SearchAppBarState.CLOSED)
val searchAppBarState: State<SearchAppBarState> = searchAppBarStateMutableState
private val listOfTaskMutableStateFlow = MutableStateFlow<List<TodoTaskEntity>>(emptyList())
val listOfTaskStateFlow = listOfTaskMutableStateFlow.asStateFlow()
}
I never expose mutableStateFlow as in the example above. And SonarLint will show a warning when doing this.
MutableStateFlow" and "MutableSharedFlow" should not be exposed
So I apply the same technique to the mutableState
However, If I do like this below, I don't get any warning.
val searchAppBarStateMutableState: MutableState<SearchAppBarState> = mutableStateOf(SearchAppBarState.CLOSED)
Just wondering what is the best practice for using MutableState with jetpack compose.
To use mutableState with viewmodel, define mutableState with private setter inside viewmodel, ex -
var isVisible by mutableState(false)
private set
By doing above we can read the mutable state from outside of viewmodel but not update it. To update create a public function inside viewmodel, ex -
fun setVisibility(value: Boolean) {
isVisible = value
}
By making a setter function we are following separation of concerns and having a single source of truth for editing the mutableState.
I think the error is in that you are setting
val searchAppBarState: State<SearchAppBarState> = searchAppBarStateMutableState
if you want to share private value as not mutable you shouldn't set it equal rather you can use get modifier
val searchAppBarState: State<SearchAppBarState> get() = searchAppBarStateMutableState
Also it would be better to name it with underscore as many developers are used to like:
private val _searchAppBarState: MutableState<SearchAppBarState> = mutableStateOf(SearchAppBarState.CLOSED)
val searchAppBarState: State<SearchAppBarState> get() = _searchAppBarState

Why do the author need to use asStateFlow() in Compose?

The Code A is a sample code from the video.
I can't understand why the author need to use asStateFlow().
I think the Code B is OK, right?
Code A
class MainViewModel: ViewModel() {
private val _stateFlow= MutableStateFlow("Hello World")
val stateFlow = _stateFlow.asStateFlow()
...
}
Code B
class MainViewModel: ViewModel() {
private val _stateFlow= MutableStateFlow("Hello World")
val stateFlow = _stateFlow
...
}
Code A makes the stateFlow read-only while Code B exposes the mutable state flow as is.
The documentation of asStateFlow() is pretty clear about this:
Represents this mutable state flow as a read-only state flow.
If you take a look at the implemenation, you can see that it wraps the current (mutable) flow in a ReadonlyStateFlow which is, well, read-only:
public fun <T> MutableStateFlow<T>.asStateFlow(): StateFlow<T> =
ReadonlyStateFlow(this, null)
To make it easier to understand, if you use MainViewModel in a component, this outsider will be able to read the values but not write them in case of Code A. Meanwhile, if you use Code B, the outsider component may emit its own values to the state flow. This is usually undesirable as it should be the ViewModel's responsibility to emit data (this is called unidirectional data flow) as a response to actions coming from the observers (the view components).

Can I always use private set instead of private val in kotlin?

I read some kotlin project samples code, I find many author like to Code A.
I think Code B is more simpler.
1: Is Code B good way?
2: Can I always use private set intead of private val in Android Studio?
Code A
private val _uiState = MutableStateFlow(InterestsUiState(loading = true))
val uiState: StateFlow<InterestsUiState> = _uiState.asStateFlow()
Code B
var uiState = MutableStateFlow(InterestsUiState(loading = true))
private set
A, B are not the same code.
In code A, define another variable as StateFlow is preventing to change value in StateFlow from out of class.
In code B, you can update value in StateFlow from out of class.
Because you can refer MutableStateFlow.
Mutating state variable itself and Mutating state in StateFlow is different.
Observer observing StateFlow is received event when value in StateFlow is change but change StateFlow itself.
In other word, you should use code A for prevent to unexpected mutating from outer
I have seen this example
var userName by mutableStateOf("")
private set
but what i thought is best for a better encapsulation is this ,to deny the possibility of mutating the useName from outside the single source of truth (the viewmodel holder and responsible for managing the state )
private var _userName by mutableStateOf("")
val userName
get() = _userName

kotlin custom get immutable list from mutableList

I have a custom getter method for a mutable list to return an immtuable list by using Google's Guava library. And then this mutable list is accessed in the constructor.
data class mutableClass(val list: List<Foo>) {
private val mutableList: MutableList<Foo>
get() = ImmutableList.copyOf(field)
init {
mutableList = mutableListOf()
list.forEach {
mutableList.add(it.copy()) // Exception is thrown here.
// It actually calls its getter method which is an immutable
// list, so when init this class, it throw exception
}
}
}
data class Foo {}
And I decompile it to Java, in the init block, it calls the getter method of mutableList.
Is there a way to call the mutabbleList itself instead of getter method?
Of course it calls the getter (which returns ImmutableList.copyOf(field)).
You can do simply assignment to mutableList new copied mutable list in your init block:
data class MutableClass(val list: List<Foo>) {
private val mutableList: MutableList<Foo>
get() = ImmutableList.copyOf(field)
init {
mutableList = list.map { it.copy() }.toMutableList()
}
}
or whithout init:
data class MutableClass(val list: List<Foo>) {
private val mutableList: MutableList<Foo> = list.map { it.copy() }.toMutableList()
get() = ImmutableList.copyOf(field)
}
Kotlin stdlib opts for interface immutability. That means, the interface an implementation is boxed in determines the mutability of the reference itself.
Therefore, the right way to make a MutableList<T> just a List<T> is to box it, like follows:
val myMutableList = mutableListOf(1, 2, 3, 4)
val myImmutableList = myMutableList as List<Int>
That way, being the myImmutableList reference boxed in a List<Int>, it will only expose members from List<Int>, and not those that just MutableList<Int> define, which allow to mutate the state of the object, hence the list.
Then, if you really want to avoid the following issue (resuming from the above code),
val hackedList = myImmutableList as MutableList<Int>
... for which you would be able to access the mutable implementation through unboxing, you may rather be opting for the following solution:
class ImmutableList<T>(list: MutableList<T>) : List<T> by list
fun <T> MutableList<T>.toImmutable() = ImmutableList(this)
And then use it as follows:
val myMutableList = mutableListOf(1, 2, 3, 4)
val myImmutableList = myMutableList.toImmutable()
So you'll be avoiding the issue above. Indeed, any attempt to unbox the value return from MutableList<T>.toImmutable() will end up with a TypeCastException, as the implementation of the List<T> is no longer a MutableList<T>. Rather, it is an ImmutableList<T>, which doesn't expose any methods that might mutate the object.
Unlike #Lucas method, this way you won't be wasting time to copy elements, as you'll be relying on the by keyword in Kotlin, which allows you to implement an interface through an already existing implementation. That is, the MutableList<T> you'll be passing to the constructor of ImmutableList<T>.
When I was researching about this topic, the best solution it just worked for me is just enforcing by contract. If you are creating a mutable list, let's say:
val immutableList = mutableListOf(
Randomy.One,
Randomy.Two,
Randomy.Three
).toList() // We make it immutable?
and then you use an extension function or any of the recommendations given below, like using ImmutableList.copyOf(field), you might be paying a penalty because you're copying the items into another collection.
Another option is just paying the unboxing cost of doing something like:
val myImmutableList = myMutableList as List<Int>
The solution I opted for is just enforcing by contract, it's a very simple concept. Your MutableList inherits from List. If you want to share a collection of items with that level of abstraction, it's your choice to do it by enforcing the type:
val immutableList: List<Randomy> = mutableListOf(
Randomy.One,
Randomy.Two,
Randomy.Three
)
now if we share that list with another component, we'll use the right abstraction without any cost. We could also have used a Collection, because List inherits from Collection:
val immutableList: Collection<Randomy> = mutableListOf(
Randomy.One,
Randomy.Two,
Randomy.Three
)
For me, using a var instead of val field along with a private setter usually works best
class Order
class Something() {
var orders: List<Order> = listOf()
private set
fun addOrder(order: Order) {
orders = orders
.toMutableList()
.apply { add(order) }
}
}
This exposes it as immutable and requires a single field only. The price we pay is the overhead of creating a new collection when adding elements it.

Is a val property thread-safety?

I can use Code A to change the value of val a .
In my mind that val property is thread-safe, but it seems that it's not thread -safe in Code A,
it's just like var property, any thread can change the variable aImpl, different thread maybe get different value of val a
Code A
var aImpl = 0
val a: Int get() = aImpl
fun seta(){
aImpl=5
}
You fail to make a clear distinction between a val with and without a custom getter. If you lump those two together, like in your question, then val is not thread-safe; however Kotlin does make this distinction, as you can observe in this example:
val simpleVal: Int? = 3
val customVal: Int? get() = simpleVal
fun main(args: Array<String>) {
if (simpleVal != null) {
println(simpleVal + 1)
}
if (customVal != null) {
println(customVal + 1) // ERROR!
}
}
Error:(12, 21) Kotlin: Smart cast to 'Int' is impossible, because 'customVal' is a property that has open or custom getter
The smart cast is not allowed, among other reasons, due to the potential of another thread mutating the result of the custom get() call.
Therefore:
A simple val is thread-safe;
A val with a custom or open getter is not (necessarily) thread-safe.
Of course not. It will work as a dynamic getter for aImpl, not as property.
And aImpl isn't #Volatile, so there is no any guarantees of thread-safety.