idanatz / OneAdapter

A Viewholderless Adapter for RecyclerView, who supports builtin diffing, states (paging, empty...), events (clicking, swiping...), and more.
MIT License
470 stars 45 forks source link

Blinking elements when fragment resumes #30

Closed strangel3t closed 3 years ago

strangel3t commented 3 years ago

I followed the Databinding example and noticed that everytime I leave the fragment and return to it, the elements inside oneadapter redraw. Is this expected or did I miss something?

idanatz commented 3 years ago

This kind of behavior should not happen but, without seeing the entire implementation of your fragment and data management there is no way of telling what's wrong.

If you can post all the relevant code or even better upload a minimal project with the behavior above so I can resolve it if needed.

strangel3t commented 3 years ago

Thanks for the quick reply, with a custom adapter it works as usual, so I don't think it's a problem related to the Fragment lifecycle, here is the sample code:

Model ->

data class ResultItem(
    override val uniqueIdentifier: Long,
    val label: String,
    val iconTint: Int
) : Parcelable, Diffable {

    override fun areContentTheSame(other: Any): Boolean {
         return other is ResultItem && label == other.label
    }
}

Module->

class CheckmarkResultModule(parent: Fragment) :
    ItemModule<ResultItem>() {
    init {
        config {
            layoutResource = R.layout.item_checkmark_result
        }
        onBind { model, viewBinder, _ ->
            viewBinder.dataBinding?.run {
                setVariable(BR.resultModel, model)
                lifecycleOwner = parent.viewLifecycleOwner
                executePendingBindings()
            }
        }
    }
}

Fragment logic->

class PlanInformationFragment : BaseFragment(R.layout.fragment_plan_information) {

    private val args: PlanInformationFragmentArgs by navArgs()

    private val binding by viewBinding(FragmentPlanInformationBinding::bind)

    private lateinit var testItems: List<ResultItem>

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val planInformationArray =
            requireContext().resources.getStringArray(R.array.plan_information)
        testItems = planInformationArray.mapIndexed { index, string ->
            ResultItem(index.toLong(), string, R.color.orange)
        }
        return super.onCreateView(inflater, container, savedInstanceState)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        with(binding) {
             with(resultsList) {
                OneAdapter(this) { itemModules += CheckmarkResultModule(this@PlanInformationFragment) }.apply {
                    setItems(testItems)
                }
            }

            choosePlan.setOnClickListener {
                safeNavigation(
                    directions = PlanInformationFragmentDirections.actionOpenPlanDetails()
                )
            }
        }
    }
}

checkmark_result.xml ->

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="resultModel"
            type="com.example.ResultItem" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@+id/checkmark"
            android:layout_width="24dp"
            android:layout_height="24dp"
            android:importantForAccessibility="no"
            android:src="@drawable/ic_check"
            android:layout_marginTop="4dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="@id/label"
            app:tint="@color/light_green" />

        <TextView
            android:id="@+id/label"
            style="@style/OnboardingResult"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:text="@{resultModel.label}"
            app:layout_constraintStart_toEndOf="@id/checkmark"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="@tools:sample/lorem" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Fragment layout ->

<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/resultsList"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_constraintTop_toTopOf="parent"
        tools:itemCount="3"
        tools:listitem="@layout/item_checkmark_result" />

    <Button
        android:id="@+id/choosePlan"
        style="@style/ButtonAction.Orange"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="42dp"
        android:text="@string/onboarding_your_program_choose_plan"
        app:layout_constraintTop_toBottomOf="@id/resultsList" />

</androidx.constraintlayout.widget.ConstraintLayout>

Navigation for the choosePlan button

    <fragment
        android:id="@+id/plan_information"
        android:name="com.example.PlanInformationFragment"
        android:label=""
        tools:layout="@layout/fragment_plan_information">

        <argument
            android:name="title"
            app:argType="reference" />

        <action
            android:id="@+id/action_open_plan_details"
            app:destination="@id/plan_details"/>
    </fragment>

Navigation code:

fun Fragment.safeNavigation(directions: NavDirections) {
    NavHostFragment.findNavController(this).currentDestination?.getAction(directions.actionId)
        ?.apply {
            findNavController().navigate(directions)
        }
}
strangel3t commented 3 years ago

Checking your suggestions I think it's caused by Fragment being recreated during onbackpress on navigation architecture by default. Because you process stuff on background it's noticeable the redrawing, while a normal adapter just makes it look like it was preserved.

I'll see what I can do about that. Thanks

strangel3t commented 3 years ago

If someone is having a similar issue check this link: https://stackoverflow.com/questions/54581071/fragments-destroyed-recreated-with-jetpacks-android-navigation-components

idanatz commented 3 years ago

Thanks for the information 👍🏼