https://kotlinlang.org logo
#coroutines
Title
# coroutines
m

melatonina

06/07/2021, 11:17 PM
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

Zach Klippenstein (he/him) [MOD]

06/07/2021, 11:54 PM
There shouldn’t be a delay. Are you sure you’re not using the non-immediate Main dispatcher somehow?
t

tateisu

06/08/2021, 1:34 AM
LiveData supports lifecycle, Flow does not. Not simple replacement.
a

Albert Chang

06/08/2021, 3:41 AM
Please don't cross-post. @tateisu Obviously you haven't read this post.
g

gildor

06/08/2021, 5:40 AM
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

melatonina

06/08/2021, 8:50 AM
@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

gildor

06/08/2021, 9:05 AM
where is code which consuming this view model?
m

melatonina

06/08/2021, 9:06 AM
@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

gildor

06/08/2021, 9:07 AM
how binding is implemented?
so I’m curious about the code which actually subscribes on Flow
is it DataBinding?
m

melatonina

06/08/2021, 9:08 AM
@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

gildor

06/08/2021, 9:11 AM
so it’s databindign
looks that binding doesn’t uses default value of stateflow
m

melatonina

06/08/2021, 1:46 PM
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

gildor

06/09/2021, 3:18 AM
huh, but without lifecycleOwner it shouldn’t work a all, isn’ it? Same for livedata, it will not subscribe until you set lifecycleOwner
m

melatonina

06/10/2021, 8:28 AM
Everything is working fine, now.
g

gildor

06/10/2021, 8:28 AM
👍
I just surprised that it had this behaviour before, I would expect that nothing is rendered without lifecycleOwner
4 Views