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

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).

Related

Handle NPE in Kotlin Flow For Room Database

I want to retrieve single object from Room database, so i have this method in Dao
// in Dao
#Query("SELECT * FROM table_foo ORDER BY RANDOM()")
fun getSingleFoo(): Flow<FooEntity>
That object then will be mapped into others model, let say PlainFoo.
// in Repository
fun getRandomFoo(): Flow<PlainFoo> = dao.getSingleFoo()
.map(FooEntity::asExternalModel)
But in the first launch of this app, the table is empty. It makes the dao function return null and trigger NPE when being mapped. I try to wrap it inside a sealed interface like this.
// Result.kt as wrapper
sealed interface Result<out T> {
data class Success<T>(val data: T) : Result<T>
data class Error(val exception: Throwable? = null) : Result<Nothing>
}
fun <T> Flow<T>.asResult(): Flow<Result<T>> = this
.map<T, Result<T>> {
Result.Success(it)
}
.catch {
emit(Result.Error(it))
}
And then i call this method in the presentation layer like this.
// in ViewModel
val randomFoo = fooRepository.getRandomFoo().asResult()
// in activity, log only for checking
lifecycleScope.launch {
viewModel.randomFoo.collect {
Timber.tag("RandomFooFlow").d("$it")
}
}
It catches the error, which look like this.
Error(exception=java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter <this>)
But when new data is inserted, it does not get updated unless i reopen the app (which means new Flow is being collected, not the old one). So it seems that the flow is cancelled.
Is there any way to handle this without making my Dao return a
nullable object?
Note: if the data is already populated when opening the app, the flow is able to keep consuming new value).
Instead of dealing with exceptions, I would suggest to return nullable types from your Dao. You can then also update your mapper function to handle the type nullability. You won't need to wrap it into any Result class, just a simple null check on the UI end would suffice.
// Dao
#Query("SELECT * FROM table_foo ORDER BY RANDOM()")
fun getSingleFoo(): Flow<FooEntity?>
// Repo
fun getRandomFoo(): Flow<PlainFoo?> = dao.getSingleFoo().map { it?.asExternalModel() }
Could you please call repository getRandomFoo() method from inside coroutine in view model ? And also you need to call response with data observe like LiveData or StateFlow. By the way, you can wrap your result with wrap inside repository. In code example, I do not care about it because your error is not related with mapping.
View Model
private val _stateFlow = MutableStateFlow()
val stateFlow:StateFlow
fun getRandom(){
fooRepository.getRandomFoo().onEach{
if(it is Result.Success){
stateFlow.value = it
}
}.launchIn(viewModelScope)
}
Fragment or activity
viewLifecycleOwner.lifecycle.repeatOnLifecycle{
stateFlow.collect{
// Listen data for your UI
}
}

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

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.

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

Trying to use ViewModels inside of another ViewModel, Errors with LifecycleObserver and Ownership (Kotlin)

Im trying to get some data out of other ViewModels inside another ViewModel to make my code smaller, but im having a problem trying to implement what already worked on a fragment or in a activity, this is what i got:
class ObraConMediaViewModel(private val context: ViewModelStoreOwner,
private val id: Int): ViewModel(), LifecycleObserver {
var allObras: LiveData<ArrayList<ObraConMedia>>
private lateinit var viewModelobras: ViewModelObras
private lateinit var viewModelMediaObra: ViewModelMediaObra
val repositoryobras =ObrasRepository()
val repositoryMediaObra = MediaObraRepository()
val viewModelFactoryobras = ViewModelFactoryObras(repositoryobras)
val viewModelMediaObraFactory = ViewModelMedIaObraFactory(repositoryMediaObra)
init{
viewModelobras = ViewModelProvider(context, viewModelFactoryobras)
.get(ViewModelObras::class.java) // requireActivity() when called
viewModelMediaObra = ViewModelProvider(context, viewModelMediaObraFactory)
.get(ViewModelMediaObra::class.java)
viewModelobras.getObras(id)
viewModelobras.myResponse.observe(this , Observer { response ->
if (response.isSuccessful){
Log.d("Response", response.body()?.ans?.get(0)?.autor)
Log.d("Response", response.body()?.ans?.get(1)?.autor)
}else{
Log.d("Response", response.errorBody().toString())
}})
viewModelMediaObra.getMediaObra(Constantes.PRUEBA_ID)
viewModelMediaObra.myResponse.observe(this, Observer { response ->
if (response.isSuccessful){
Log.d("Response", response.body()?.ans?.get(0)?.filePath)
}
})
}}
I was having trouble with the Observer but extending the class to LifecycleObserver fixed it, i have no idea if this will even work but the only error that i have right now its the owner of the .observe(this,....), i dont seem to find a way to pass a lifecycleowner from the fragment to this viewmodel. All the variables i need to make this viewmodel work are inside those two responses. If this is a very bad way to do it please tell me. Thanks for reading.
Kindly note that above approach is not correct.
One should not create a instance of ViewModel inside another ViewModel.
There is a possibility that one ViewModel may get destroyed before another. This will lead to garbage reference and memory leaks.
I would recommend you to create the instance of both View Models in an Activity/Fragment and then call respective methods of ViewModel from Activity/Fragment.
Also, as you want to make your code smaller and concise, I highly recommend you Shared ViewModel.
This Shared ViewModel can be used by two fragments.
Please refer to this link

Is there an annotation that triggers a warning if an object is passed that captures enclosing ‘this’?

I have a Kotlin function with this signature:
fun registerDisposer(obj: Any, disposer: Closeable)
What the function does is attach disposer to a phantom reference and arrange it to be closed when obj is garbage-collected (i.e. when the phantom reference object is enqueued). The class of obj is supposed to call it something like this:
class Holder(private val res1: Closeable, private val res2: Closeable) {
init {
registerDisposer(this, object: Closeable {
private val res1 = this#Holder.res1
private val res2 = this#Holder.res2
override fun close() {
res1.close()
res2.close()
}
})
}
}
(Let’s ignore whether this is a good idea to rely on this with general Closeables; the actual resource in question is a pointer managed by native/JNI code – I am trying to follow Hans Boehm’s advice. But all of this is not particularly relevant for this question.)
I am worried that this design makes it too easy to inadvertently pass an object that captures this from the outer scope, creating a reference loop and preventing the object from being garbage-collected at all:
registerDisposer(this, Closeable {
this.res1.close()
this.res2.close()
})
Is there an annotation I can add to the disposer parameter that will trigger a warning in this situation?
As of this writing, the answer seems to be: probably not.
It turns out a registerDisposer function already exists as the register method of java.lang.ref.Cleaner, and it has no such annotation.
In Android, there is a similar annotation for android.os.AsyncTask, but that simply warns at any anonymous object having AsyncTask as its base class, whether it captures this or not. (This makes sense in Java, where anonymous classes always capture this, but not in Kotlin.)