summaryrefslogtreecommitdiff
path: root/src/com/android/customization/picker/preview
diff options
context:
space:
mode:
authorGeorge Lin <giolin@google.com>2023-04-18 23:06:17 +0000
committerGeorge Lin <giolin@google.com>2023-04-19 16:27:46 +0000
commit12147e699dcfb93fd1b419322449f903ef329233 (patch)
tree428e05edfeafa4626439d0cb7d2084b20404124e /src/com/android/customization/picker/preview
parentcd6f960604d2c31a034a1b69a34188093ff33342 (diff)
downloadThemePicker-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.kt45
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