facebook / litho

A declarative framework for building efficient UIs on Android.
https://fblitho.com
Apache License 2.0
7.71k stars 766 forks source link

View size in recycler sometimes changes #624

Open ChristinaGit opened 4 years ago

ChristinaGit commented 4 years ago

Version

    api "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.1"

    implementation 'com.facebook.litho:litho-core:0.33.0'
    implementation 'com.facebook.litho:litho-widget:0.33.0'

    kapt 'com.facebook.litho:litho-processor:0.33.0'
    implementation 'com.facebook.soloader:soloader:0.6.0'
    implementation 'com.facebook.litho:litho-fresco:0.33.0'

    implementation 'com.facebook.litho:litho-sections-core:0.33.0'
    implementation 'com.facebook.litho:litho-sections-widget:0.33.0'
    compileOnly 'com.facebook.litho:litho-sections-annotations:0.33.0'
    kapt 'com.facebook.litho:litho-sections-processor:0.33.0'

Issues and Steps to Reproduce


import com.facebook.litho.StateValue
import com.facebook.litho.annotations.OnCreateInitialState
import com.facebook.litho.annotations.OnUpdateState
import com.facebook.litho.annotations.Param
import com.facebook.litho.annotations.State
import com.facebook.litho.sections.Children
import com.facebook.litho.sections.SectionContext
import com.facebook.litho.sections.annotations.GroupSectionSpec
import com.facebook.litho.sections.annotations.OnCreateChildren
import com.facebook.litho.sections.annotations.OnViewportChanged
import com.facebook.litho.sections.common.SingleComponentSection
import com.facebook.litho.widget.Progress
import com.facebook.litho.widget.Text
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.immutableListOf

@GroupSectionSpec
object LoadingTestSectionSpec {
    @OnCreateInitialState
    fun onCreateInitialState(
        c: SectionContext,
        isLoading: StateValue<Boolean>,
        lastPage: StateValue<Int>,
        items: StateValue<ImmutableList<String>>
    ) {
        isLoading.set(false)
        lastPage.set(0)
        items.set(immutableListOf())
    }

    @OnCreateChildren
    fun onCreateChildren(
        c: SectionContext,
        @State isLoading: Boolean,
        @State lastPage: Int,
        @State items: ImmutableList<String>
    ): Children {
        val builder = Children.create()
        for (item in items) {
            builder.child(
                SingleComponentSection.create(c)
                    .key(item)
                    .component(
                        Text.create(c)
                            .textSizeSp(32f)
                            .text(item)
                            .build()
                    )
            )
        }

        if (isLoading) {
            builder.child(
                SingleComponentSection.create(c)
                    .key("loading")
                    .component(Progress.create(c).heightDip(25f).build())
                    .build()
            )
        }

        if (!isLoading && items.isEmpty()) {
            startLoading(c, lastPage)
        }

        return builder.build()
    }

    @OnViewportChanged
    fun onViewportChanged(
        c: SectionContext,
        firstVisiblePosition: Int,
        lastVisiblePosition: Int,
        totalCount: Int,
        firstFullyVisibleIndex: Int,
        lastFullyVisibleIndex: Int,
        @State isLoading: Boolean,
        @State lastPage: Int
    ) {
        if (!isLoading && (lastVisiblePosition == totalCount - 1 || totalCount == 0)) {
            startLoading(c, lastPage)
        }
    }

    private fun startLoading(
        c: SectionContext,
        lastPage: Int
    ) {
        LoadingTestSection.switchLoading(c)

        Thread {
            Thread.sleep(2000)

            LoadingTestSection.addNextPageAsync(
                c,
                immutableListOf(
                    "1-$lastPage",
                    "2-$lastPage",
                    "3-$lastPage"))
        }.start()
    }

    @OnUpdateState
    fun switchLoading(isLoading: StateValue<Boolean>) {
        isLoading.set(!(isLoading.get() ?: true))
    }

    @OnUpdateState
    fun addNextPage(
        isLoading: StateValue<Boolean>,
        lastPage: StateValue<Int>,
        items: StateValue<ImmutableList<String>>,
        @Param page: ImmutableList<String>
    ) {
        isLoading.set(false)
        lastPage.set((lastPage.get() ?: 0) + 1)
        items.set((items.get() ?: immutableListOf()).addAll(page))
    }
}
val c = ComponentContext(this);
val component: Component = RecyclerCollectionComponent.create(c)
    .disablePTR(true)
    .section(
        LoadingTestSection
            .create(SectionContext(c))
            .build()
    )
    .build()
setContentView(LithoView.create(c, component))

The progress view size changes after several updates.

Expected Behavior

The progress view size must not change.

colriot commented 4 years ago

Thanks for reporting @ChristinaGit! This is definitely a bug, for now you can workaround it by setting width for a Progress bar.

Btw, for a data set it's better to use DataDiffSection instead of converting each items to SingleComponentSection by hand. DataDiffSection handles updates and diffing efficiently.