From 504cc5a4d8f0b6ddd28ee9cbddf0422a99fcc621 Mon Sep 17 00:00:00 2001 From: Manuel Vivo Date: Thu, 21 May 2020 20:09:52 +0200 Subject: [PATCH] Removes ExecutorService in favor of coroutines Change-Id: Ibcdd2b67820a22741735cd79ba6deaecf6d25d96 --- .../iosched/tests/SyncTaskExecutorRule.kt | 37 ----- .../apps/iosched/tests/ui/AgendaTest.kt | 5 - .../apps/iosched/tests/ui/CodelabTest.kt | 5 - .../samples/apps/iosched/tests/ui/HomeTest.kt | 5 - .../samples/apps/iosched/tests/ui/InfoTest.kt | 5 - .../samples/apps/iosched/tests/ui/MapTest.kt | 5 - .../apps/iosched/tests/ui/ScheduleTest.kt | 5 - .../iosched/tests/ui/SessionDetailTest.kt | 5 - .../apps/iosched/tests/ui/SettingsTest.kt | 5 - .../samples/apps/iosched/di/SignInModule.kt | 20 ++- .../samples/apps/iosched/di/AppModule.kt | 20 ++- .../EventActionsViewModelDelegate.kt | 9 +- .../EventActionsViewModelDelegateModule.kt | 4 + .../sessiondetail/SessionDetailViewModel.kt | 65 ++++---- .../iosched/util/FirebaseAnalyticsHelper.kt | 10 +- .../apps/iosched/util/signin/SignInHandler.kt | 7 +- .../iosched/test/util/SyncTaskExecutorRule.kt | 51 ------ .../test/util/time/FakeIntervalMapperRule.kt | 65 -------- .../apps/iosched/ui/LaunchViewModelTest.kt | 5 - .../iosched/ui/MainActivityViewModelTest.kt | 5 - .../iosched/ui/agenda/AgendaViewModelTest.kt | 5 - .../apps/iosched/ui/feed/FeedViewModelTest.kt | 5 - .../apps/iosched/ui/map/MapViewModelTest.kt | 4 - .../ui/messages/SnackbarMessageManagerTest.kt | 5 - .../ui/onboarding/OnboardingViewModelTest.kt | 5 - .../MarkScheduleUiHintsShownUseCaseTest.kt | 5 - .../ui/schedule/ScheduleViewModelTest.kt | 12 +- .../SessionDetailViewModelTest.kt | 40 ++--- .../SessionFeedbackViewModelTest.kt | 10 -- .../FirebaseSignInViewModelDelegateTest.kt | 12 +- .../iosched/ui/signin/SignInViewModelTest.kt | 5 - .../ui/speaker/SpeakerViewModelTest.kt | 4 - .../util/IntervalMediatorLiveDataTest.kt | 136 ++++++++++++++++ .../iosched/util/SetIntervalLiveDataTest.kt | 146 ----------------- .../shared/data/job/ConferenceDataService.kt | 17 +- .../signin/AuthenticatedUserRegistration.kt | 11 +- .../FirebaseAuthStateUserDataSource.kt | 7 +- ...rQualifiers.kt => CoroutinesQualifiers.kt} | 4 + .../auth/ObserveUserAuthStateUseCase.kt | 6 +- .../shared/domain/internal/TaskScheduler.kt | 121 -------------- .../sessions/NotificationAlarmUpdater.kt | 18 +-- .../iosched/shared/fcm/FcmTokenUpdater.kt | 14 +- .../notifications/AlarmBroadcastReceiver.kt | 13 +- .../shared/util/IntervalMediatorLiveData.kt | 73 +++++++++ .../shared/util/SetIntervalLiveData.kt | 147 ------------------ .../data/ConferenceDataRepositoryTest.kt | 5 - .../signin/ObserveUserAuthStateUseCaseTest.kt | 2 + ...efaultSessionAndUserEventRepositoryTest.kt | 5 - .../feed/GetConferenceStateUseCaseTest.kt | 4 - .../feed/LoadAnnouncementsUseCaseTest.kt | 4 - .../feed/LoadCurrentMomentUseCaseTest.kt | 4 - .../LoadPinnedSessionsJsonUseCaseTest.kt | 4 - .../LoadScheduleUserSessionsUseCaseTest.kt | 4 - ...adStarredAndReservedSessionsUseCaseTest.kt | 4 - .../domain/users/FeedbackUseCaseTest.kt | 5 - .../users/ReservationActionUseCaseTest.kt | 5 - .../iosched/shared/util/SyncExecutorRule.kt | 37 ----- .../iosched/test/util/SyncTaskExecutorRule.kt | 37 ----- .../iosched/test/data/MainCoroutineRule.kt | 6 + 59 files changed, 384 insertions(+), 905 deletions(-) delete mode 100644 mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/SyncTaskExecutorRule.kt delete mode 100644 mobile/src/test/java/com/google/samples/apps/iosched/test/util/SyncTaskExecutorRule.kt delete mode 100644 mobile/src/test/java/com/google/samples/apps/iosched/test/util/time/FakeIntervalMapperRule.kt create mode 100644 mobile/src/test/java/com/google/samples/apps/iosched/util/IntervalMediatorLiveDataTest.kt delete mode 100644 mobile/src/test/java/com/google/samples/apps/iosched/util/SetIntervalLiveDataTest.kt rename shared/src/main/java/com/google/samples/apps/iosched/shared/di/{DispatcherQualifiers.kt => CoroutinesQualifiers.kt} (92%) delete mode 100644 shared/src/main/java/com/google/samples/apps/iosched/shared/domain/internal/TaskScheduler.kt create mode 100644 shared/src/main/java/com/google/samples/apps/iosched/shared/util/IntervalMediatorLiveData.kt delete mode 100644 shared/src/main/java/com/google/samples/apps/iosched/shared/util/SetIntervalLiveData.kt delete mode 100644 shared/src/test/java/com/google/samples/apps/iosched/shared/util/SyncExecutorRule.kt delete mode 100644 shared/src/test/java/com/google/samples/apps/iosched/test/util/SyncTaskExecutorRule.kt diff --git a/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/SyncTaskExecutorRule.kt b/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/SyncTaskExecutorRule.kt deleted file mode 100644 index 9482b73a63..0000000000 --- a/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/SyncTaskExecutorRule.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.samples.apps.iosched.tests - -import com.google.samples.apps.iosched.shared.domain.internal.DefaultScheduler -import com.google.samples.apps.iosched.shared.domain.internal.SyncScheduler -import org.junit.rules.TestWatcher -import org.junit.runner.Description - -/** - * Rule to be used in tests that sets a synchronous task scheduler used to avoid race conditions. - */ -class SyncTaskExecutorRule : TestWatcher() { - override fun starting(description: Description?) { - super.starting(description) - DefaultScheduler.setDelegate(SyncScheduler) - } - - override fun finished(description: Description?) { - super.finished(description) - DefaultScheduler.setDelegate(null) - } -} diff --git a/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/AgendaTest.kt b/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/AgendaTest.kt index e2a170bcc7..34bf23f0fa 100644 --- a/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/AgendaTest.kt +++ b/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/AgendaTest.kt @@ -27,7 +27,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.samples.apps.iosched.R import com.google.samples.apps.iosched.di.CoroutinesModule import com.google.samples.apps.iosched.tests.SetPreferencesRule -import com.google.samples.apps.iosched.tests.SyncTaskExecutorRule import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.UninstallModules @@ -45,10 +44,6 @@ class AgendaTest { @get:Rule(order = 0) var hiltRule = HiltAndroidRule(this) - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule(order = 1) - var syncTaskExecutorRule = SyncTaskExecutorRule() - // Sets the preferences so no welcome screens are shown @get:Rule(order = 1) var preferencesRule = SetPreferencesRule() diff --git a/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/CodelabTest.kt b/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/CodelabTest.kt index 5938fa65ed..5bab1eb681 100644 --- a/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/CodelabTest.kt +++ b/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/CodelabTest.kt @@ -27,7 +27,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.samples.apps.iosched.R import com.google.samples.apps.iosched.di.CoroutinesModule import com.google.samples.apps.iosched.tests.SetPreferencesRule -import com.google.samples.apps.iosched.tests.SyncTaskExecutorRule import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.UninstallModules @@ -45,10 +44,6 @@ class CodelabTest { @get:Rule(order = 0) var hiltRule = HiltAndroidRule(this) - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule(order = 1) - var syncTaskExecutorRule = SyncTaskExecutorRule() - // Sets the preferences so no welcome screens are shown @get:Rule(order = 1) var preferencesRule = SetPreferencesRule() diff --git a/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/HomeTest.kt b/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/HomeTest.kt index 2ee00a75d6..181734ec11 100644 --- a/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/HomeTest.kt +++ b/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/HomeTest.kt @@ -30,7 +30,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.samples.apps.iosched.R import com.google.samples.apps.iosched.di.CoroutinesModule import com.google.samples.apps.iosched.tests.SetPreferencesRule -import com.google.samples.apps.iosched.tests.SyncTaskExecutorRule import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.UninstallModules @@ -48,10 +47,6 @@ class HomeTest { @get:Rule(order = 0) var hiltRule = HiltAndroidRule(this) - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule(order = 1) - var syncTaskExecutorRule = SyncTaskExecutorRule() - // Sets the preferences so no welcome screens are shown @get:Rule(order = 1) var preferencesRule = SetPreferencesRule() diff --git a/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/InfoTest.kt b/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/InfoTest.kt index ec2fcf5b78..2a0169365a 100644 --- a/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/InfoTest.kt +++ b/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/InfoTest.kt @@ -31,7 +31,6 @@ import com.google.samples.apps.iosched.R import com.google.samples.apps.iosched.R.id import com.google.samples.apps.iosched.di.CoroutinesModule import com.google.samples.apps.iosched.tests.SetPreferencesRule -import com.google.samples.apps.iosched.tests.SyncTaskExecutorRule import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.UninstallModules @@ -52,10 +51,6 @@ class InfoTest { @get:Rule(order = 0) var hiltRule = HiltAndroidRule(this) - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule(order = 1) - var syncTaskExecutorRule = SyncTaskExecutorRule() - // Sets the preferences so no welcome screens are shown @get:Rule(order = 1) var preferencesRule = SetPreferencesRule() diff --git a/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/MapTest.kt b/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/MapTest.kt index 9f382dddde..07589f6466 100644 --- a/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/MapTest.kt +++ b/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/MapTest.kt @@ -27,7 +27,6 @@ import com.google.samples.apps.iosched.R import com.google.samples.apps.iosched.R.id import com.google.samples.apps.iosched.di.CoroutinesModule import com.google.samples.apps.iosched.tests.SetPreferencesRule -import com.google.samples.apps.iosched.tests.SyncTaskExecutorRule import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.UninstallModules @@ -48,10 +47,6 @@ class MapTest { @get:Rule(order = 0) var hiltRule = HiltAndroidRule(this) - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule(order = 1) - var syncTaskExecutorRule = SyncTaskExecutorRule() - // Sets the preferences so no welcome screens are shown @get:Rule(order = 1) var preferencesRule = SetPreferencesRule() diff --git a/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/ScheduleTest.kt b/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/ScheduleTest.kt index 72dfe348ea..7127a8e211 100644 --- a/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/ScheduleTest.kt +++ b/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/ScheduleTest.kt @@ -30,7 +30,6 @@ import com.google.samples.apps.iosched.R import com.google.samples.apps.iosched.di.CoroutinesModule import com.google.samples.apps.iosched.tests.FixedTimeRule import com.google.samples.apps.iosched.tests.SetPreferencesRule -import com.google.samples.apps.iosched.tests.SyncTaskExecutorRule import com.google.samples.apps.iosched.ui.sessioncommon.SessionViewHolder import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest @@ -50,10 +49,6 @@ class ScheduleTest { @get:Rule(order = 0) var hiltRule = HiltAndroidRule(this) - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule(order = 1) - var syncTaskExecutorRule = SyncTaskExecutorRule() - // Sets the time to before the conference @get:Rule(order = 1) var timeProviderRule = FixedTimeRule() diff --git a/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/SessionDetailTest.kt b/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/SessionDetailTest.kt index a341dcf3d4..972b55ba68 100644 --- a/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/SessionDetailTest.kt +++ b/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/SessionDetailTest.kt @@ -31,7 +31,6 @@ import com.google.samples.apps.iosched.di.CoroutinesModule import com.google.samples.apps.iosched.shared.data.FakeConferenceDataSource import com.google.samples.apps.iosched.tests.FixedTimeRule import com.google.samples.apps.iosched.tests.SetPreferencesRule -import com.google.samples.apps.iosched.tests.SyncTaskExecutorRule import com.google.samples.apps.iosched.ui.sessiondetail.SessionDetailActivity import com.google.samples.apps.iosched.ui.sessiondetail.SessionDetailViewHolder import dagger.hilt.android.testing.HiltAndroidRule @@ -66,10 +65,6 @@ class SessionDetailTest { @get:Rule(order = 0) var hiltRule = HiltAndroidRule(this) - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule(order = 1) - var syncTaskExecutorRule = SyncTaskExecutorRule() - // Sets the time to before the conference @get:Rule(order = 1) var timeProviderRule = FixedTimeRule() diff --git a/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/SettingsTest.kt b/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/SettingsTest.kt index 21f754f433..6ad88f1c24 100644 --- a/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/SettingsTest.kt +++ b/mobile/src/androidTest/java/com/google/samples/apps/iosched/tests/ui/SettingsTest.kt @@ -30,7 +30,6 @@ import com.google.samples.apps.iosched.R import com.google.samples.apps.iosched.R.id import com.google.samples.apps.iosched.di.CoroutinesModule import com.google.samples.apps.iosched.tests.SetPreferencesRule -import com.google.samples.apps.iosched.tests.SyncTaskExecutorRule import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.UninstallModules @@ -51,10 +50,6 @@ class SettingsTest { @get:Rule(order = 0) var hiltRule = HiltAndroidRule(this) - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule(order = 1) - var syncTaskExecutorRule = SyncTaskExecutorRule() - // Sets the preferences so no welcome screens are shown @get:Rule(order = 1) var preferencesRule = SetPreferencesRule() diff --git a/mobile/src/debugRelease/java/com/google/samples/apps/iosched/di/SignInModule.kt b/mobile/src/debugRelease/java/com/google/samples/apps/iosched/di/SignInModule.kt index 8063d7ed40..1ce14bc459 100644 --- a/mobile/src/debugRelease/java/com/google/samples/apps/iosched/di/SignInModule.kt +++ b/mobile/src/debugRelease/java/com/google/samples/apps/iosched/di/SignInModule.kt @@ -25,6 +25,9 @@ import com.google.samples.apps.iosched.shared.data.signin.datasources.AuthStateU import com.google.samples.apps.iosched.shared.data.signin.datasources.FirebaseAuthStateUserDataSource import com.google.samples.apps.iosched.shared.data.signin.datasources.FirestoreRegisteredUserDataSource import com.google.samples.apps.iosched.shared.data.signin.datasources.RegisteredUserDataSource +import com.google.samples.apps.iosched.shared.di.ApplicationScope +import com.google.samples.apps.iosched.shared.di.IoDispatcher +import com.google.samples.apps.iosched.shared.di.MainDispatcher import com.google.samples.apps.iosched.shared.domain.sessions.NotificationAlarmUpdater import com.google.samples.apps.iosched.shared.fcm.FcmTokenUpdater import com.google.samples.apps.iosched.util.signin.FirebaseAuthSignInHandler @@ -33,13 +36,18 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.components.ApplicationComponent +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope import javax.inject.Singleton @InstallIn(ApplicationComponent::class) @Module internal class SignInModule { + @Provides - fun provideSignInHandler(): SignInHandler = FirebaseAuthSignInHandler() + fun provideSignInHandler( + @ApplicationScope applicationScope: CoroutineScope + ): SignInHandler = FirebaseAuthSignInHandler(applicationScope) @Singleton @Provides @@ -54,13 +62,17 @@ internal class SignInModule { fun provideAuthStateUserDataSource( firebase: FirebaseAuth, firestore: FirebaseFirestore, - notificationAlarmUpdater: NotificationAlarmUpdater + notificationAlarmUpdater: NotificationAlarmUpdater, + @ApplicationScope applicationScope: CoroutineScope, + @IoDispatcher ioDispatcher: CoroutineDispatcher, + @MainDispatcher mainDispatcher: CoroutineDispatcher ): AuthStateUserDataSource { return FirebaseAuthStateUserDataSource( firebase, - FcmTokenUpdater(firestore), - notificationAlarmUpdater + FcmTokenUpdater(applicationScope, mainDispatcher, firestore), + notificationAlarmUpdater, + ioDispatcher ) } diff --git a/mobile/src/main/java/com/google/samples/apps/iosched/di/AppModule.kt b/mobile/src/main/java/com/google/samples/apps/iosched/di/AppModule.kt index afd49d2e16..612881a0f1 100644 --- a/mobile/src/main/java/com/google/samples/apps/iosched/di/AppModule.kt +++ b/mobile/src/main/java/com/google/samples/apps/iosched/di/AppModule.kt @@ -29,6 +29,8 @@ import com.google.samples.apps.iosched.shared.data.config.AppConfigDataSource import com.google.samples.apps.iosched.shared.data.db.AppDatabase import com.google.samples.apps.iosched.shared.data.prefs.PreferenceStorage import com.google.samples.apps.iosched.shared.data.prefs.SharedPreferenceStorage +import com.google.samples.apps.iosched.shared.di.ApplicationScope +import com.google.samples.apps.iosched.shared.di.DefaultDispatcher import com.google.samples.apps.iosched.shared.di.MainThreadHandler import com.google.samples.apps.iosched.shared.domain.internal.IOSchedHandler import com.google.samples.apps.iosched.shared.domain.internal.IOSchedMainHandler @@ -39,6 +41,9 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.components.ApplicationComponent import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob import javax.inject.Singleton /** @@ -63,13 +68,20 @@ class AppModule { @Provides fun provideConnectivityManager(@ApplicationContext context: Context): ConnectivityManager = context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) - as ConnectivityManager + as ConnectivityManager @Provides fun provideClipboardManager(@ApplicationContext context: Context): ClipboardManager = context.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + @ApplicationScope + @Singleton + @Provides + fun providesApplicationScope( + @DefaultDispatcher defaultDispatcher: CoroutineDispatcher + ): CoroutineScope = CoroutineScope(SupervisorJob() + defaultDispatcher) + @Singleton @Provides @MainThreadHandler @@ -78,15 +90,17 @@ class AppModule { @Singleton @Provides fun provideAnalyticsHelper( + @ApplicationScope applicationScope: CoroutineScope, @ApplicationContext context: Context, signInDelegate: SignInViewModelDelegate, preferenceStorage: PreferenceStorage - ): AnalyticsHelper = FirebaseAnalyticsHelper(context, signInDelegate, preferenceStorage) + ): AnalyticsHelper = + FirebaseAnalyticsHelper(applicationScope, context, signInDelegate, preferenceStorage) @Singleton @Provides fun provideAgendaRepository(appConfigDataSource: AppConfigDataSource): AgendaRepository = - DefaultAgendaRepository(appConfigDataSource) + DefaultAgendaRepository(appConfigDataSource) @Singleton @Provides diff --git a/mobile/src/main/java/com/google/samples/apps/iosched/ui/sessioncommon/EventActionsViewModelDelegate.kt b/mobile/src/main/java/com/google/samples/apps/iosched/ui/sessioncommon/EventActionsViewModelDelegate.kt index f0a9041b9a..272ef2e986 100644 --- a/mobile/src/main/java/com/google/samples/apps/iosched/ui/sessioncommon/EventActionsViewModelDelegate.kt +++ b/mobile/src/main/java/com/google/samples/apps/iosched/ui/sessioncommon/EventActionsViewModelDelegate.kt @@ -22,6 +22,7 @@ import androidx.lifecycle.MutableLiveData import com.google.samples.apps.iosched.R import com.google.samples.apps.iosched.model.SessionId import com.google.samples.apps.iosched.model.userdata.UserSession +import com.google.samples.apps.iosched.shared.di.ApplicationScope import com.google.samples.apps.iosched.shared.di.MainDispatcher import com.google.samples.apps.iosched.shared.domain.users.StarEventAndNotifyUseCase import com.google.samples.apps.iosched.shared.domain.users.StarEventParameter @@ -32,11 +33,10 @@ import com.google.samples.apps.iosched.ui.messages.SnackbarMessageManager import com.google.samples.apps.iosched.ui.signin.SignInViewModelDelegate import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch +import timber.log.Timber import java.util.UUID import javax.inject.Inject -import timber.log.Timber /** * A delegate providing common functionality for displaying a list of events and responding to @@ -52,11 +52,10 @@ class DefaultEventActionsViewModelDelegate @Inject constructor( signInViewModelDelegate: SignInViewModelDelegate, private val starEventUseCase: StarEventAndNotifyUseCase, private val snackbarMessageManager: SnackbarMessageManager, + @ApplicationScope private val externalScope: CoroutineScope, @MainDispatcher private val mainDispatcher: CoroutineDispatcher ) : EventActionsViewModelDelegate, SignInViewModelDelegate by signInViewModelDelegate { - private val delegateScope = CoroutineScope(mainDispatcher + SupervisorJob()) - private val _navigateToEventAction = MutableLiveData>() override val navigateToEventAction: LiveData> get() = _navigateToEventAction @@ -95,7 +94,7 @@ class DefaultEventActionsViewModelDelegate @Inject constructor( ) ) - delegateScope.launch { + externalScope.launch(mainDispatcher) { getUserId()?.let { val result = starEventUseCase( StarEventParameter( diff --git a/mobile/src/main/java/com/google/samples/apps/iosched/ui/sessioncommon/EventActionsViewModelDelegateModule.kt b/mobile/src/main/java/com/google/samples/apps/iosched/ui/sessioncommon/EventActionsViewModelDelegateModule.kt index 5cb806be12..1c5de057fb 100644 --- a/mobile/src/main/java/com/google/samples/apps/iosched/ui/sessioncommon/EventActionsViewModelDelegateModule.kt +++ b/mobile/src/main/java/com/google/samples/apps/iosched/ui/sessioncommon/EventActionsViewModelDelegateModule.kt @@ -16,6 +16,7 @@ package com.google.samples.apps.iosched.ui.sessioncommon +import com.google.samples.apps.iosched.shared.di.ApplicationScope import com.google.samples.apps.iosched.shared.di.MainDispatcher import com.google.samples.apps.iosched.shared.domain.users.StarEventAndNotifyUseCase import com.google.samples.apps.iosched.ui.messages.SnackbarMessageManager @@ -25,6 +26,7 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.components.ActivityComponent import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope /** * Provides a default implementation of [EventActionsViewModelDelegate]. @@ -38,12 +40,14 @@ internal class EventActionsViewModelDelegateModule { signInViewModelDelegate: SignInViewModelDelegate, starEventUseCase: StarEventAndNotifyUseCase, snackbarMessageManager: SnackbarMessageManager, + @ApplicationScope applicationScope: CoroutineScope, @MainDispatcher mainDispatcher: CoroutineDispatcher ): EventActionsViewModelDelegate { return DefaultEventActionsViewModelDelegate( signInViewModelDelegate, starEventUseCase, snackbarMessageManager, + applicationScope, mainDispatcher ) } diff --git a/mobile/src/main/java/com/google/samples/apps/iosched/ui/sessiondetail/SessionDetailViewModel.kt b/mobile/src/main/java/com/google/samples/apps/iosched/ui/sessiondetail/SessionDetailViewModel.kt index 6bc71eae21..88f2a79121 100644 --- a/mobile/src/main/java/com/google/samples/apps/iosched/ui/sessiondetail/SessionDetailViewModel.kt +++ b/mobile/src/main/java/com/google/samples/apps/iosched/ui/sessiondetail/SessionDetailViewModel.kt @@ -32,6 +32,7 @@ import com.google.samples.apps.iosched.model.userdata.UserEvent import com.google.samples.apps.iosched.model.userdata.UserSession import com.google.samples.apps.iosched.shared.analytics.AnalyticsActions import com.google.samples.apps.iosched.shared.analytics.AnalyticsHelper +import com.google.samples.apps.iosched.shared.di.DefaultDispatcher import com.google.samples.apps.iosched.shared.di.ReservationEnabledFlag import com.google.samples.apps.iosched.shared.domain.sessions.LoadUserSessionUseCase import com.google.samples.apps.iosched.shared.domain.sessions.LoadUserSessionsUseCase @@ -50,8 +51,8 @@ import com.google.samples.apps.iosched.shared.result.Result.Success import com.google.samples.apps.iosched.shared.result.data import com.google.samples.apps.iosched.shared.result.successOr import com.google.samples.apps.iosched.shared.time.TimeProvider +import com.google.samples.apps.iosched.shared.util.IntervalMediatorLiveData import com.google.samples.apps.iosched.shared.util.NetworkUtils -import com.google.samples.apps.iosched.shared.util.SetIntervalLiveData.DefaultIntervalMapper import com.google.samples.apps.iosched.shared.util.TimeUtils import com.google.samples.apps.iosched.shared.util.cancelIfActive import com.google.samples.apps.iosched.shared.util.map @@ -63,6 +64,7 @@ import com.google.samples.apps.iosched.ui.sessioncommon.EventActions import com.google.samples.apps.iosched.ui.sessioncommon.stringRes import com.google.samples.apps.iosched.ui.signin.SignInViewModelDelegate import com.google.samples.apps.iosched.util.combine +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collect @@ -92,7 +94,8 @@ class SessionDetailViewModel @ViewModelInject constructor( timeProvider: TimeProvider, private val networkUtils: NetworkUtils, private val analyticsHelper: AnalyticsHelper, - @ReservationEnabledFlag val isReservationEnabledByRemoteConfig: Boolean + @ReservationEnabledFlag val isReservationEnabledByRemoteConfig: Boolean, + @DefaultDispatcher defaultDispatcher: CoroutineDispatcher ) : ViewModel(), SessionDetailEventListener, EventActions, SignInViewModelDelegate by signInViewModelDelegate { @@ -125,27 +128,31 @@ class SessionDetailViewModel @ViewModelInject constructor( private val _userEvent = MediatorLiveData() val userEvent: LiveData = _userEvent - val showFeedbackButton: LiveData = userEvent.combine(session) { - userEvent, currentSession -> - isSignedIn() && + val showFeedbackButton: LiveData = + userEvent.combine(session) { userEvent, currentSession -> + isSignedIn() && !userEvent.isReviewed && currentSession.type == SessionType.SESSION && TimeUtils.getSessionState(currentSession, ZonedDateTime.now()) == TimeUtils.SessionRelativeTimeState.AFTER - } - // Updates periodically with a special [IntervalLiveData] - val timeUntilStart: LiveData = - DefaultIntervalMapper.mapAtInterval(session, TEN_SECONDS) { session -> - session?.startTime?.let { startTime -> - val duration = Duration.between(timeProvider.now(), startTime) - when (duration.toMinutes()) { - in 1..5 -> duration - else -> null - } + } + + // Updates periodically with a special [IntervalMediatorLiveData] + val timeUntilStart = IntervalMediatorLiveData( + source = session, dispatcher = defaultDispatcher, intervalMs = TEN_SECONDS + ) { session -> + session?.startTime?.let { startTime -> + val duration = Duration.between(timeProvider.now(), startTime) + when (duration.toMinutes()) { + in 1..5 -> duration + else -> null } } - val isReservationDeniedByCutoff: LiveData = - DefaultIntervalMapper.mapAtInterval(session, SIXTY_SECONDS) { session -> + } + val isReservationDeniedByCutoff = + IntervalMediatorLiveData( + source = session, dispatcher = defaultDispatcher, intervalMs = SIXTY_SECONDS + ) { session -> session?.startTime?.let { startTime -> // Only allow reservations if the sessions starts more than an hour from now Duration.between(timeProvider.now(), startTime).toMinutes() <= 60 @@ -167,12 +174,12 @@ class SessionDetailViewModel @ViewModelInject constructor( emit(getTimeZoneUseCase(Unit).successOr(true)) } val timeZoneId: LiveData = showInConferenceTimeZone.map { inConferenceTimeZone -> - if (inConferenceTimeZone) { - TimeUtils.CONFERENCE_TIMEZONE - } else { - ZoneId.systemDefault() - } + if (inConferenceTimeZone) { + TimeUtils.CONFERENCE_TIMEZONE + } else { + ZoneId.systemDefault() } + } private val _navigateToRemoveReservationDialogAction = MutableLiveData>() @@ -364,12 +371,14 @@ class SessionDetailViewModel @ViewModelInject constructor( val userSession = UserSession(sessionSnapshot, userEventSnapshot) viewModelScope.launch { - val result = reservationActionUseCase(ReservationRequestParameters( - userId, - sessionSnapshot.id, - RequestAction(), - userSession - )) + val result = reservationActionUseCase( + ReservationRequestParameters( + userId, + sessionSnapshot.id, + RequestAction(), + userSession + ) + ) when (result) { is Success -> reservationActionResult.value = result.data is Error -> { diff --git a/mobile/src/main/java/com/google/samples/apps/iosched/util/FirebaseAnalyticsHelper.kt b/mobile/src/main/java/com/google/samples/apps/iosched/util/FirebaseAnalyticsHelper.kt index e809520aee..8384d50c67 100644 --- a/mobile/src/main/java/com/google/samples/apps/iosched/util/FirebaseAnalyticsHelper.kt +++ b/mobile/src/main/java/com/google/samples/apps/iosched/util/FirebaseAnalyticsHelper.kt @@ -28,14 +28,17 @@ import com.google.samples.apps.iosched.shared.analytics.AnalyticsActions import com.google.samples.apps.iosched.shared.analytics.AnalyticsHelper import com.google.samples.apps.iosched.shared.data.prefs.PreferenceStorage import com.google.samples.apps.iosched.shared.data.prefs.SharedPreferenceStorage -import com.google.samples.apps.iosched.shared.domain.internal.DefaultScheduler +import com.google.samples.apps.iosched.shared.di.ApplicationScope import com.google.samples.apps.iosched.ui.signin.SignInViewModelDelegate +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import timber.log.Timber /** * Firebase Analytics implementation of AnalyticsHelper */ class FirebaseAnalyticsHelper( + @ApplicationScope private val externalScope: CoroutineScope, context: Context, signInViewModelDelegate: SignInViewModelDelegate, preferenceStorage: PreferenceStorage @@ -60,8 +63,7 @@ class FirebaseAnalyticsHelper( * (possible except on first run), initialize analytics Immediately. */ init { - - DefaultScheduler.execute { // Prevent access to preferences on main thread + externalScope.launch { // Prevent access to preferences on main thread analyticsEnabled = preferenceStorage.sendUsageStatistics } @@ -131,7 +133,7 @@ class FirebaseAnalyticsHelper( } } - DefaultScheduler.execute { // Prevent access to preferences on main thread + externalScope.launch { // Prevent access to preferences on main thread SharedPreferenceStorage(context).registerOnPreferenceChangeListener(listener) } prefListener = listener diff --git a/mobile/src/main/java/com/google/samples/apps/iosched/util/signin/SignInHandler.kt b/mobile/src/main/java/com/google/samples/apps/iosched/util/signin/SignInHandler.kt index 372c009a73..9440c7b809 100644 --- a/mobile/src/main/java/com/google/samples/apps/iosched/util/signin/SignInHandler.kt +++ b/mobile/src/main/java/com/google/samples/apps/iosched/util/signin/SignInHandler.kt @@ -24,7 +24,8 @@ import androidx.lifecycle.MutableLiveData import com.firebase.ui.auth.AuthUI import com.firebase.ui.auth.IdpResponse import com.google.android.gms.auth.api.signin.GoogleSignInOptions -import com.google.samples.apps.iosched.shared.domain.internal.DefaultScheduler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch /** * Element in the presentation layer that interacts with the Auth provider (Firebase in this case). @@ -43,7 +44,7 @@ interface SignInHandler { /** * Implementation of [SignInHandler] that interacts with Firebase Auth. */ -class FirebaseAuthSignInHandler : SignInHandler { +class FirebaseAuthSignInHandler(private val externalScope: CoroutineScope) : SignInHandler { /** * Request a sign in intent. @@ -55,7 +56,7 @@ class FirebaseAuthSignInHandler : SignInHandler { val result = MutableLiveData() // Run on background because AuthUI does I/O operations. - DefaultScheduler.execute { + externalScope.launch { // this is mutable because FirebaseUI requires it be mutable val providers = mutableListOf( AuthUI.IdpConfig.GoogleBuilder().setSignInOptions( diff --git a/mobile/src/test/java/com/google/samples/apps/iosched/test/util/SyncTaskExecutorRule.kt b/mobile/src/test/java/com/google/samples/apps/iosched/test/util/SyncTaskExecutorRule.kt deleted file mode 100644 index 724911cca8..0000000000 --- a/mobile/src/test/java/com/google/samples/apps/iosched/test/util/SyncTaskExecutorRule.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.samples.apps.iosched.test.util - -import com.google.samples.apps.iosched.shared.domain.internal.DefaultScheduler -import com.google.samples.apps.iosched.shared.domain.internal.SyncScheduler -import org.junit.rules.TestWatcher -import org.junit.runner.Description - -/** - * Rule to be used in tests that sets a synchronous task scheduler used to avoid race conditions. - */ -class SyncTaskExecutorRule : TestWatcher() { - override fun starting(description: Description?) { - super.starting(description) - DefaultScheduler.setDelegate(SyncScheduler) - } - - override fun finished(description: Description?) { - super.finished(description) - SyncScheduler.clearScheduledPostdelayedTasks() - DefaultScheduler.setDelegate(null) - } - - /** - * Force the (previously deferred) execution of all [Scheduler.postDelayedToMainThread] tasks. - * - * In tests, postDelayed is not eagerly executed, allowing test code to test self-scheduling - * tasks. - * - * This will *not* run any tasks that are scheduled as a result of running the current delayed - * tasks. If you need to test that more tasks were scheduled, call this function again. - */ - fun runAllScheduledPostDelayedTasks() { - SyncScheduler.runAllScheduledPostDelayedTasks() - } -} diff --git a/mobile/src/test/java/com/google/samples/apps/iosched/test/util/time/FakeIntervalMapperRule.kt b/mobile/src/test/java/com/google/samples/apps/iosched/test/util/time/FakeIntervalMapperRule.kt deleted file mode 100644 index 29d8265343..0000000000 --- a/mobile/src/test/java/com/google/samples/apps/iosched/test/util/time/FakeIntervalMapperRule.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.samples.apps.iosched.test.util.time - -import androidx.lifecycle.LiveData -import com.google.samples.apps.iosched.shared.util.SetIntervalLiveData -import com.google.samples.apps.iosched.shared.util.SetIntervalLiveData.DefaultIntervalMapper -import com.google.samples.apps.iosched.shared.util.SetIntervalLiveData.IntervalMapper -import org.junit.rules.TestWatcher -import org.junit.runner.Description - -/** - * Rule for [IntervalMapper] in tests to execute the map function immediately without waiting for - * the interval. - */ -class FakeIntervalMapperRule : TestWatcher() { - - override fun starting(description: Description?) { - super.starting(description) - DefaultIntervalMapper.setDelegate(FakeIntervalMapper()) - } - - override fun finished(description: Description?) { - super.finished(description) - DefaultIntervalMapper.setDelegate(null) - } -} - -class FakeIntervalMapper : IntervalMapper { - override fun mapAtInterval( - source: LiveData

, - interval: Long, - map: (P?) -> R? - ): SetIntervalLiveData { - return FakeSetIntervalLiveData(source, interval, map) - } -} - -/** - * Fake implementation of [SetIntervalLiveData] where the map function is executed - * immediately. - */ -class FakeSetIntervalLiveData( - source: LiveData

, - intervalMs: Long, - map: (P?) -> R? -) : SetIntervalLiveData(source, intervalMs, map) { - init { - value = map(source.value) - } -} diff --git a/mobile/src/test/java/com/google/samples/apps/iosched/ui/LaunchViewModelTest.kt b/mobile/src/test/java/com/google/samples/apps/iosched/ui/LaunchViewModelTest.kt index 0351fba999..233403d754 100644 --- a/mobile/src/test/java/com/google/samples/apps/iosched/ui/LaunchViewModelTest.kt +++ b/mobile/src/test/java/com/google/samples/apps/iosched/ui/LaunchViewModelTest.kt @@ -23,7 +23,6 @@ import com.google.samples.apps.iosched.androidtest.util.LiveDataTestUtil import com.google.samples.apps.iosched.shared.domain.prefs.OnboardingCompletedUseCase import com.google.samples.apps.iosched.test.data.MainCoroutineRule import com.google.samples.apps.iosched.test.data.runBlockingTest -import com.google.samples.apps.iosched.test.util.SyncTaskExecutorRule import com.google.samples.apps.iosched.test.util.fakes.FakePreferenceStorage import com.google.samples.apps.iosched.ui.LaunchDestination.MAIN_ACTIVITY import com.google.samples.apps.iosched.ui.LaunchDestination.ONBOARDING @@ -40,10 +39,6 @@ class LaunchViewModelTest { @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule - var syncTaskExecutorRule = SyncTaskExecutorRule() - // Overrides Dispatchers.Main used in Coroutines @get:Rule var coroutineRule = MainCoroutineRule() diff --git a/mobile/src/test/java/com/google/samples/apps/iosched/ui/MainActivityViewModelTest.kt b/mobile/src/test/java/com/google/samples/apps/iosched/ui/MainActivityViewModelTest.kt index 1a9bbb19bd..1e6acd7bfd 100644 --- a/mobile/src/test/java/com/google/samples/apps/iosched/ui/MainActivityViewModelTest.kt +++ b/mobile/src/test/java/com/google/samples/apps/iosched/ui/MainActivityViewModelTest.kt @@ -29,7 +29,6 @@ import com.google.samples.apps.iosched.shared.domain.ar.LoadArDebugFlagUseCase import com.google.samples.apps.iosched.shared.domain.sessions.LoadPinnedSessionsJsonUseCase import com.google.samples.apps.iosched.test.data.MainCoroutineRule import com.google.samples.apps.iosched.test.data.runBlockingTest -import com.google.samples.apps.iosched.test.util.SyncTaskExecutorRule import com.google.samples.apps.iosched.test.util.fakes.FakeSignInViewModelDelegate import com.google.samples.apps.iosched.test.util.fakes.FakeThemedActivityDelegate import com.google.samples.apps.iosched.ui.schedule.TestUserEventDataSource @@ -47,10 +46,6 @@ class MainActivityViewModelTest { @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule - var syncTaskExecutorRule = SyncTaskExecutorRule() - // Overrides Dispatchers.Main used in Coroutines @get:Rule var coroutineRule = MainCoroutineRule() diff --git a/mobile/src/test/java/com/google/samples/apps/iosched/ui/agenda/AgendaViewModelTest.kt b/mobile/src/test/java/com/google/samples/apps/iosched/ui/agenda/AgendaViewModelTest.kt index 0bb430ac61..9749965a4e 100644 --- a/mobile/src/test/java/com/google/samples/apps/iosched/ui/agenda/AgendaViewModelTest.kt +++ b/mobile/src/test/java/com/google/samples/apps/iosched/ui/agenda/AgendaViewModelTest.kt @@ -24,7 +24,6 @@ import com.google.samples.apps.iosched.shared.domain.agenda.LoadAgendaUseCase import com.google.samples.apps.iosched.shared.domain.settings.GetTimeZoneUseCase import com.google.samples.apps.iosched.test.data.MainCoroutineRule import com.google.samples.apps.iosched.test.data.TestData -import com.google.samples.apps.iosched.test.util.SyncTaskExecutorRule import com.google.samples.apps.iosched.test.util.fakes.FakePreferenceStorage import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.equalTo as isEqualTo @@ -40,10 +39,6 @@ class AgendaViewModelTest { @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - // Executes tasks in a synchronous [TaskScheduler] TODO(COROUTINES): Remove - @get:Rule - var syncTaskExecutorRule = SyncTaskExecutorRule() - // Overrides Dispatchers.Main used in Coroutines @get:Rule var coroutineRule = MainCoroutineRule() diff --git a/mobile/src/test/java/com/google/samples/apps/iosched/ui/feed/FeedViewModelTest.kt b/mobile/src/test/java/com/google/samples/apps/iosched/ui/feed/FeedViewModelTest.kt index 330c7932e2..6285987b98 100644 --- a/mobile/src/test/java/com/google/samples/apps/iosched/ui/feed/FeedViewModelTest.kt +++ b/mobile/src/test/java/com/google/samples/apps/iosched/ui/feed/FeedViewModelTest.kt @@ -34,7 +34,6 @@ import com.google.samples.apps.iosched.shared.time.TimeProvider import com.google.samples.apps.iosched.test.data.MainCoroutineRule import com.google.samples.apps.iosched.test.data.TestData import com.google.samples.apps.iosched.test.data.runBlockingTest -import com.google.samples.apps.iosched.test.util.SyncTaskExecutorRule import com.google.samples.apps.iosched.test.util.fakes.FakeAnalyticsHelper import com.google.samples.apps.iosched.test.util.fakes.FakePreferenceStorage import com.google.samples.apps.iosched.test.util.fakes.FakeSignInViewModelDelegate @@ -63,10 +62,6 @@ class FeedViewModelTest { @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule - var syncTaskExecutorRule = SyncTaskExecutorRule() - // Overrides Dispatchers.Main used in Coroutines @get:Rule var coroutineRule = MainCoroutineRule() diff --git a/mobile/src/test/java/com/google/samples/apps/iosched/ui/map/MapViewModelTest.kt b/mobile/src/test/java/com/google/samples/apps/iosched/ui/map/MapViewModelTest.kt index 0f108fa568..1d5b6c397f 100644 --- a/mobile/src/test/java/com/google/samples/apps/iosched/ui/map/MapViewModelTest.kt +++ b/mobile/src/test/java/com/google/samples/apps/iosched/ui/map/MapViewModelTest.kt @@ -23,7 +23,6 @@ import com.google.samples.apps.iosched.androidtest.util.LiveDataTestUtil import com.google.samples.apps.iosched.shared.domain.prefs.MyLocationOptedInUseCase import com.google.samples.apps.iosched.shared.domain.prefs.OptIntoMyLocationUseCase import com.google.samples.apps.iosched.test.data.MainCoroutineRule -import com.google.samples.apps.iosched.test.util.SyncTaskExecutorRule import com.google.samples.apps.iosched.test.util.fakes.FakeAnalyticsHelper import com.google.samples.apps.iosched.test.util.fakes.FakePreferenceStorage import com.google.samples.apps.iosched.test.util.fakes.FakeSignInViewModelDelegate @@ -44,9 +43,6 @@ class MapViewModelTest { // Executes tasks in the Architecture Components in the same thread @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule var syncTaskExecutorRule = SyncTaskExecutorRule() - @get:Rule var coroutineRule = MainCoroutineRule() private val storage = FakePreferenceStorage() diff --git a/mobile/src/test/java/com/google/samples/apps/iosched/ui/messages/SnackbarMessageManagerTest.kt b/mobile/src/test/java/com/google/samples/apps/iosched/ui/messages/SnackbarMessageManagerTest.kt index d104a766d2..9747a0a4f2 100644 --- a/mobile/src/test/java/com/google/samples/apps/iosched/ui/messages/SnackbarMessageManagerTest.kt +++ b/mobile/src/test/java/com/google/samples/apps/iosched/ui/messages/SnackbarMessageManagerTest.kt @@ -21,7 +21,6 @@ import androidx.lifecycle.LiveData import com.google.samples.apps.iosched.R import com.google.samples.apps.iosched.androidtest.util.LiveDataTestUtil import com.google.samples.apps.iosched.test.data.TestData -import com.google.samples.apps.iosched.test.util.SyncTaskExecutorRule import com.google.samples.apps.iosched.test.util.fakes.FakePreferenceStorage import com.google.samples.apps.iosched.ui.SnackbarMessage import com.nhaarman.mockito_kotlin.doReturn @@ -46,10 +45,6 @@ class SnackbarMessageManagerTest { @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule - var syncTaskExecutorRule = SyncTaskExecutorRule() - @Before fun createSubject() { snackbarMessageManager = SnackbarMessageManager(FakePreferenceStorage()) diff --git a/mobile/src/test/java/com/google/samples/apps/iosched/ui/onboarding/OnboardingViewModelTest.kt b/mobile/src/test/java/com/google/samples/apps/iosched/ui/onboarding/OnboardingViewModelTest.kt index cb2e9e137e..f2bde2f41c 100644 --- a/mobile/src/test/java/com/google/samples/apps/iosched/ui/onboarding/OnboardingViewModelTest.kt +++ b/mobile/src/test/java/com/google/samples/apps/iosched/ui/onboarding/OnboardingViewModelTest.kt @@ -23,7 +23,6 @@ import com.google.samples.apps.iosched.androidtest.util.LiveDataTestUtil import com.google.samples.apps.iosched.shared.data.prefs.PreferenceStorage import com.google.samples.apps.iosched.shared.domain.prefs.OnboardingCompleteActionUseCase import com.google.samples.apps.iosched.test.data.MainCoroutineRule -import com.google.samples.apps.iosched.test.util.SyncTaskExecutorRule import com.google.samples.apps.iosched.test.util.fakes.FakeSignInViewModelDelegate import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.verify @@ -41,10 +40,6 @@ class OnboardingViewModelTest { @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule - var syncTaskExecutorRule = SyncTaskExecutorRule() - // Overrides Dispatchers.Main used in Coroutines @get:Rule var coroutineRule = MainCoroutineRule() diff --git a/mobile/src/test/java/com/google/samples/apps/iosched/ui/schedule/MarkScheduleUiHintsShownUseCaseTest.kt b/mobile/src/test/java/com/google/samples/apps/iosched/ui/schedule/MarkScheduleUiHintsShownUseCaseTest.kt index 5baed60860..93a9d3ca4c 100644 --- a/mobile/src/test/java/com/google/samples/apps/iosched/ui/schedule/MarkScheduleUiHintsShownUseCaseTest.kt +++ b/mobile/src/test/java/com/google/samples/apps/iosched/ui/schedule/MarkScheduleUiHintsShownUseCaseTest.kt @@ -21,7 +21,6 @@ import com.google.samples.apps.iosched.shared.data.prefs.PreferenceStorage import com.google.samples.apps.iosched.shared.domain.prefs.MarkScheduleUiHintsShownUseCase import com.google.samples.apps.iosched.test.data.MainCoroutineRule import com.google.samples.apps.iosched.test.data.runBlockingTest -import com.google.samples.apps.iosched.test.util.SyncTaskExecutorRule import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.verify import kotlinx.coroutines.test.TestCoroutineDispatcher @@ -37,10 +36,6 @@ class MarkScheduleUiHintsShownUseCaseTest { @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule - var syncTaskExecutorRule = SyncTaskExecutorRule() - // Overrides Dispatchers.Main used in Coroutines @get:Rule var coroutineRule = MainCoroutineRule() diff --git a/mobile/src/test/java/com/google/samples/apps/iosched/ui/schedule/ScheduleViewModelTest.kt b/mobile/src/test/java/com/google/samples/apps/iosched/ui/schedule/ScheduleViewModelTest.kt index bc7c9119cd..ee764e7369 100644 --- a/mobile/src/test/java/com/google/samples/apps/iosched/ui/schedule/ScheduleViewModelTest.kt +++ b/mobile/src/test/java/com/google/samples/apps/iosched/ui/schedule/ScheduleViewModelTest.kt @@ -49,10 +49,10 @@ import com.google.samples.apps.iosched.shared.domain.users.StarEventAndNotifyUse import com.google.samples.apps.iosched.shared.fcm.TopicSubscriber import com.google.samples.apps.iosched.shared.result.Event import com.google.samples.apps.iosched.shared.result.Result +import com.google.samples.apps.iosched.test.data.CoroutineScope import com.google.samples.apps.iosched.test.data.MainCoroutineRule import com.google.samples.apps.iosched.test.data.TestData import com.google.samples.apps.iosched.test.data.runBlockingTest -import com.google.samples.apps.iosched.test.util.SyncTaskExecutorRule import com.google.samples.apps.iosched.test.util.fakes.FakeAnalyticsHelper import com.google.samples.apps.iosched.test.util.fakes.FakeAppDatabase import com.google.samples.apps.iosched.test.util.fakes.FakePreferenceStorage @@ -65,6 +65,7 @@ import com.google.samples.apps.iosched.ui.signin.SignInViewModelDelegate import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.mock import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow @@ -92,10 +93,6 @@ class ScheduleViewModelTest { @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule - var syncTaskExecutorRule = SyncTaskExecutorRule() - // Overrides Dispatchers.Main used in Coroutines @get:Rule var coroutineRule = MainCoroutineRule() @@ -282,6 +279,7 @@ class ScheduleViewModelTest { val observableFirebaseUserUseCase = FakeObserveUserAuthStateUseCase( user = Result.Success(noFirebaseUser), isRegistered = Result.Success(false), + coroutineScope = coroutineRule.CoroutineScope(), coroutineDispatcher = testDispatcher ) val signInViewModelComponent = FirebaseSignInViewModelDelegate( @@ -311,6 +309,7 @@ class ScheduleViewModelTest { val observableFirebaseUserUseCase = FakeObserveUserAuthStateUseCase( user = Result.Success(mockUser), isRegistered = Result.Success(true), + coroutineScope = coroutineRule.CoroutineScope(), coroutineDispatcher = testDispatcher ) val signInViewModelComponent = FirebaseSignInViewModelDelegate( @@ -341,6 +340,7 @@ class ScheduleViewModelTest { val observableFirebaseUserUseCase = FakeObserveUserAuthStateUseCase( user = Result.Success(mockUser), isRegistered = Result.Success(false), + coroutineScope = coroutineRule.CoroutineScope(), coroutineDispatcher = testDispatcher ) val signInViewModelComponent = FirebaseSignInViewModelDelegate( @@ -514,11 +514,13 @@ class TestAuthStateUserDataSource( class FakeObserveUserAuthStateUseCase( user: Result, isRegistered: Result, + coroutineScope: CoroutineScope, coroutineDispatcher: CoroutineDispatcher ) : ObserveUserAuthStateUseCase( TestRegisteredUserDataSource(isRegistered), TestAuthStateUserDataSource(user), mock {}, + coroutineScope, coroutineDispatcher ) diff --git a/mobile/src/test/java/com/google/samples/apps/iosched/ui/sessiondetail/SessionDetailViewModelTest.kt b/mobile/src/test/java/com/google/samples/apps/iosched/ui/sessiondetail/SessionDetailViewModelTest.kt index 64205d5e14..e94def932f 100644 --- a/mobile/src/test/java/com/google/samples/apps/iosched/ui/sessiondetail/SessionDetailViewModelTest.kt +++ b/mobile/src/test/java/com/google/samples/apps/iosched/ui/sessiondetail/SessionDetailViewModelTest.kt @@ -40,17 +40,14 @@ import com.google.samples.apps.iosched.shared.result.Event import com.google.samples.apps.iosched.shared.time.DefaultTimeProvider import com.google.samples.apps.iosched.shared.time.TimeProvider import com.google.samples.apps.iosched.shared.util.NetworkUtils -import com.google.samples.apps.iosched.shared.util.SetIntervalLiveData import com.google.samples.apps.iosched.shared.util.TimeUtils.ConferenceDays import com.google.samples.apps.iosched.test.data.MainCoroutineRule import com.google.samples.apps.iosched.test.data.TestData import com.google.samples.apps.iosched.test.data.runBlockingTest -import com.google.samples.apps.iosched.test.util.SyncTaskExecutorRule import com.google.samples.apps.iosched.test.util.fakes.FakeAnalyticsHelper import com.google.samples.apps.iosched.test.util.fakes.FakePreferenceStorage import com.google.samples.apps.iosched.test.util.fakes.FakeSignInViewModelDelegate import com.google.samples.apps.iosched.test.util.fakes.FakeStarEventUseCase -import com.google.samples.apps.iosched.test.util.time.FakeIntervalMapperRule import com.google.samples.apps.iosched.test.util.time.FixedTimeExecutorRule import com.google.samples.apps.iosched.ui.SnackbarMessage import com.google.samples.apps.iosched.ui.messages.SnackbarMessageManager @@ -65,6 +62,7 @@ import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.validateMockitoUsage import com.nhaarman.mockito_kotlin.verify import com.nhaarman.mockito_kotlin.whenever +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.test.TestCoroutineDispatcher import org.hamcrest.CoreMatchers.`is` import org.hamcrest.CoreMatchers.equalTo @@ -79,6 +77,8 @@ import org.junit.Before import org.junit.Rule import org.junit.Test +private const val TEN_SECONDS = 10_000L + /** * Unit tests for the [SessionDetailViewModel]. */ @@ -87,15 +87,9 @@ class SessionDetailViewModelTest { // Executes tasks in the Architecture Components in the same thread @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule var syncTaskExecutorRule = SyncTaskExecutorRule() - // Allows explicit setting of "now" @get:Rule var fixedTimeExecutorRule = FixedTimeExecutorRule() - // Allows IntervalMapper to execute immediately - @get:Rule var fakeIntervalMapperRule = FakeIntervalMapperRule() - // Overrides Dispatchers.Main used in Coroutines @get:Rule var coroutineRule = MainCoroutineRule() @@ -121,7 +115,7 @@ class SessionDetailViewModelTest { } @Test - fun testAnonymous_dataReady() { + fun testAnonymous_dataReady() = coroutineRule.runBlockingTest { // Even with a session ID set, data is null if no user is available assertNotEquals(null, LiveDataTestUtil.getValue(viewModel.session)) } @@ -183,7 +177,7 @@ class SessionDetailViewModelTest { } @Test - fun testReserveEvent_notLoggedIn() { + fun testReserveEvent_notLoggedIn() = coroutineRule.runBlockingTest { // Create test use cases with test data val signInDelegate = FakeSignInViewModelDelegate() signInDelegate.injectIsSignedIn = false @@ -205,7 +199,7 @@ class SessionDetailViewModelTest { } @Test - fun testReserveEvent_noInternet() { + fun testReserveEvent_noInternet() = coroutineRule.runBlockingTest { // Create test use cases with test data val signInDelegate = FakeSignInViewModelDelegate() signInDelegate.injectIsSignedIn = false @@ -265,10 +259,10 @@ class SessionDetailViewModelTest { } @Test - fun testStartsInTenMinutes_thenHasNullTimeUntilStart() { + fun testStartsInTenMinutes_thenHasNullTimeUntilStart() = coroutineRule.runBlockingTest { val vm = createSessionDetailViewModelWithAuthEnabled() fixedTimeExecutorRule.time = testSession.startTime.minusMinutes(10).toInstant() - forceTimeUntilStartIntervalUpdate(vm) + coroutineRule.testDispatcher.advanceTimeBy(TEN_SECONDS) assertEquals(null, LiveDataTestUtil.getValue(vm.timeUntilStart)) } @@ -292,18 +286,18 @@ class SessionDetailViewModelTest { // } @Test - fun testStartsIn0Minutes_thenHasNullTimeUntilStart() { + fun testStartsIn0Minutes_thenHasNullTimeUntilStart() = coroutineRule.runBlockingTest { val vm = createSessionDetailViewModelWithAuthEnabled() fixedTimeExecutorRule.time = testSession.startTime.minusSeconds(30).toInstant() - forceTimeUntilStartIntervalUpdate(vm) + coroutineRule.testDispatcher.advanceTimeBy(TEN_SECONDS) assertEquals(null, LiveDataTestUtil.getValue(vm.timeUntilStart)) } @Test - fun testStarts10MinutesAgo_thenHasNullTimeUntilStart() { + fun testStarts10MinutesAgo_thenHasNullTimeUntilStart() = coroutineRule.runBlockingTest { val vm = createSessionDetailViewModelWithAuthEnabled() fixedTimeExecutorRule.time = testSession.startTime.plusMinutes(10).toInstant() - forceTimeUntilStartIntervalUpdate(vm) + coroutineRule.testDispatcher.advanceTimeBy(TEN_SECONDS) assertEquals(null, LiveDataTestUtil.getValue(vm.timeUntilStart)) } @@ -379,19 +373,17 @@ class SessionDetailViewModelTest { networkUtils: NetworkUtils = mockNetworkUtils, timeProvider: TimeProvider = DefaultTimeProvider, analyticsHelper: AnalyticsHelper = FakeAnalyticsHelper(), - isReservationEnabledByRemoteConfig: Boolean = true + isReservationEnabledByRemoteConfig: Boolean = true, + defaultDispatcher: CoroutineDispatcher = coroutineRule.testDispatcher ): SessionDetailViewModel { return SessionDetailViewModel( signInViewModelPlugin, loadUserSessionUseCase, loadRelatedSessionsUseCase, starEventUseCase, reservationActionUseCase, getTimeZoneUseCase, snackbarMessageManager, - timeProvider, networkUtils, analyticsHelper, isReservationEnabledByRemoteConfig + timeProvider, networkUtils, analyticsHelper, isReservationEnabledByRemoteConfig, + defaultDispatcher ) } - private fun forceTimeUntilStartIntervalUpdate(vm: SessionDetailViewModel) { - (vm.timeUntilStart as SetIntervalLiveData<*, *>).updateValue() - } - private fun createSessionWithUrl(youtubeUrl: String) = Session( id = "0", title = "Session 0", description = "", diff --git a/mobile/src/test/java/com/google/samples/apps/iosched/ui/sessiondetail/SessionFeedbackViewModelTest.kt b/mobile/src/test/java/com/google/samples/apps/iosched/ui/sessiondetail/SessionFeedbackViewModelTest.kt index 91745873c0..0a3924cb84 100644 --- a/mobile/src/test/java/com/google/samples/apps/iosched/ui/sessiondetail/SessionFeedbackViewModelTest.kt +++ b/mobile/src/test/java/com/google/samples/apps/iosched/ui/sessiondetail/SessionFeedbackViewModelTest.kt @@ -30,9 +30,7 @@ import com.google.samples.apps.iosched.shared.result.Result import com.google.samples.apps.iosched.shared.util.NetworkUtils import com.google.samples.apps.iosched.test.data.MainCoroutineRule import com.google.samples.apps.iosched.test.data.TestData -import com.google.samples.apps.iosched.test.util.SyncTaskExecutorRule import com.google.samples.apps.iosched.test.util.fakes.FakeSignInViewModelDelegate -import com.google.samples.apps.iosched.test.util.time.FakeIntervalMapperRule import com.google.samples.apps.iosched.test.util.time.FixedTimeExecutorRule import com.google.samples.apps.iosched.ui.schedule.TestUserEventDataSource import com.google.samples.apps.iosched.ui.signin.SignInViewModelDelegate @@ -52,18 +50,10 @@ class SessionFeedbackViewModelTest { @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule - var syncTaskExecutorRule = SyncTaskExecutorRule() - // Allows explicit setting of "now" @get:Rule var fixedTimeExecutorRule = FixedTimeExecutorRule() - // Allows IntervalMapper to execute immediately - @get:Rule - var fakeIntervalMapperRule = FakeIntervalMapperRule() - // Overrides Dispatchers.Main used in Coroutines @get:Rule var coroutineRule = MainCoroutineRule() diff --git a/mobile/src/test/java/com/google/samples/apps/iosched/ui/signin/FirebaseSignInViewModelDelegateTest.kt b/mobile/src/test/java/com/google/samples/apps/iosched/ui/signin/FirebaseSignInViewModelDelegateTest.kt index 8c847b09b1..ec0d67b204 100644 --- a/mobile/src/test/java/com/google/samples/apps/iosched/ui/signin/FirebaseSignInViewModelDelegateTest.kt +++ b/mobile/src/test/java/com/google/samples/apps/iosched/ui/signin/FirebaseSignInViewModelDelegateTest.kt @@ -24,9 +24,9 @@ import com.google.samples.apps.iosched.shared.data.signin.AuthenticatedUserInfoB import com.google.samples.apps.iosched.shared.domain.auth.ObserveUserAuthStateUseCase import com.google.samples.apps.iosched.shared.domain.prefs.NotificationsPrefIsShownUseCase import com.google.samples.apps.iosched.shared.result.Result +import com.google.samples.apps.iosched.test.data.CoroutineScope import com.google.samples.apps.iosched.test.data.MainCoroutineRule import com.google.samples.apps.iosched.test.data.runBlockingTest -import com.google.samples.apps.iosched.test.util.SyncTaskExecutorRule import com.google.samples.apps.iosched.test.util.fakes.FakePreferenceStorage import com.google.samples.apps.iosched.ui.schedule.FakeObserveUserAuthStateUseCase import com.nhaarman.mockito_kotlin.doReturn @@ -47,10 +47,6 @@ class FirebaseSignInViewModelDelegateTest { @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule - var syncTaskExecutorRule = SyncTaskExecutorRule() - // Overrides Dispatchers.Main used in Coroutines @get:Rule var coroutineRule = MainCoroutineRule() @@ -61,6 +57,7 @@ class FirebaseSignInViewModelDelegateTest { observeUserAuthStateUseCase = FakeObserveUserAuthStateUseCase( user = Result.Success(null), isRegistered = Result.Success(false), + coroutineScope = coroutineRule.CoroutineScope(), coroutineDispatcher = coroutineRule.testDispatcher ) ) @@ -91,6 +88,7 @@ class FirebaseSignInViewModelDelegateTest { val fakeObserveUserAuthStateUseCase = FakeObserveUserAuthStateUseCase( user = Result.Success(user), isRegistered = Result.Success(true), + coroutineScope = coroutineRule.CoroutineScope(), coroutineDispatcher = coroutineRule.testDispatcher ) @@ -125,6 +123,7 @@ class FirebaseSignInViewModelDelegateTest { val fakeObserveUserAuthStateUseCase = FakeObserveUserAuthStateUseCase( user = Result.Success(user), isRegistered = Result.Success(false), + coroutineScope = coroutineRule.CoroutineScope(), coroutineDispatcher = coroutineRule.testDispatcher ) @@ -154,6 +153,7 @@ class FirebaseSignInViewModelDelegateTest { observeUserAuthStateUseCase = FakeObserveUserAuthStateUseCase( user = Result.Success(null), isRegistered = Result.Success(false), + coroutineScope = coroutineRule.CoroutineScope(), coroutineDispatcher = coroutineRule.testDispatcher ) ) @@ -173,6 +173,7 @@ class FirebaseSignInViewModelDelegateTest { observeUserAuthStateUseCase = FakeObserveUserAuthStateUseCase( user = Result.Success(null), isRegistered = Result.Success(false), + coroutineScope = coroutineRule.CoroutineScope(), coroutineDispatcher = coroutineRule.testDispatcher ) ) @@ -197,6 +198,7 @@ class FirebaseSignInViewModelDelegateTest { FakeObserveUserAuthStateUseCase( user = Result.Success(null), isRegistered = Result.Success(true), + coroutineScope = coroutineRule.CoroutineScope(), coroutineDispatcher = coroutineRule.testDispatcher), notificationsPrefIsShownUseCase: NotificationsPrefIsShownUseCase = createNotificationsPrefIsShownUseCase(), diff --git a/mobile/src/test/java/com/google/samples/apps/iosched/ui/signin/SignInViewModelTest.kt b/mobile/src/test/java/com/google/samples/apps/iosched/ui/signin/SignInViewModelTest.kt index 702dd7e8c3..187ac7181d 100644 --- a/mobile/src/test/java/com/google/samples/apps/iosched/ui/signin/SignInViewModelTest.kt +++ b/mobile/src/test/java/com/google/samples/apps/iosched/ui/signin/SignInViewModelTest.kt @@ -19,7 +19,6 @@ package com.google.samples.apps.iosched.ui.signin import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.google.samples.apps.iosched.androidtest.util.LiveDataTestUtil import com.google.samples.apps.iosched.test.data.MainCoroutineRule -import com.google.samples.apps.iosched.test.util.SyncTaskExecutorRule import com.google.samples.apps.iosched.test.util.fakes.FakeSignInViewModelDelegate import junit.framework.Assert.assertEquals import junit.framework.Assert.assertNotNull @@ -32,10 +31,6 @@ class SignInViewModelTest { @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule - var syncTaskExecutorRule = SyncTaskExecutorRule() - // Overrides Dispatchers.Main used in Coroutines @get:Rule var coroutineRule = MainCoroutineRule() diff --git a/mobile/src/test/java/com/google/samples/apps/iosched/ui/speaker/SpeakerViewModelTest.kt b/mobile/src/test/java/com/google/samples/apps/iosched/ui/speaker/SpeakerViewModelTest.kt index 06574654b7..55426dbd90 100644 --- a/mobile/src/test/java/com/google/samples/apps/iosched/ui/speaker/SpeakerViewModelTest.kt +++ b/mobile/src/test/java/com/google/samples/apps/iosched/ui/speaker/SpeakerViewModelTest.kt @@ -31,7 +31,6 @@ import com.google.samples.apps.iosched.shared.domain.speakers.LoadSpeakerUseCase import com.google.samples.apps.iosched.test.data.MainCoroutineRule import com.google.samples.apps.iosched.test.data.TestData import com.google.samples.apps.iosched.test.data.runBlockingTest -import com.google.samples.apps.iosched.test.util.SyncTaskExecutorRule import com.google.samples.apps.iosched.test.util.fakes.FakeAnalyticsHelper import com.google.samples.apps.iosched.test.util.fakes.FakeEventActionsViewModelDelegate import com.google.samples.apps.iosched.test.util.fakes.FakePreferenceStorage @@ -53,9 +52,6 @@ class SpeakerViewModelTest { // Executes tasks in the Architecture Components in the same thread @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule var syncTaskExecutorRule = SyncTaskExecutorRule() - // Overrides Dispatchers.Main used in Coroutines @get:Rule var coroutineRule = MainCoroutineRule() diff --git a/mobile/src/test/java/com/google/samples/apps/iosched/util/IntervalMediatorLiveDataTest.kt b/mobile/src/test/java/com/google/samples/apps/iosched/util/IntervalMediatorLiveDataTest.kt new file mode 100644 index 0000000000..05ae32f5fe --- /dev/null +++ b/mobile/src/test/java/com/google/samples/apps/iosched/util/IntervalMediatorLiveDataTest.kt @@ -0,0 +1,136 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.iosched.util + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import com.google.samples.apps.iosched.shared.util.IntervalMediatorLiveData +import kotlinx.coroutines.test.TestCoroutineDispatcher +import org.junit.After +import org.junit.Assert +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class IntervalMediatorLiveDataTest { + + @get:Rule + var instantTaskExecutorRule = InstantTaskExecutorRule() + + private lateinit var testDispatcher: TestCoroutineDispatcher + private lateinit var sourceLiveData: MutableLiveData + private lateinit var subject: LiveData + + @Before + fun setup() { + testDispatcher = TestCoroutineDispatcher() + sourceLiveData = MutableLiveData() + + var calls = 0 + subject = IntervalMediatorLiveData( + source = sourceLiveData, + dispatcher = testDispatcher, + intervalMs = 1_000L + ) { + ++calls + } + } + + @After + fun teardown() { + Assert.assertFalse(subject.hasObservers()) + testDispatcher.cleanupTestCoroutines() + } + + @Test + fun `when not observed then transformation is not called`() { + testDispatcher.advanceTimeBy(10_000L) + Assert.assertNull(subject.value) + } + + @Test + fun `when observed and source changes then transformation is called`() { + // Source has no value yet. + Assert.assertNull(subject.value) + + sourceLiveData.postValue(Unit) + val observer = Observer {} + subject.observeForever(observer) + // Transformation called immediately because sourceLiveData has a value + Assert.assertEquals(1, subject.value) + + // Post a value to source, but don't advance time. + sourceLiveData.postValue(Unit) + Assert.assertEquals(2, subject.value) + + // Cleanup + subject.removeObserver(observer) + } + + @Test + fun `when observed then transformation called every interval`() { + val observer = Observer {} + subject.observeForever(observer) + // Source has no value yet. + Assert.assertNull(subject.value) + + // Advance time. Note: don't post value to source. + testDispatcher.advanceTimeBy(1_000L) + Assert.assertEquals(1, subject.value) + + // Half interval, no change. + testDispatcher.advanceTimeBy(500L) + Assert.assertEquals(1, subject.value) + // Complete the interval. + testDispatcher.advanceTimeBy(500L) + Assert.assertEquals(2, subject.value) + + // Cleanup + subject.removeObserver(observer) + } + + @Test + fun `when not observed then interval pauses`() { + val observer = Observer {} + subject.observeForever(observer) + // Source has no value yet. + Assert.assertNull(subject.value) + + sourceLiveData.postValue(Unit) + Assert.assertEquals(1, subject.value) + testDispatcher.advanceTimeBy(1_000L) + Assert.assertEquals(2, subject.value) + + subject.removeObserver(observer) + // Posting changes to source and advancing time has no effect. + sourceLiveData.postValue(Unit) + testDispatcher.advanceTimeBy(10_000L) + Assert.assertEquals(2, subject.value) + + subject.observeForever(observer) + // Transformation called immediately because source has new value. + Assert.assertEquals(3, subject.value) + + testDispatcher.advanceTimeBy(1_000L) + Assert.assertEquals(4, subject.value) + + // Cleanup + subject.removeObserver(observer) + } +} diff --git a/mobile/src/test/java/com/google/samples/apps/iosched/util/SetIntervalLiveDataTest.kt b/mobile/src/test/java/com/google/samples/apps/iosched/util/SetIntervalLiveDataTest.kt deleted file mode 100644 index 4d1bee5a17..0000000000 --- a/mobile/src/test/java/com/google/samples/apps/iosched/util/SetIntervalLiveDataTest.kt +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.samples.apps.iosched.util - -import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.Observer -import com.google.samples.apps.iosched.androidtest.util.LiveDataTestUtil -import com.google.samples.apps.iosched.shared.util.SetIntervalLiveData.DefaultIntervalMapper -import com.google.samples.apps.iosched.test.util.SyncTaskExecutorRule -import junit.framework.Assert.assertEquals -import org.junit.Rule -import org.junit.Test - -class SetIntervalLiveDataTest { - - // Executes tasks in the Architecture Components in the same thread - @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule var syncTaskExecutorRule = SyncTaskExecutorRule() - - @Test - fun testBasicMapOperation() { - val source = MutableLiveData() - source.value = 5 - - val subject = DefaultIntervalMapper.mapAtInterval(source) { sourceValue -> - sourceValue?.run { - sourceValue + 5 - } - } - - assertEquals(10, LiveDataTestUtil.getValue(subject)) - } - - @Test - fun whenNoIntervalsTriggered_thenMapFn_isOnlyCalledOnce() { - val source = MutableLiveData() - source.value = 5 - - var calls = 0 - - val subject = DefaultIntervalMapper.mapAtInterval(source) { - ++calls - } - - assertEquals(1, LiveDataTestUtil.getValue(subject)) - } - - @Test - fun whenNoIntervalsTriggered_thenMapFn_isCalledWhenNewValue() { - val source = MutableLiveData() - source.value = 5 - - var calls = 0 - - val subject = DefaultIntervalMapper.mapAtInterval(source) { - ++calls - } - - LiveDataTestUtil.getValue(subject) // register observer so it processes setValue - - source.value = 10 - - assertEquals(2, LiveDataTestUtil.getValue(subject)) - } - - @Test - fun whenSourceHasEmittedSeveral_thenMapFnIsCalledWithLastData() { - val source = MutableLiveData() - source.value = 5 - source.value = 6 - source.value = 7 - source.value = 8 - - var calls = 0 - - val subject = DefaultIntervalMapper.mapAtInterval(source) { sourceValue -> - sourceValue?.run { - calls++ - sourceValue + 5 - } - } - - assertEquals(13, LiveDataTestUtil.getValue(subject)) - assertEquals(1, calls) - } - - @Test - fun whenIntervalTriggered_mapRunsOnLastValue() { - val source = MutableLiveData() - source.value = 5 - - var timeModifier = 5 - - val subject = DefaultIntervalMapper.mapAtInterval(source) { sourceValue -> - sourceValue?.run { - sourceValue + timeModifier - } - } - - val observer = Observer({}) - - // make the subject active so it runs intervals - subject.observeForever(observer) - - timeModifier = 10 - syncTaskExecutorRule.runAllScheduledPostDelayedTasks() - - assertEquals(15, LiveDataTestUtil.getValue(subject)) - - subject.removeObserver(observer) - } - - @Test - fun whenNotActive_mapDoesNotRunOnInterval() { - val source = MutableLiveData() - source.value = 5 - - var calls = 0 - - val subject = DefaultIntervalMapper.mapAtInterval(source) { - ++calls - } - assertEquals(1, LiveDataTestUtil.getValue(subject)) - - syncTaskExecutorRule.runAllScheduledPostDelayedTasks() - - assertEquals(1, LiveDataTestUtil.getValue(subject)) - } -} diff --git a/shared/src/main/java/com/google/samples/apps/iosched/shared/data/job/ConferenceDataService.kt b/shared/src/main/java/com/google/samples/apps/iosched/shared/data/job/ConferenceDataService.kt index 761b96928d..0cd2c4dd4e 100644 --- a/shared/src/main/java/com/google/samples/apps/iosched/shared/data/job/ConferenceDataService.kt +++ b/shared/src/main/java/com/google/samples/apps/iosched/shared/data/job/ConferenceDataService.kt @@ -23,7 +23,8 @@ import com.google.samples.apps.iosched.shared.result.succeeded import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -38,14 +39,12 @@ class ConferenceDataService : JobService() { @Inject lateinit var refreshEventDataUseCase: RefreshConferenceDataUseCase - private val serviceJob = Job() - private val serviceScope = CoroutineScope(Dispatchers.Main + serviceJob) + private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) override fun onStartJob(params: JobParameters?): Boolean { Timber.i("ConferenceDataService triggering refresh conference data.") - // Execute off the main thread serviceScope.launch { val result = refreshEventDataUseCase(Unit) @@ -71,12 +70,12 @@ class ConferenceDataService : JobService() { return true } - companion object { - const val JOB_ID = 0xFE0FE0 - } - override fun onDestroy() { - serviceJob.cancel() + serviceScope.cancel() super.onDestroy() } + + companion object { + const val JOB_ID = 0xFE0FE0 + } } diff --git a/shared/src/main/java/com/google/samples/apps/iosched/shared/data/signin/AuthenticatedUserRegistration.kt b/shared/src/main/java/com/google/samples/apps/iosched/shared/data/signin/AuthenticatedUserRegistration.kt index 0cf04f063d..41e5a1b075 100644 --- a/shared/src/main/java/com/google/samples/apps/iosched/shared/data/signin/AuthenticatedUserRegistration.kt +++ b/shared/src/main/java/com/google/samples/apps/iosched/shared/data/signin/AuthenticatedUserRegistration.kt @@ -17,7 +17,8 @@ package com.google.samples.apps.iosched.shared.data.signin import com.google.samples.apps.iosched.shared.BuildConfig -import com.google.samples.apps.iosched.shared.domain.internal.DefaultScheduler +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext import java.io.IOException import okhttp3.OkHttpClient import okhttp3.Request @@ -38,8 +39,8 @@ object AuthenticatedUserRegistration { .build() } - fun callRegistrationEndpoint(token: String) { - DefaultScheduler.execute { + suspend fun callRegistrationEndpoint(token: String, coroutineDispatcher: CoroutineDispatcher) { + withContext(coroutineDispatcher) { val request = Request.Builder() .header("Authorization", "Bearer $token") .url(BuildConfig.REGISTRATION_ENDPOINT_URL) @@ -50,13 +51,13 @@ object AuthenticatedUserRegistration { client.newCall(request).execute() } catch (e: IOException) { Timber.e(e) - return@execute + return@withContext } val body = response.body()?.string() ?: "" if (body.isEmpty() || !response.isSuccessful) { Timber.e("Network error calling registration point (response ${response.code()} )") - return@execute + return@withContext } Timber.d("Registration point called, user is registered: $body") response.body()?.close() diff --git a/shared/src/main/java/com/google/samples/apps/iosched/shared/data/signin/datasources/FirebaseAuthStateUserDataSource.kt b/shared/src/main/java/com/google/samples/apps/iosched/shared/data/signin/datasources/FirebaseAuthStateUserDataSource.kt index 33d68064af..a346618393 100644 --- a/shared/src/main/java/com/google/samples/apps/iosched/shared/data/signin/datasources/FirebaseAuthStateUserDataSource.kt +++ b/shared/src/main/java/com/google/samples/apps/iosched/shared/data/signin/datasources/FirebaseAuthStateUserDataSource.kt @@ -22,11 +22,13 @@ import com.google.firebase.auth.GetTokenResult import com.google.samples.apps.iosched.shared.data.signin.AuthenticatedUserInfoBasic import com.google.samples.apps.iosched.shared.data.signin.AuthenticatedUserRegistration import com.google.samples.apps.iosched.shared.data.signin.FirebaseUserInfo +import com.google.samples.apps.iosched.shared.di.IoDispatcher import com.google.samples.apps.iosched.shared.domain.sessions.NotificationAlarmUpdater import com.google.samples.apps.iosched.shared.fcm.FcmTokenUpdater import com.google.samples.apps.iosched.shared.result.Result import com.google.samples.apps.iosched.shared.result.Result.Success import com.google.samples.apps.iosched.shared.util.suspendAndWait +import kotlinx.coroutines.CoroutineDispatcher import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow @@ -52,7 +54,8 @@ import timber.log.Timber class FirebaseAuthStateUserDataSource @Inject constructor( val firebase: FirebaseAuth, private val tokenUpdater: FcmTokenUpdater, - private val notificationAlarmUpdater: NotificationAlarmUpdater + private val notificationAlarmUpdater: NotificationAlarmUpdater, + @IoDispatcher private val ioDispatcher: CoroutineDispatcher ) : AuthStateUserDataSource { // lastUid can be potentially consumed and written from different threads @@ -87,7 +90,7 @@ class FirebaseAuthStateUserDataSource @Inject constructor( tokenResult.token?.let { // Call registration point to generate a result in Firestore Timber.d("User authenticated, hitting registration endpoint") - AuthenticatedUserRegistration.callRegistrationEndpoint(it) + AuthenticatedUserRegistration.callRegistrationEndpoint(it, ioDispatcher) } } catch (e: Exception) { Timber.e(e) diff --git a/shared/src/main/java/com/google/samples/apps/iosched/shared/di/DispatcherQualifiers.kt b/shared/src/main/java/com/google/samples/apps/iosched/shared/di/CoroutinesQualifiers.kt similarity index 92% rename from shared/src/main/java/com/google/samples/apps/iosched/shared/di/DispatcherQualifiers.kt rename to shared/src/main/java/com/google/samples/apps/iosched/shared/di/CoroutinesQualifiers.kt index 7155d8abad..07aeaa26ec 100644 --- a/shared/src/main/java/com/google/samples/apps/iosched/shared/di/DispatcherQualifiers.kt +++ b/shared/src/main/java/com/google/samples/apps/iosched/shared/di/CoroutinesQualifiers.kt @@ -33,3 +33,7 @@ annotation class MainDispatcher @Retention(AnnotationRetention.BINARY) @Qualifier annotation class MainImmediateDispatcher + +@Retention(AnnotationRetention.BINARY) +@Qualifier +annotation class ApplicationScope diff --git a/shared/src/main/java/com/google/samples/apps/iosched/shared/domain/auth/ObserveUserAuthStateUseCase.kt b/shared/src/main/java/com/google/samples/apps/iosched/shared/domain/auth/ObserveUserAuthStateUseCase.kt index cd33893035..eb1886e48e 100644 --- a/shared/src/main/java/com/google/samples/apps/iosched/shared/domain/auth/ObserveUserAuthStateUseCase.kt +++ b/shared/src/main/java/com/google/samples/apps/iosched/shared/domain/auth/ObserveUserAuthStateUseCase.kt @@ -21,6 +21,7 @@ import com.google.samples.apps.iosched.shared.data.signin.AuthenticatedUserInfoB import com.google.samples.apps.iosched.shared.data.signin.FirebaseRegisteredUserInfo import com.google.samples.apps.iosched.shared.data.signin.datasources.AuthStateUserDataSource import com.google.samples.apps.iosched.shared.data.signin.datasources.RegisteredUserDataSource +import com.google.samples.apps.iosched.shared.di.ApplicationScope import com.google.samples.apps.iosched.shared.di.IoDispatcher import com.google.samples.apps.iosched.shared.domain.FlowUseCase import com.google.samples.apps.iosched.shared.fcm.TopicSubscriber @@ -33,7 +34,6 @@ import javax.inject.Singleton import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.channelFlow @@ -53,10 +53,10 @@ open class ObserveUserAuthStateUseCase @Inject constructor( private val registeredUserDataSource: RegisteredUserDataSource, private val authStateUserDataSource: AuthStateUserDataSource, private val topicSubscriber: TopicSubscriber, + @ApplicationScope private val externalScope: CoroutineScope, @IoDispatcher private val ioDispatcher: CoroutineDispatcher ) : FlowUseCase(ioDispatcher) { - private val useCaseScope = CoroutineScope(SupervisorJob() + ioDispatcher) private var observeUserRegisteredChangesJob: Job? = null override fun execute(parameters: Any): Flow> { @@ -107,7 +107,7 @@ open class ObserveUserAuthStateUseCase @Inject constructor( // Observing the user registration changes from another scope as doing it using a // supervisorScope was keeping the coroutine busy and updates to // authStateUserDataSource.getBasicUserInfo() were ignored - observeUserRegisteredChangesJob = useCaseScope.launch { + observeUserRegisteredChangesJob = externalScope.launch(ioDispatcher) { // Start observing the user in Firestore to fetch the `registered` flag registeredUserDataSource.observeUserChanges(userId).collect { result -> val isRegisteredValue: Boolean? = result.data diff --git a/shared/src/main/java/com/google/samples/apps/iosched/shared/domain/internal/TaskScheduler.kt b/shared/src/main/java/com/google/samples/apps/iosched/shared/domain/internal/TaskScheduler.kt deleted file mode 100644 index 616ff89a1a..0000000000 --- a/shared/src/main/java/com/google/samples/apps/iosched/shared/domain/internal/TaskScheduler.kt +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.samples.apps.iosched.shared.domain.internal - -import android.os.Handler -import android.os.Looper -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors - -private const val NUMBER_OF_THREADS = 4 // TODO: Make this depend on device's hw - -interface Scheduler { - - fun execute(task: () -> Unit) - - fun postToMainThread(task: () -> Unit) - - fun postDelayedToMainThread(delay: Long, task: () -> Unit) -} - -/** - * A shim [Scheduler] that by default handles operations in the [AsyncScheduler]. - */ -object DefaultScheduler : Scheduler { - - private var delegate: Scheduler = AsyncScheduler - - /** - * Sets the new delegate scheduler, null to revert to the default async one. - */ - fun setDelegate(newDelegate: Scheduler?) { - delegate = newDelegate ?: AsyncScheduler - } - - override fun execute(task: () -> Unit) { - delegate.execute(task) - } - - override fun postToMainThread(task: () -> Unit) { - delegate.postToMainThread(task) - } - - override fun postDelayedToMainThread(delay: Long, task: () -> Unit) { - delegate.postDelayedToMainThread(delay, task) - } -} - -/** - * Runs tasks in a [ExecutorService] with a fixed thread of pools - */ -internal object AsyncScheduler : Scheduler { - - private val executorService: ExecutorService = Executors.newFixedThreadPool(NUMBER_OF_THREADS) - - override fun execute(task: () -> Unit) { - executorService.execute(task) - } - - override fun postToMainThread(task: () -> Unit) { - if (isMainThread()) { - task() - } else { - val mainThreadHandler = Handler(Looper.getMainLooper()) - mainThreadHandler.post(task) - } - } - - private fun isMainThread(): Boolean { - return Looper.getMainLooper().thread === Thread.currentThread() - } - - override fun postDelayedToMainThread(delay: Long, task: () -> Unit) { - val mainThreadHandler = Handler(Looper.getMainLooper()) - mainThreadHandler.postDelayed(task, delay) - } -} - -/** - * Runs tasks synchronously. - */ -object SyncScheduler : Scheduler { - private val postDelayedTasks = mutableListOf<() -> Unit>() - - override fun execute(task: () -> Unit) { - task() - } - - override fun postToMainThread(task: () -> Unit) { - task() - } - - override fun postDelayedToMainThread(delay: Long, task: () -> Unit) { - postDelayedTasks.add(task) - } - - fun runAllScheduledPostDelayedTasks() { - val tasks = postDelayedTasks.toList() - clearScheduledPostdelayedTasks() - for (task in tasks) { - task() - } - } - - fun clearScheduledPostdelayedTasks() { - postDelayedTasks.clear() - } -} diff --git a/shared/src/main/java/com/google/samples/apps/iosched/shared/domain/sessions/NotificationAlarmUpdater.kt b/shared/src/main/java/com/google/samples/apps/iosched/shared/domain/sessions/NotificationAlarmUpdater.kt index 7f254fb14a..57604c7d5a 100644 --- a/shared/src/main/java/com/google/samples/apps/iosched/shared/domain/sessions/NotificationAlarmUpdater.kt +++ b/shared/src/main/java/com/google/samples/apps/iosched/shared/domain/sessions/NotificationAlarmUpdater.kt @@ -19,18 +19,16 @@ package com.google.samples.apps.iosched.shared.domain.sessions import com.google.samples.apps.iosched.model.userdata.UserSession import com.google.samples.apps.iosched.shared.data.userevent.ObservableUserEvents import com.google.samples.apps.iosched.shared.data.userevent.SessionAndUserEventRepository -import com.google.samples.apps.iosched.shared.di.DefaultDispatcher +import com.google.samples.apps.iosched.shared.di.ApplicationScope import com.google.samples.apps.iosched.shared.notifications.SessionAlarmManager import com.google.samples.apps.iosched.shared.result.Result import com.google.samples.apps.iosched.shared.result.data -import javax.inject.Inject -import javax.inject.Singleton -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton /** * Sets a notification for each session that is starred or reserved by the user. @@ -39,15 +37,11 @@ import timber.log.Timber class NotificationAlarmUpdater @Inject constructor( private val alarmManager: SessionAlarmManager, private val repository: SessionAndUserEventRepository, - @DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher + @ApplicationScope private val externalScope: CoroutineScope ) { - // Coroutines scope for NotificationAlarmUpdater background work - private val alarmUpdaterScope: CoroutineScope = - CoroutineScope(defaultDispatcher + SupervisorJob()) - fun updateAll(userId: String) { - alarmUpdaterScope.launch { + externalScope.launch { val events = repository.getObservableUserEvents(userId).first { it is Result.Success } events.data?.let { data -> processEvents(userId, data) @@ -70,7 +64,7 @@ class NotificationAlarmUpdater @Inject constructor( } fun cancelAll() { - alarmUpdaterScope.launch { + externalScope.launch { val events = repository.getObservableUserEvents(null).first { it is Result.Success } events.data?.let { data -> cancelAllSessions(data) diff --git a/shared/src/main/java/com/google/samples/apps/iosched/shared/fcm/FcmTokenUpdater.kt b/shared/src/main/java/com/google/samples/apps/iosched/shared/fcm/FcmTokenUpdater.kt index 7232b78223..56477ffde8 100644 --- a/shared/src/main/java/com/google/samples/apps/iosched/shared/fcm/FcmTokenUpdater.kt +++ b/shared/src/main/java/com/google/samples/apps/iosched/shared/fcm/FcmTokenUpdater.kt @@ -21,14 +21,22 @@ import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.SetOptions import com.google.firebase.iid.FirebaseInstanceId import com.google.samples.apps.iosched.shared.data.document2020 -import com.google.samples.apps.iosched.shared.domain.internal.DefaultScheduler +import com.google.samples.apps.iosched.shared.di.ApplicationScope +import com.google.samples.apps.iosched.shared.di.MainDispatcher +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import javax.inject.Inject import timber.log.Timber /** * Saves the FCM ID tokens in Firestore. */ -class FcmTokenUpdater @Inject constructor(val firestore: FirebaseFirestore) { +class FcmTokenUpdater @Inject constructor( + @ApplicationScope private val externalScope: CoroutineScope, + @MainDispatcher private val mainDispatcher: CoroutineDispatcher, + val firestore: FirebaseFirestore +) { fun updateTokenForUser(userId: String) { FirebaseInstanceId.getInstance().instanceId.addOnSuccessListener { instanceIdResult -> @@ -41,7 +49,7 @@ class FcmTokenUpdater @Inject constructor(val firestore: FirebaseFirestore) { ) // All Firestore operations start from the main thread to avoid concurrency issues. - DefaultScheduler.postToMainThread { + externalScope.launch(mainDispatcher) { firestore .document2020() .collection(USERS_COLLECTION) diff --git a/shared/src/main/java/com/google/samples/apps/iosched/shared/notifications/AlarmBroadcastReceiver.kt b/shared/src/main/java/com/google/samples/apps/iosched/shared/notifications/AlarmBroadcastReceiver.kt index 81ca3800fa..903ba8cba2 100644 --- a/shared/src/main/java/com/google/samples/apps/iosched/shared/notifications/AlarmBroadcastReceiver.kt +++ b/shared/src/main/java/com/google/samples/apps/iosched/shared/notifications/AlarmBroadcastReceiver.kt @@ -37,14 +37,13 @@ import com.google.samples.apps.iosched.model.userdata.UserSession import com.google.samples.apps.iosched.shared.R import com.google.samples.apps.iosched.shared.data.prefs.SharedPreferenceStorage import com.google.samples.apps.iosched.shared.data.signin.datasources.AuthIdDataSource +import com.google.samples.apps.iosched.shared.di.ApplicationScope import com.google.samples.apps.iosched.shared.domain.sessions.LoadSessionOneShotUseCase import com.google.samples.apps.iosched.shared.domain.sessions.LoadUserSessionOneShotUseCase import com.google.samples.apps.iosched.shared.result.Result import com.google.samples.apps.iosched.shared.result.Result.Success import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch @@ -74,9 +73,9 @@ class AlarmBroadcastReceiver : BroadcastReceiver() { @Inject lateinit var authIdDataSource: AuthIdDataSource - // Coroutines scope for AlarmBroadcastReceiver background work - private val alarmScope: CoroutineScope = - CoroutineScope(Dispatchers.Default + SupervisorJob()) + @ApplicationScope + @Inject + lateinit var externalScope: CoroutineScope override fun onReceive(context: Context, intent: Intent) { Timber.d("Alarm received") @@ -90,12 +89,12 @@ class AlarmBroadcastReceiver : BroadcastReceiver() { val channel = intent.getStringExtra(EXTRA_NOTIFICATION_CHANNEL) when (channel) { CHANNEL_ID_UPCOMING -> { - alarmScope.launch { + externalScope.launch { checkThenShowPreSessionNotification(context, sessionId, userId) } } CHANNEL_ID_FEEDBACK -> { - alarmScope.launch { + externalScope.launch { checkThenShowPostSessionNotification(context, sessionId, userId) } } diff --git a/shared/src/main/java/com/google/samples/apps/iosched/shared/util/IntervalMediatorLiveData.kt b/shared/src/main/java/com/google/samples/apps/iosched/shared/util/IntervalMediatorLiveData.kt new file mode 100644 index 0000000000..3097adf959 --- /dev/null +++ b/shared/src/main/java/com/google/samples/apps/iosched/shared/util/IntervalMediatorLiveData.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.iosched.shared.util + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MediatorLiveData +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch + +/** + * LiveData that runs a transformation [block] every interval and also whenever a source + * LiveData changes. + */ +class IntervalMediatorLiveData( + source: LiveData

, + dispatcher: CoroutineDispatcher = Dispatchers.Default, + private val intervalMs: Long, + private val block: (P?) -> T? +) : MediatorLiveData() { + + private val intervalScope = CoroutineScope(dispatcher + SupervisorJob()) + // Nullable because sometimes onInactive is called without onActive first. + private var intervalJob: Job? = null + + private var lastEmitted: P? = null + + init { + addSource(source) { + lastEmitted = it + // We're on the main thread, so use a coroutine in case the transformation is expensive. + intervalScope.launch { + postValue(block(lastEmitted)) + } + } + } + + override fun onActive() { + super.onActive() + // Loop until canceled. + intervalJob = intervalScope.launch { + while (isActive) { + delay(intervalMs) + postValue(block(lastEmitted)) + } + } + } + + override fun onInactive() { + super.onInactive() + intervalJob?.cancel() + intervalJob = null + } +} diff --git a/shared/src/main/java/com/google/samples/apps/iosched/shared/util/SetIntervalLiveData.kt b/shared/src/main/java/com/google/samples/apps/iosched/shared/util/SetIntervalLiveData.kt deleted file mode 100644 index c01be69754..0000000000 --- a/shared/src/main/java/com/google/samples/apps/iosched/shared/util/SetIntervalLiveData.kt +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.samples.apps.iosched.shared.util - -import androidx.annotation.UiThread -import androidx.annotation.WorkerThread -import androidx.lifecycle.LiveData -import androidx.lifecycle.MediatorLiveData -import com.google.samples.apps.iosched.shared.domain.internal.DefaultScheduler - -private const val ONE_SECOND = 1_000L - -/** - * LiveData that applies a map operation to a source at an interval. - * - * This is useful if you want to transform a source LiveData on a timer, for example to show - * a countdown timer to the user based on data provided in source liveData. - * - * @param source The source LiveData to transform - * @param intervalMs How often to run the map operation on the last value provided from source - * @param map operation to map source data to output - */ -open class SetIntervalLiveData( - source: LiveData

, - private val intervalMs: Long = ONE_SECOND, - private val map: (P?) -> R? -) : MediatorLiveData() { - - private var lastEmitted: P? = null - - // Only run the timer if there is an active observer - private var isActive = false - - // Use the task scheduler to manage threads - private val taskScheduler = DefaultScheduler - - init { - addSource(source) { value -> - lastEmitted = value - // since this is not in the background thread, go to background before running - // (potentially expensive) map operation. - taskScheduler.execute(::updateValue) - } - } - - object DefaultIntervalMapper : IntervalMapper { - - private var delegate: IntervalMapper = IntervalMapperDelegate - - fun setDelegate(value: IntervalMapper?) { - delegate = value ?: IntervalMapperDelegate - } - - override fun mapAtInterval( - source: LiveData

, - interval: Long, - map: (P?) -> R? - ): SetIntervalLiveData { - return delegate.mapAtInterval(source, interval, map) - } - } - - internal object IntervalMapperDelegate : IntervalMapper { - override fun mapAtInterval( - source: LiveData

, - interval: Long, - map: (P?) -> R? - ): SetIntervalLiveData { - return SetIntervalLiveData(source, interval, map) - } - } - - interface IntervalMapper { - - /** - * Apply a map operation to a LiveData repeatedly on an interval. - * - * @param source LiveData to transform (the source) - * @param interval how often to run the transform - * @param map operation to map the source data to output - */ - fun mapAtInterval( - source: LiveData

, - interval: Long = ONE_SECOND, - map: (P?) -> R? - ): SetIntervalLiveData - } - - /** - * In a background thread, run the map operation (which is potentially expensive), then post - * the result to any observers. - */ - @WorkerThread - fun updateValue() { - postValue(map(lastEmitted)) - } - - /** - * In the foreground thread, re-schedule itself on an interval. Each tick, call [updateValue] - * - * This will not execute when there are no observers. - */ - @UiThread - private fun onInterval() { - if (!isActive) { - return // don't process if no one is listening - } - taskScheduler.execute(::updateValue) - taskScheduler.postDelayedToMainThread(intervalMs, ::onInterval) - } - - /** - * Called when at an observer is watching this LiveData. It will only be called once for the - * transition between 0 and 1 observers. - * - * Start the interval. - */ - override fun onActive() { - super.onActive() - isActive = true - taskScheduler.postDelayedToMainThread(intervalMs, ::onInterval) - } - - /** - * Called when there are zero observers. - * - * Stop the interval. - */ - override fun onInactive() { - super.onInactive() - isActive = false - } -} diff --git a/shared/src/test/java/com/google/samples/apps/iosched/shared/data/ConferenceDataRepositoryTest.kt b/shared/src/test/java/com/google/samples/apps/iosched/shared/data/ConferenceDataRepositoryTest.kt index 27fe862dea..3201852c07 100644 --- a/shared/src/test/java/com/google/samples/apps/iosched/shared/data/ConferenceDataRepositoryTest.kt +++ b/shared/src/test/java/com/google/samples/apps/iosched/shared/data/ConferenceDataRepositoryTest.kt @@ -24,7 +24,6 @@ import com.google.samples.apps.iosched.test.data.TestData.session1 import com.google.samples.apps.iosched.test.data.TestData.session3 import com.google.samples.apps.iosched.test.data.runBlockingTest import com.google.samples.apps.iosched.test.util.FakeAppDatabase -import com.google.samples.apps.iosched.test.util.SyncTaskExecutorRule import kotlinx.coroutines.flow.first import java.io.IOException import org.hamcrest.core.Is.`is` as Is @@ -46,10 +45,6 @@ class ConferenceDataRepositoryTest { @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule - var syncTaskExecutorRule = SyncTaskExecutorRule() - // Overrides Dispatchers.Main used in Coroutines @get:Rule var coroutineRule = MainCoroutineRule() diff --git a/shared/src/test/java/com/google/samples/apps/iosched/shared/data/signin/ObserveUserAuthStateUseCaseTest.kt b/shared/src/test/java/com/google/samples/apps/iosched/shared/data/signin/ObserveUserAuthStateUseCaseTest.kt index 0b9037d6b7..c4ff355d63 100644 --- a/shared/src/test/java/com/google/samples/apps/iosched/shared/data/signin/ObserveUserAuthStateUseCaseTest.kt +++ b/shared/src/test/java/com/google/samples/apps/iosched/shared/data/signin/ObserveUserAuthStateUseCaseTest.kt @@ -23,6 +23,7 @@ import com.google.samples.apps.iosched.shared.domain.auth.ObserveUserAuthStateUs import com.google.samples.apps.iosched.shared.fcm.TopicSubscriber import com.google.samples.apps.iosched.shared.result.Result import com.google.samples.apps.iosched.shared.result.data +import com.google.samples.apps.iosched.test.data.CoroutineScope import com.google.samples.apps.iosched.test.data.MainCoroutineRule import com.google.samples.apps.iosched.test.data.runBlockingTest import com.nhaarman.mockito_kotlin.doReturn @@ -185,6 +186,7 @@ class ObserveUserAuthStateUseCaseTest { registeredUserDataSource, authStateUserDataSource, topicSubscriber, + coroutineRule.CoroutineScope(), coroutineRule.testDispatcher ) } diff --git a/shared/src/test/java/com/google/samples/apps/iosched/shared/data/userevent/DefaultSessionAndUserEventRepositoryTest.kt b/shared/src/test/java/com/google/samples/apps/iosched/shared/data/userevent/DefaultSessionAndUserEventRepositoryTest.kt index 5267bbc7b3..32a7a5ac1c 100644 --- a/shared/src/test/java/com/google/samples/apps/iosched/shared/data/userevent/DefaultSessionAndUserEventRepositoryTest.kt +++ b/shared/src/test/java/com/google/samples/apps/iosched/shared/data/userevent/DefaultSessionAndUserEventRepositoryTest.kt @@ -21,7 +21,6 @@ import com.google.samples.apps.iosched.shared.data.session.DefaultSessionReposit import com.google.samples.apps.iosched.shared.domain.repository.TestUserEventDataSource import com.google.samples.apps.iosched.shared.model.TestDataRepository import com.google.samples.apps.iosched.shared.result.Result -import com.google.samples.apps.iosched.shared.util.SyncExecutorRule import com.google.samples.apps.iosched.test.data.MainCoroutineRule import com.google.samples.apps.iosched.test.data.TestData import com.google.samples.apps.iosched.test.data.runBlockingTest @@ -46,10 +45,6 @@ class DefaultSessionAndUserEventRepositoryTest { @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule - var syncExecutorRule = SyncExecutorRule() - // Overrides Dispatchers.Main used in Coroutines @get:Rule var coroutineRule = MainCoroutineRule() diff --git a/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/feed/GetConferenceStateUseCaseTest.kt b/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/feed/GetConferenceStateUseCaseTest.kt index 506e4a33bb..5f9ae3536a 100644 --- a/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/feed/GetConferenceStateUseCaseTest.kt +++ b/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/feed/GetConferenceStateUseCaseTest.kt @@ -26,7 +26,6 @@ import com.google.samples.apps.iosched.shared.result.Result import com.google.samples.apps.iosched.shared.time.TimeProvider import com.google.samples.apps.iosched.test.data.MainCoroutineRule import com.google.samples.apps.iosched.test.data.TestData -import com.google.samples.apps.iosched.test.util.SyncTaskExecutorRule import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.mock import java.util.concurrent.TimeUnit @@ -54,9 +53,6 @@ class GetConferenceStateUseCaseTest { @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - @get:Rule - var syncTaskExecutorRule = SyncTaskExecutorRule() - @get:Rule var coroutineRule = MainCoroutineRule() diff --git a/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/feed/LoadAnnouncementsUseCaseTest.kt b/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/feed/LoadAnnouncementsUseCaseTest.kt index e7b0ebe606..4acb8c3fb9 100644 --- a/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/feed/LoadAnnouncementsUseCaseTest.kt +++ b/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/feed/LoadAnnouncementsUseCaseTest.kt @@ -25,7 +25,6 @@ import com.google.samples.apps.iosched.shared.result.Result import com.google.samples.apps.iosched.test.data.MainCoroutineRule import com.google.samples.apps.iosched.test.data.TestData import com.google.samples.apps.iosched.test.data.runBlockingTest -import com.google.samples.apps.iosched.test.util.SyncTaskExecutorRule import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Rule @@ -40,9 +39,6 @@ class LoadAnnouncementsUseCaseTest { @get:Rule val instantRule = InstantTaskExecutorRule() - @get:Rule - val syncTaskExecutorRule = SyncTaskExecutorRule() - // Overrides Dispatchers.Main used in Coroutines @get:Rule var coroutineRule = MainCoroutineRule() diff --git a/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/feed/LoadCurrentMomentUseCaseTest.kt b/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/feed/LoadCurrentMomentUseCaseTest.kt index 0612bd34a7..5bdc3cbe8c 100644 --- a/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/feed/LoadCurrentMomentUseCaseTest.kt +++ b/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/feed/LoadCurrentMomentUseCaseTest.kt @@ -26,7 +26,6 @@ import com.google.samples.apps.iosched.shared.result.successOr import com.google.samples.apps.iosched.test.data.MainCoroutineRule import com.google.samples.apps.iosched.test.data.TestData import com.google.samples.apps.iosched.test.data.runBlockingTest -import com.google.samples.apps.iosched.test.util.SyncTaskExecutorRule import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Assert.assertTrue @@ -42,9 +41,6 @@ class LoadCurrentMomentUseCaseTest { @get:Rule val instantRule = InstantTaskExecutorRule() - @get:Rule - val syncTaskExecutorRule = SyncTaskExecutorRule() - // Overrides Dispatchers.Main used in Coroutines @get:Rule var coroutineRule = MainCoroutineRule() diff --git a/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/sessions/LoadPinnedSessionsJsonUseCaseTest.kt b/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/sessions/LoadPinnedSessionsJsonUseCaseTest.kt index 125d87dd7e..677244639d 100644 --- a/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/sessions/LoadPinnedSessionsJsonUseCaseTest.kt +++ b/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/sessions/LoadPinnedSessionsJsonUseCaseTest.kt @@ -26,7 +26,6 @@ import com.google.samples.apps.iosched.shared.domain.repository.TestUserEventDat import com.google.samples.apps.iosched.shared.model.TestDataRepository import com.google.samples.apps.iosched.shared.result.Result.Loading import com.google.samples.apps.iosched.shared.result.data -import com.google.samples.apps.iosched.shared.util.SyncExecutorRule import com.google.samples.apps.iosched.shared.util.TimeUtils import com.google.samples.apps.iosched.shared.util.toEpochMilli import com.google.samples.apps.iosched.test.data.MainCoroutineRule @@ -50,9 +49,6 @@ class LoadPinnedSessionsJsonUseCaseTest { @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule var syncExecutorRule = SyncExecutorRule() - // Overrides Dispatchers.Main used in Coroutines @get:Rule var coroutineRule = MainCoroutineRule() diff --git a/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/sessions/LoadScheduleUserSessionsUseCaseTest.kt b/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/sessions/LoadScheduleUserSessionsUseCaseTest.kt index 7112c816e2..f3a903b8ec 100644 --- a/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/sessions/LoadScheduleUserSessionsUseCaseTest.kt +++ b/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/sessions/LoadScheduleUserSessionsUseCaseTest.kt @@ -27,7 +27,6 @@ import com.google.samples.apps.iosched.shared.domain.repository.TestUserEventDat import com.google.samples.apps.iosched.shared.model.TestDataRepository import com.google.samples.apps.iosched.shared.result.Result import com.google.samples.apps.iosched.shared.result.data -import com.google.samples.apps.iosched.shared.util.SyncExecutorRule import com.google.samples.apps.iosched.test.data.MainCoroutineRule import com.google.samples.apps.iosched.test.data.TestData import com.google.samples.apps.iosched.test.data.TestData.TestConferenceDays @@ -50,9 +49,6 @@ class LoadScheduleUserSessionsUseCaseTest { @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule var syncExecutorRule = SyncExecutorRule() - // Overrides Dispatchers.Main used in Coroutines @get:Rule var coroutineRule = MainCoroutineRule() diff --git a/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/sessions/LoadStarredAndReservedSessionsUseCaseTest.kt b/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/sessions/LoadStarredAndReservedSessionsUseCaseTest.kt index d7cc16b7c9..a5c661330f 100644 --- a/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/sessions/LoadStarredAndReservedSessionsUseCaseTest.kt +++ b/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/sessions/LoadStarredAndReservedSessionsUseCaseTest.kt @@ -23,7 +23,6 @@ import com.google.samples.apps.iosched.shared.domain.repository.TestUserEventDat import com.google.samples.apps.iosched.shared.model.TestDataRepository import com.google.samples.apps.iosched.shared.result.Result import com.google.samples.apps.iosched.shared.result.data -import com.google.samples.apps.iosched.shared.util.SyncExecutorRule import com.google.samples.apps.iosched.test.data.MainCoroutineRule import com.google.samples.apps.iosched.test.data.TestData import com.google.samples.apps.iosched.test.data.runBlockingTest @@ -41,9 +40,6 @@ class LoadStarredAndReservedSessionsUseCaseTest { @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule var syncExecutorRule = SyncExecutorRule() - // Overrides Dispatchers.Main used in Coroutines @get:Rule var coroutineRule = MainCoroutineRule() diff --git a/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/users/FeedbackUseCaseTest.kt b/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/users/FeedbackUseCaseTest.kt index 3c456e0d4e..f3a050f93d 100644 --- a/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/users/FeedbackUseCaseTest.kt +++ b/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/users/FeedbackUseCaseTest.kt @@ -24,7 +24,6 @@ import com.google.samples.apps.iosched.shared.data.userevent.DefaultSessionAndUs import com.google.samples.apps.iosched.shared.domain.repository.TestUserEventDataSource import com.google.samples.apps.iosched.shared.model.TestDataRepository import com.google.samples.apps.iosched.shared.result.Result -import com.google.samples.apps.iosched.shared.util.SyncExecutorRule import com.google.samples.apps.iosched.test.data.MainCoroutineRule import com.google.samples.apps.iosched.test.data.TestData import com.google.samples.apps.iosched.test.data.runBlockingTest @@ -41,10 +40,6 @@ class FeedbackUseCaseTest { @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule - var syncExecutorRule = SyncExecutorRule() - // Overrides Dispatchers.Main used in Coroutines @get:Rule var coroutineRule = MainCoroutineRule() diff --git a/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/users/ReservationActionUseCaseTest.kt b/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/users/ReservationActionUseCaseTest.kt index bf1c3b75da..d12488fa75 100644 --- a/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/users/ReservationActionUseCaseTest.kt +++ b/shared/src/test/java/com/google/samples/apps/iosched/shared/domain/users/ReservationActionUseCaseTest.kt @@ -29,7 +29,6 @@ import com.google.samples.apps.iosched.shared.domain.users.ReservationRequestAct import com.google.samples.apps.iosched.shared.domain.users.ReservationRequestAction.RequestAction import com.google.samples.apps.iosched.shared.notifications.SessionAlarmManager import com.google.samples.apps.iosched.shared.result.Result -import com.google.samples.apps.iosched.shared.util.SyncExecutorRule import com.google.samples.apps.iosched.test.data.MainCoroutineRule import com.google.samples.apps.iosched.test.data.TestData import com.google.samples.apps.iosched.test.data.runBlockingTest @@ -52,10 +51,6 @@ class ReservationActionUseCaseTest { @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - // Executes tasks in a synchronous [TaskScheduler] - @get:Rule - var syncExecutorRule = SyncExecutorRule() - // Overrides Dispatchers.Main used in Coroutines @get:Rule var coroutineRule = MainCoroutineRule() diff --git a/shared/src/test/java/com/google/samples/apps/iosched/shared/util/SyncExecutorRule.kt b/shared/src/test/java/com/google/samples/apps/iosched/shared/util/SyncExecutorRule.kt deleted file mode 100644 index 366b76199e..0000000000 --- a/shared/src/test/java/com/google/samples/apps/iosched/shared/util/SyncExecutorRule.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.samples.apps.iosched.shared.util - -import com.google.samples.apps.iosched.shared.domain.internal.DefaultScheduler -import com.google.samples.apps.iosched.shared.domain.internal.SyncScheduler -import org.junit.rules.TestWatcher -import org.junit.runner.Description - -/** - * Rule to be used in tests that sets a synchronous task scheduler used to avoid race conditions. - */ -class SyncExecutorRule : TestWatcher() { - override fun starting(description: Description?) { - super.starting(description) - DefaultScheduler.setDelegate(SyncScheduler) - } - - override fun finished(description: Description?) { - super.finished(description) - DefaultScheduler.setDelegate(null) - } -} diff --git a/shared/src/test/java/com/google/samples/apps/iosched/test/util/SyncTaskExecutorRule.kt b/shared/src/test/java/com/google/samples/apps/iosched/test/util/SyncTaskExecutorRule.kt deleted file mode 100644 index d2a2b340b5..0000000000 --- a/shared/src/test/java/com/google/samples/apps/iosched/test/util/SyncTaskExecutorRule.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2018 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.samples.apps.iosched.test.util - -import com.google.samples.apps.iosched.shared.domain.internal.DefaultScheduler -import com.google.samples.apps.iosched.shared.domain.internal.SyncScheduler -import org.junit.rules.TestWatcher -import org.junit.runner.Description - -/** - * Rule to be used in tests that sets a synchronous task scheduler used to avoid race conditions. - */ -class SyncTaskExecutorRule : TestWatcher() { - override fun starting(description: Description?) { - super.starting(description) - DefaultScheduler.setDelegate(SyncScheduler) - } - - override fun finished(description: Description?) { - super.finished(description) - DefaultScheduler.setDelegate(null) - } -} diff --git a/test-shared/src/main/java/com/google/samples/apps/iosched/test/data/MainCoroutineRule.kt b/test-shared/src/main/java/com/google/samples/apps/iosched/test/data/MainCoroutineRule.kt index ab87c5f783..193c937c36 100644 --- a/test-shared/src/main/java/com/google/samples/apps/iosched/test/data/MainCoroutineRule.kt +++ b/test-shared/src/main/java/com/google/samples/apps/iosched/test/data/MainCoroutineRule.kt @@ -16,6 +16,7 @@ package com.google.samples.apps.iosched.test.data +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.TestCoroutineDispatcher import kotlinx.coroutines.test.resetMain @@ -44,3 +45,8 @@ fun MainCoroutineRule.runBlockingTest(block: suspend () -> Unit) = this.testDispatcher.runBlockingTest { block() } + +/** + * Creates a new [CoroutineScope] with the rule's testDispatcher + */ +fun MainCoroutineRule.CoroutineScope(): CoroutineScope = CoroutineScope(testDispatcher)