airbnb / epoxy

Epoxy is an Android library for building complex screens in a RecyclerView
https://goo.gl/eIK82p
Apache License 2.0
8.5k stars 733 forks source link

EpoxyVisibilityTracker events not firing when using Mavericks #1135

Closed ryanmdd closed 3 years ago

ryanmdd commented 3 years ago

I am using the mavericks todo app example and am trying to add the onVisibilityStateChanged to one of my Epoxy Models. The onVisibilityStateChanged events never fire.

here is some sample code


package io.iotdrive.mddmapskt.fragments

import android.content.Context
import android.os.Bundle
import android.os.Parcelable
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ProgressBar
import androidx.annotation.IdRes
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.SnapHelper
import com.airbnb.epoxy.Carousel
import com.airbnb.epoxy.EpoxyRecyclerView
import com.airbnb.epoxy.EpoxyVisibilityTracker
import com.airbnb.mvrx.Mavericks
import com.airbnb.mvrx.MavericksView
import com.airbnb.mvrx.activityViewModel
import com.google.android.material.bottomnavigation.BottomNavigationView
import io.iotdrive.mddmapskt.R
import io.iotdrive.mddmapskt.utils.MddEpoxyController
import io.iotdrive.mddmapskt.viewmodels.LayoutViewModel

abstract class MddBaseFragment : Fragment(), MavericksView {

//    protected val viewModel by activityViewModel(LayoutViewModel::class)
    protected lateinit var mCoordinatorLayout: CoordinatorLayout
    protected lateinit var mEpoxyRecyclerView: EpoxyRecyclerView
    protected lateinit var mNavigationView: BottomNavigationView
    protected val epoxyVisibilityTracker: EpoxyVisibilityTracker = EpoxyVisibilityTracker()
    private val epoxyController by lazy { epoxyController() }

    abstract fun epoxyController(): MddEpoxyController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        epoxyController.onRestoreInstanceState(savedInstanceState)
    }

    override fun onResume() {
        super.onResume()
        epoxyVisibilityTracker.requestVisibilityCheck()
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_main, container, false)
        mCoordinatorLayout = view.findViewById(R.id.io_mdd_maps_id_fragment_main_coordinator_layout)
        mEpoxyRecyclerView = view.findViewById(R.id.id_view_recycler_epoxy_mdd)
        mEpoxyRecyclerView.setController(epoxyController)
        epoxyVisibilityTracker.attach(mEpoxyRecyclerView).also {
            Log.d("TRACKER", "ATTACHED")
        }
        mNavigationView = view.findViewById(R.id.navigation_bottom)
        mNavigationView.visibility = View.INVISIBLE
        return view
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        Carousel.setDefaultGlobalSnapHelperFactory(object : Carousel.SnapHelperFactory() {
            override fun buildSnapHelper(context: Context?): SnapHelper {
                return PagerSnapHelper()
            }
        })
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        epoxyController.onSaveInstanceState(outState)
    }

    override fun invalidate() {
        mEpoxyRecyclerView.requestModelBuild()
    }

    protected fun navigate(@IdRes id: Int, args: Parcelable? = null) {
        findNavController().navigate(id, Bundle().apply { putParcelable(Mavericks.KEY_ARG, args) })
    }
}

override fun epoxyController() = simpleController(layoutViewModel) { state ->
        when (state.componentScreen) {
            is Loading -> {
                loadingRowModel { id("loading") }
                return@simpleController
            }
            else -> {
                if (true == state.componentScreen()?.sections?.isEmpty()) {
                    emptyContent {
                        id("empty_content")
                        title("Empty Content")
                    }
                    return@simpleController
                }
            }
        }
        group {
            id("mdd-map-carousel-group")
            layout(R.layout.controller_carousel_map_group_mdd)
            mddMapModel {
                id("Mdd Map")
                state.componentScreen.invoke()?.sections?.mapIndexed { _, item: Section ->
                    when (item) {
                        is MddMapComponent -> {
                            center(state.visibleIndex)
                            item.items.mapIndexed { _, mddCarouselComponentItem ->
                                LatLng(
                                    mddCarouselComponentItem.coordinates.lat,
                                    mddCarouselComponentItem.coordinates.lon
                                )
                            }.let { markers(it) }
                        }
                        else -> {
                            Log.d("TAG", "nothing to do")
                        }
                    }
                }
            }
            carousel {
                id("This is a ViewPager.")
                hasFixedSize(true)
                state.componentScreen.invoke()?.sections?.mapIndexed { _, item: Section ->
                    when (item) {
                        is MddMapComponent -> {
                            Log.d("TAG", "MddMapComponent")
                            item.items.mapIndexed { iIndex: Int, iItem: MddCarouselComponentItem ->
                                MddPagerItemModel_()
                                    .title(iItem.title)
                                    .id(iItem.title)
                                    .onClickListener {
                                        Log.d("ON_CLICK", "TESTING")
                                        layoutViewModel.setVisibleIndex(iIndex)
                                    }
                                    .onVisibilityStateChanged { _, _, visibilityState ->
                                        Log.d("VISIBILITY_STATE", visibilityState.toString())
                                        if (visibilityState == VisibilityState.FOCUSED_VISIBLE) {
//                                            layoutViewModel.setVisibleIndex(iIndex)
                                        }
                                    }
                            }.let { models(it) }
                        }
                        else -> {
                            Log.e("TAG", "we have a problem")
                        }
                    }

                }
            }
        }
    }
pmecho commented 3 years ago

@ryanmdd Would you be able to share a sample project that reproduces this issue?

ryanmdd commented 3 years ago

@pmecho here is a sample project: https://github.com/ryanmdd/MddMapsKt

Thanks for the help.

pmecho commented 3 years ago

@ryanmdd Oh I see the problem! You need to update the version to 4.3.1 as that's when we added support for propagating the visibility events to models in a group. I see this in the logcat after bumping the version number:

2021-02-19 23:04:14.268 17076-17076/io.iotdrive.mddmapskt D/TAG: INIT
2021-02-19 23:04:14.351 17076-17076/io.iotdrive.mddmapskt D/VISIBILITY_STATE: 0
2021-02-19 23:04:14.351 17076-17076/io.iotdrive.mddmapskt D/VISIBILITY_STATE: 2
2021-02-19 23:04:14.351 17076-17076/io.iotdrive.mddmapskt D/VISIBILITY_STATE: 4
2021-02-19 23:04:14.352 17076-17076/io.iotdrive.mddmapskt D/VISIBILITY_STATE: 0