Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 12147e69 authored by George Lin's avatar George Lin
Browse files

Fix race condition for clock carousel

Carousel.mMotionLayout is only ready after attachedToWindow. We can only
bind the view after attachedToWindow; otherwise, whenever the flow emits
any events before attachedToWindow and triggers calls to
Carousel.mMotionLayout, there will be a null pointer exception.

Test: Manually tested the app does not crash when emits early
Fixes: 278784117
Change-Id: Id65ed932b1526062063e453e910d16e01e1508dd
parent cd6f9606
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -38,7 +38,7 @@ class ClockCarouselView(

    var isCarouselInTransition = false

    private val carousel: Carousel
    val carousel: Carousel
    private val motionLayout: MotionLayout
    private lateinit var adapter: ClockCarouselAdapter
    private lateinit var scalingUpClockController: ClockController
+36 −9
Original line number Diff line number Diff line
@@ -20,8 +20,10 @@ package com.android.customization.picker.preview.ui.section
import android.app.Activity
import android.content.Context
import android.view.View
import android.view.View.OnAttachStateChangeListener
import android.view.ViewGroup
import android.view.ViewStub
import androidx.constraintlayout.helper.widget.Carousel
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.android.customization.picker.clock.ui.binder.ClockCarouselViewBinder
@@ -39,6 +41,7 @@ import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInt
import com.android.wallpaper.picker.customization.ui.section.ScreenPreviewSectionController
import com.android.wallpaper.picker.customization.ui.section.ScreenPreviewView
import com.android.wallpaper.util.DisplayUtils
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch

/** Controls the screen preview section. */
@@ -90,6 +93,18 @@ class PreviewWithClockCarouselSectionController(
            val singleClockViewStub: ViewStub = view.requireViewById(R.id.single_clock_view_stub)
            singleClockViewStub.layoutResource = R.layout.single_clock_view
            val singleClockView = singleClockViewStub.inflate() as ViewGroup

            /**
             * Only bind after [Carousel.onAttachedToWindow]. This is to avoid the race condition
             * that the flow emits before attached to window where [Carousel.mMotionLayout] is still
             * null.
             */
            var onAttachStateChangeListener: OnAttachStateChangeListener? = null
            var bindJob: Job? = null
            onAttachStateChangeListener =
                object : OnAttachStateChangeListener {
                    override fun onViewAttachedToWindow(view: View?) {
                        bindJob =
                            lifecycleOwner.lifecycleScope.launch {
                                ClockCarouselViewBinder.bind(
                                    carouselView = carouselView,
@@ -98,7 +113,19 @@ class PreviewWithClockCarouselSectionController(
                                    clockViewFactory = clockViewFactory,
                                    lifecycleOwner = lifecycleOwner,
                                )
                                if (onAttachStateChangeListener != null) {
                                    carouselView.carousel.removeOnAttachStateChangeListener(
                                        onAttachStateChangeListener,
                                    )
                                }
                            }
                    }

                    override fun onViewDetachedFromWindow(view: View?) {
                        bindJob?.cancel()
                    }
                }
            carouselView.carousel.addOnAttachStateChangeListener(onAttachStateChangeListener)
        }

        return view