Do people feel that `StateFlow` is a good replacem...
# coroutines
m
Do people feel that
StateFlow
is a good replacement for
LiveData
on Android? There is often a noticeable delay in updates, which leads to the UI to represent incorrect information when it first shows up. That never happened to me with
LiveData
.
z
There shouldn’t be a delay. Are you sure you’re not using the non-immediate Main dispatcher somehow?
t
LiveData supports lifecycle, Flow does not. Not simple replacement.
a
Please don't cross-post. @tateisu Obviously you haven't read this post.
g
LiveData supports lifecycle, Flow does not
It’s completely incorrect Flow was built with structured concurrency from ground up which is much more strict and powerful version than integration with lifecycle on LiveData it just doesn’t have exactly the same semantics as LiveData by default, but it’s not an issue of Flow, it’s just a matter of different semantics
☝️ 4
m
@Zach Klippenstein (he/him) [MOD] I checked and I was using
Dispatchers.Main
, but even changing it to
Dispatchers.Main.immediate
, the problem remains. @Albert Chang Yes, cross-posting was a bad idea. Sorry. I'm attaching a video which illustrates the problem. When the
RecyclerView
item view appear on the screen, the text is missing and an icon which should be hidden is visible. The video is slowed down to 1/10 of the original speed.
Here is the declaration of the item layout:
Copy code
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="<http://schemas.android.com/apk/res/android>"
    xmlns:tools="<http://schemas.android.com/tools>"
    xmlns:app="<http://schemas.android.com/apk/res-auto>">
    <data>
        <import type="android.view.View" alias="View" />
        <variable
            name="viewModel"
            type="com.example.ProjectViewModel" />
    </data>

    <com.google.android.material.card.MaterialCardView
        android:id="@+id/card_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginVertical="5dp"
        android:layout_marginHorizontal="10dp"
        android:background="#fff"
        app:cardCornerRadius="4dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:orientation="vertical">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:fontFamily="@font/montserrat"
                android:text="@{viewModel.stateFlow.projectEntity.name}"
                android:textSize="24sp"
                android:textStyle="bold"
                tools:text="Project name" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">

                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:src="@drawable/ic_baseline_cloud_24"
                    android:visibility="@{viewModel.isRemoteFlow ? View.VISIBLE : View.INVISIBLE}"
                    />
            </LinearLayout>
        </LinearLayout>

    </com.google.android.material.card.MaterialCardView>

</layout>
and here is the
ViewModel
definition:
Copy code
class ProjectViewModel(
    initialState: ProjectState2
) : ViewModel() {
    companion object {
        fun remote(projectEntity: ProjectEntity) =
            ProjectViewModel(ProjectState2.Remote.Available(projectEntity))
        fun local(projectEntity: ProjectEntity) =
            ProjectViewModel(ProjectState2.Local.Available(projectEntity))
    }

    private val stateMutableFlow = MutableStateFlow<ProjectState2>(initialState)
    val stateFlow : StateFlow<ProjectState2> get() = stateMutableFlow
    var state by stateMutableFlow
        private set

    val projectId get() = state.projectId

    val isRemoteFlow = state(stateFlow.map { it.isRemote }, false)
    val isRemote by isRemoteFlow
}
where
state()
is defined as:
Copy code
fun <T> ViewModel.state(flow : Flow<T>, initialValue: T) =
    flow.stateIn(viewModelScope, SharingStarted.Eagerly, initialValue)
Note that, so far,
state
is never updated, so the underlying
MutableStateFlow
never changes its state after inizialization.
g
where is code which consuming this view model?
m
@gildor The only code interacting with the view model is the view holder:
Copy code
class ViewHolder(
    private val binding: ProjectItemBinding
) : RecyclerView.ViewHolder(binding.root) {
    fun bindTo(projectViewModel: ProjectViewModel) {
        binding.viewModel = projectViewModel
    }
}
g
how binding is implemented?
so I’m curious about the code which actually subscribes on Flow
is it DataBinding?
m
@gildor The code which subscribes to the StateFlow and updates the view is automatically generated by DataBinding. I posted the layout file.
@gildor Did I omit anything?
g
so it’s databindign
looks that binding doesn’t uses default value of stateflow
m
What was missing is probably setting the lifecycleOwner of the item binding. My code, at the time when I reported the problem was:
Copy code
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder =
    ViewHolder(
        ProjectItemBinding.inflate(
            LayoutInflater.from(viewGroup.context),
            viewGroup,
            false
        )
    )
now it's:
Copy code
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder =
    ViewHolder(
        ProjectItemBinding.inflate(
            LayoutInflater.from(viewGroup.context),
            viewGroup,
            false
        ).apply {
            lifecycleOwner = this@ProjectListAdapter.lifecycleOwner
        }
    )
g
huh, but without lifecycleOwner it shouldn’t work a all, isn’ it? Same for livedata, it will not subscribe until you set lifecycleOwner
m
Everything is working fine, now.
g
👍
I just surprised that it had this behaviour before, I would expect that nothing is rendered without lifecycleOwner