diff options
author | George Lin <giolin@google.com> | 2023-04-18 23:06:17 +0000 |
---|---|---|
committer | George Lin <giolin@google.com> | 2023-04-19 16:27:46 +0000 |
commit | 12147e699dcfb93fd1b419322449f903ef329233 (patch) | |
tree | 428e05edfeafa4626439d0cb7d2084b20404124e /src/com/android/customization/picker/preview | |
parent | cd6f960604d2c31a034a1b69a34188093ff33342 (diff) | |
download | ThemePicker-12147e699dcfb93fd1b419322449f903ef329233.tar.gz |
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
Diffstat (limited to 'src/com/android/customization/picker/preview')
-rw-r--r-- | src/com/android/customization/picker/preview/ui/section/PreviewWithClockCarouselSectionController.kt | 45 |
1 files changed, 36 insertions, 9 deletions
diff --git a/src/com/android/customization/picker/preview/ui/section/PreviewWithClockCarouselSectionController.kt b/src/com/android/customization/picker/preview/ui/section/PreviewWithClockCarouselSectionController.kt index 43fb85bf..2f83fa76 100644 --- a/src/com/android/customization/picker/preview/ui/section/PreviewWithClockCarouselSectionController.kt +++ b/src/com/android/customization/picker/preview/ui/section/PreviewWithClockCarouselSectionController.kt @@ -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,15 +93,39 @@ 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 - lifecycleOwner.lifecycleScope.launch { - ClockCarouselViewBinder.bind( - carouselView = carouselView, - singleClockView = singleClockView, - viewModel = clockCarouselViewModel, - clockViewFactory = clockViewFactory, - lifecycleOwner = lifecycleOwner, - ) - } + + /** + * 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, + singleClockView = singleClockView, + viewModel = clockCarouselViewModel, + clockViewFactory = clockViewFactory, + lifecycleOwner = lifecycleOwner, + ) + if (onAttachStateChangeListener != null) { + carouselView.carousel.removeOnAttachStateChangeListener( + onAttachStateChangeListener, + ) + } + } + } + + override fun onViewDetachedFromWindow(view: View?) { + bindJob?.cancel() + } + } + carouselView.carousel.addOnAttachStateChangeListener(onAttachStateChangeListener) } return view |