Suspend set selected clock function
1. Make the function suspend to void UI blocking.
2. Only set the clock after a short delay to avoid users scrolling
frquently and too many heavy calls of setting clocks.
Test: Manually tested the clock carousel is smooth
Bug: 278850684
Change-Id: Ie3c1e6f274597f97d44cea59eb533aaf9c8cc949
diff --git a/src/com/android/customization/module/ThemePickerInjector.kt b/src/com/android/customization/module/ThemePickerInjector.kt
index e716406..98b0f5c 100644
--- a/src/com/android/customization/module/ThemePickerInjector.kt
+++ b/src/com/android/customization/module/ThemePickerInjector.kt
@@ -357,7 +357,7 @@
interactor: ClockPickerInteractor,
): ClockCarouselViewModel.Factory {
return clockCarouselViewModelFactory
- ?: ClockCarouselViewModel.Factory(interactor).also {
+ ?: ClockCarouselViewModel.Factory(interactor, Dispatchers.IO).also {
clockCarouselViewModelFactory = it
}
}
diff --git a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
index ae66ce3..cb2c86e 100644
--- a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
+++ b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
@@ -33,7 +33,7 @@
val selectedClockSize: Flow<ClockSize>
- fun setSelectedClock(clockId: String)
+ suspend fun setSelectedClock(clockId: String)
/**
* Set clock color to the settings.
diff --git a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
index 9537365..747f174 100644
--- a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
+++ b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
@@ -110,7 +110,7 @@
}
.mapNotNull { it }
- override fun setSelectedClock(clockId: String) {
+ override suspend fun setSelectedClock(clockId: String) {
registry.mutateSetting { oldSettings ->
val newSettings = oldSettings.copy(clockId = clockId)
newSettings.metadata = oldSettings.metadata
diff --git a/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt b/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
index 6f3657a..91b2773 100644
--- a/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
+++ b/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
@@ -47,7 +47,7 @@
val selectedClockSize: Flow<ClockSize> = repository.selectedClockSize
- fun setSelectedClock(clockId: String) {
+ suspend fun setSelectedClock(clockId: String) {
repository.setSelectedClock(clockId)
}
diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
index 34ccdb6..47c6d2d 100644
--- a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
@@ -19,7 +19,9 @@
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
@@ -30,6 +32,7 @@
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/**
* Clock carousel view model that provides data for the carousel of clock previews. When there is
@@ -38,6 +41,7 @@
class ClockCarouselViewModel
constructor(
private val interactor: ClockPickerInteractor,
+ private val backgroundDispatcher: CoroutineDispatcher,
) : ViewModel() {
@OptIn(ExperimentalCoroutinesApi::class)
val allClockIds: StateFlow<List<String>> =
@@ -77,17 +81,25 @@
.map { allClockIds -> if (allClockIds.size == 1) allClockIds[0] else null }
.mapNotNull { it }
+ private var setSelectedClockJob: Job? = null
fun setSelectedClock(clockId: String) {
- interactor.setSelectedClock(clockId)
+ setSelectedClockJob?.cancel()
+ setSelectedClockJob =
+ viewModelScope.launch(backgroundDispatcher) {
+ delay(SET_SELECTED_CLOCK_DELAY_MILLIS)
+ interactor.setSelectedClock(clockId)
+ }
}
class Factory(
private val interactor: ClockPickerInteractor,
+ private val backgroundDispatcher: CoroutineDispatcher,
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return ClockCarouselViewModel(
interactor = interactor,
+ backgroundDispatcher = backgroundDispatcher,
)
as T
}
@@ -95,5 +107,9 @@
companion object {
const val CLOCKS_EVENT_UPDATE_DELAY_MILLIS: Long = 100
+
+ // In the case if the user scroll the clock carousel frequently, we make a delay for
+ // setting the selected clock to avoid too many heavy calls.
+ const val SET_SELECTED_CLOCK_DELAY_MILLIS: Long = 650
}
}
diff --git a/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt b/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
index 2b0b47f..38bf25a 100644
--- a/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
+++ b/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
@@ -56,7 +56,7 @@
private val _selectedClockSize = MutableStateFlow(ClockSize.SMALL)
override val selectedClockSize: Flow<ClockSize> = _selectedClockSize.asStateFlow()
- override fun setSelectedClock(clockId: String) {
+ override suspend fun setSelectedClock(clockId: String) {
selectedClockId.value = clockId
}
diff --git a/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt
index 5bc7461..4cda0d5 100644
--- a/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt
+++ b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt
@@ -21,6 +21,7 @@
import com.android.customization.picker.clock.shared.model.ClockMetadataModel
import com.android.wallpaper.testing.collectLastValue
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -52,11 +53,12 @@
)
)
}
+ private lateinit var testDispatcher: CoroutineDispatcher
private lateinit var underTest: ClockCarouselViewModel
@Before
fun setUp() {
- val testDispatcher = StandardTestDispatcher()
+ testDispatcher = StandardTestDispatcher()
Dispatchers.setMain(testDispatcher)
}
@@ -67,16 +69,25 @@
@Test
fun setSelectedClock() = runTest {
- underTest = ClockCarouselViewModel(ClockPickerInteractor(repositoryWithMultipleClocks))
+ underTest =
+ ClockCarouselViewModel(
+ ClockPickerInteractor(repositoryWithMultipleClocks),
+ testDispatcher,
+ )
val observedSelectedIndex = collectLastValue(underTest.selectedIndex)
advanceTimeBy(ClockCarouselViewModel.CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
underTest.setSelectedClock(FakeClockPickerRepository.fakeClocks[2].clockId)
+ advanceTimeBy(ClockCarouselViewModel.SET_SELECTED_CLOCK_DELAY_MILLIS)
assertThat(observedSelectedIndex()).isEqualTo(2)
}
@Test
fun multipleClockCase() = runTest {
- underTest = ClockCarouselViewModel(ClockPickerInteractor(repositoryWithMultipleClocks))
+ underTest =
+ ClockCarouselViewModel(
+ ClockPickerInteractor(repositoryWithMultipleClocks),
+ testDispatcher,
+ )
val observedIsCarouselVisible = collectLastValue(underTest.isCarouselVisible)
val observedIsSingleClockViewVisible = collectLastValue(underTest.isSingleClockViewVisible)
advanceTimeBy(ClockCarouselViewModel.CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
@@ -86,7 +97,11 @@
@Test
fun singleClockCase() = runTest {
- underTest = ClockCarouselViewModel(ClockPickerInteractor(repositoryWithSingleClock))
+ underTest =
+ ClockCarouselViewModel(
+ ClockPickerInteractor(repositoryWithSingleClock),
+ testDispatcher,
+ )
val observedIsCarouselVisible = collectLastValue(underTest.isCarouselVisible)
val observedIsSingleClockViewVisible = collectLastValue(underTest.isSingleClockViewVisible)
advanceTimeBy(ClockCarouselViewModel.CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
diff --git a/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModelTest.kt b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModelTest.kt
index 92059e3..a329bb3 100644
--- a/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModelTest.kt
+++ b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModelTest.kt
@@ -15,8 +15,10 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
@@ -33,6 +35,7 @@
class ClockSettingsViewModelTest {
private lateinit var context: Context
+ private lateinit var testScope: TestScope
private lateinit var colorPickerInteractor: ColorPickerInteractor
private lateinit var clockPickerInteractor: ClockPickerInteractor
private lateinit var underTest: ClockSettingsViewModel
@@ -53,6 +56,7 @@
val testDispatcher = StandardTestDispatcher()
Dispatchers.setMain(testDispatcher)
context = InstrumentationRegistry.getInstrumentation().targetContext
+ testScope = TestScope(testDispatcher)
clockPickerInteractor = ClockPickerInteractor(FakeClockPickerRepository())
colorPickerInteractor =
ColorPickerInteractor(
@@ -73,7 +77,9 @@
.create(ClockSettingsViewModel::class.java)
colorMap = ClockColorViewModel.getPresetColorMap(context.resources)
- clockPickerInteractor.setSelectedClock(FakeClockPickerRepository.CLOCK_ID_0)
+ testScope.launch {
+ clockPickerInteractor.setSelectedClock(FakeClockPickerRepository.CLOCK_ID_0)
+ }
}
@After