diff --git a/.github/workflows/Build.yaml b/.github/workflows/Build.yaml index 0389dcf56..a3b328dcb 100644 --- a/.github/workflows/Build.yaml +++ b/.github/workflows/Build.yaml @@ -100,7 +100,7 @@ jobs: disable-animations: true disk-size: 6000M heap-size: 600M - script: ./gradlew connectedDemoDebugAndroidTest -x :benchmark:connectedDemoBenchmarkAndroidTest --daemon + script: ./gradlew connectedDemoDebugAndroidTest --daemon - name: Upload test reports if: always() diff --git a/benchmarks/src/main/java/com/google/samples/apps/nowinandroid/GeneralActions.kt b/benchmarks/src/main/java/com/google/samples/apps/nowinandroid/GeneralActions.kt new file mode 100644 index 000000000..48472e523 --- /dev/null +++ b/benchmarks/src/main/java/com/google/samples/apps/nowinandroid/GeneralActions.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * 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.nowinandroid + +import android.Manifest.permission +import android.os.Build.VERSION.SDK_INT +import android.os.Build.VERSION_CODES.TIRAMISU +import androidx.benchmark.macro.MacrobenchmarkScope + +/** + * Because the app under test is different from the one running the instrumentation test, + * the permission has to be granted manually by either: + * + * - tapping the Allow button + * ```kotlin + * val obj = By.text("Allow") + * val dialog = device.wait(Until.findObject(obj), TIMEOUT) + * dialog?.let { + * it.click() + * device.wait(Until.gone(obj), 5_000) + * } + * ``` + * - or (preferred) executing the grant command on the target package. + */ +fun MacrobenchmarkScope.allowNotifications() { + if (SDK_INT >= TIRAMISU) { + val command = "pm grant $packageName ${permission.POST_NOTIFICATIONS}" + device.executeShellCommand(command) + } +} diff --git a/benchmarks/src/main/java/com/google/samples/apps/nowinandroid/foryou/ScrollForYouFeedBenchmark.kt b/benchmarks/src/main/java/com/google/samples/apps/nowinandroid/foryou/ScrollForYouFeedBenchmark.kt index f8945a31c..3008fdc0d 100644 --- a/benchmarks/src/main/java/com/google/samples/apps/nowinandroid/foryou/ScrollForYouFeedBenchmark.kt +++ b/benchmarks/src/main/java/com/google/samples/apps/nowinandroid/foryou/ScrollForYouFeedBenchmark.kt @@ -22,6 +22,7 @@ import androidx.benchmark.macro.StartupMode import androidx.benchmark.macro.junit4.MacrobenchmarkRule import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner import com.google.samples.apps.nowinandroid.PACKAGE_NAME +import com.google.samples.apps.nowinandroid.allowNotifications import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -47,6 +48,7 @@ class ScrollForYouFeedBenchmark { // Start the app pressHome() startActivityAndWait() + allowNotifications() }, ) { forYouWaitForContent() diff --git a/benchmarks/src/main/java/com/google/samples/apps/nowinandroid/interests/TopicsScreenRecompositionBenchmark.kt b/benchmarks/src/main/java/com/google/samples/apps/nowinandroid/interests/TopicsScreenRecompositionBenchmark.kt index 24bd233ea..0030386b7 100644 --- a/benchmarks/src/main/java/com/google/samples/apps/nowinandroid/interests/TopicsScreenRecompositionBenchmark.kt +++ b/benchmarks/src/main/java/com/google/samples/apps/nowinandroid/interests/TopicsScreenRecompositionBenchmark.kt @@ -23,6 +23,7 @@ import androidx.benchmark.macro.junit4.MacrobenchmarkRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.uiautomator.By import com.google.samples.apps.nowinandroid.PACKAGE_NAME +import com.google.samples.apps.nowinandroid.allowNotifications import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -47,7 +48,7 @@ class TopicsScreenRecompositionBenchmark { // Start the app pressHome() startActivityAndWait() - + allowNotifications() // Navigate to interests screen device.findObject(By.text("Interests")).click() device.waitForIdle() diff --git a/core/common/src/main/java/com/google/samples/apps/nowinandroid/core/network/di/CoroutineScopesModule.kt b/core/common/src/main/java/com/google/samples/apps/nowinandroid/core/network/di/CoroutineScopesModule.kt index b73c63568..c265394a8 100644 --- a/core/common/src/main/java/com/google/samples/apps/nowinandroid/core/network/di/CoroutineScopesModule.kt +++ b/core/common/src/main/java/com/google/samples/apps/nowinandroid/core/network/di/CoroutineScopesModule.kt @@ -17,7 +17,7 @@ package com.google.samples.apps.nowinandroid.core.network.di import com.google.samples.apps.nowinandroid.core.network.Dispatcher -import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.IO +import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.Default import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -25,15 +25,20 @@ import dagger.hilt.components.SingletonComponent import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob +import javax.inject.Qualifier import javax.inject.Singleton +@Retention(AnnotationRetention.RUNTIME) +@Qualifier +annotation class ApplicationScope + @Module @InstallIn(SingletonComponent::class) object CoroutineScopesModule { @Provides @Singleton - @Dispatcher(IO) - fun providesIOCoroutineScope( - @Dispatcher(IO) ioDispatcher: CoroutineDispatcher, - ): CoroutineScope = CoroutineScope(SupervisorJob() + ioDispatcher) + @ApplicationScope + fun providesCoroutineScope( + @Dispatcher(Default) dispatcher: CoroutineDispatcher, + ): CoroutineScope = CoroutineScope(SupervisorJob() + dispatcher) } diff --git a/core/data-test/build.gradle.kts b/core/data-test/build.gradle.kts index f50e7b4b8..dfc224e19 100644 --- a/core/data-test/build.gradle.kts +++ b/core/data-test/build.gradle.kts @@ -25,4 +25,5 @@ android { dependencies { api(project(":core:data")) implementation(project(":core:testing")) + implementation(project(":core:common")) } diff --git a/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/DefaultSearchContentsRepository.kt b/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/DefaultSearchContentsRepository.kt index 40b170cbe..dc3caa143 100644 --- a/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/DefaultSearchContentsRepository.kt +++ b/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/DefaultSearchContentsRepository.kt @@ -20,6 +20,7 @@ import com.google.samples.apps.nowinandroid.core.database.dao.NewsResourceDao import com.google.samples.apps.nowinandroid.core.database.dao.NewsResourceFtsDao import com.google.samples.apps.nowinandroid.core.database.dao.TopicDao import com.google.samples.apps.nowinandroid.core.database.dao.TopicFtsDao +import com.google.samples.apps.nowinandroid.core.database.model.PopulatedNewsResource import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel import com.google.samples.apps.nowinandroid.core.database.model.asFtsEntity import com.google.samples.apps.nowinandroid.core.model.data.SearchResult @@ -29,6 +30,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.withContext @@ -45,7 +47,12 @@ class DefaultSearchContentsRepository @Inject constructor( override suspend fun populateFtsData() { withContext(ioDispatcher) { newsResourceFtsDao.insertAll( - newsResourceDao.getOneOffNewsResources().map { it.asFtsEntity() }, + newsResourceDao.getNewsResources( + useFilterTopicIds = false, + useFilterNewsIds = false, + ) + .first() + .map(PopulatedNewsResource::asFtsEntity), ) topicFtsDao.insertAll(topicDao.getOneOffTopicEntities().map { it.asFtsEntity() }) } diff --git a/core/data/src/test/java/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestNewsResourceDao.kt b/core/data/src/test/java/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestNewsResourceDao.kt index 09af77213..d5d8932e7 100644 --- a/core/data/src/test/java/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestNewsResourceDao.kt +++ b/core/data/src/test/java/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestNewsResourceDao.kt @@ -67,8 +67,6 @@ class TestNewsResourceDao : NewsResourceDao { result } - override suspend fun getOneOffNewsResources(): List = emptyList() - override suspend fun insertOrIgnoreNewsResources( entities: List, ): List { diff --git a/core/database/src/main/java/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceDao.kt b/core/database/src/main/java/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceDao.kt index b5949c6d2..a05507a8b 100644 --- a/core/database/src/main/java/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceDao.kt +++ b/core/database/src/main/java/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceDao.kt @@ -65,10 +65,6 @@ interface NewsResourceDao { filterNewsIds: Set = emptySet(), ): Flow> - @Transaction - @Query(value = "SELECT * FROM news_resources ORDER BY publish_date DESC") - suspend fun getOneOffNewsResources(): List - /** * Inserts [entities] into the db if they don't exist, and ignores those that do */ diff --git a/core/datastore-test/build.gradle.kts b/core/datastore-test/build.gradle.kts index c7c423c25..193c49da7 100644 --- a/core/datastore-test/build.gradle.kts +++ b/core/datastore-test/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { api(project(":core:datastore")) api(libs.androidx.dataStore.core) + implementation(libs.protobuf.kotlin.lite) implementation(project(":core:common")) implementation(project(":core:testing")) } diff --git a/core/datastore-test/src/main/java/com/google/samples/apps/nowinandroid/core/datastore/test/TestDataStoreModule.kt b/core/datastore-test/src/main/java/com/google/samples/apps/nowinandroid/core/datastore/test/TestDataStoreModule.kt index cb7e38db7..b86003e83 100644 --- a/core/datastore-test/src/main/java/com/google/samples/apps/nowinandroid/core/datastore/test/TestDataStoreModule.kt +++ b/core/datastore-test/src/main/java/com/google/samples/apps/nowinandroid/core/datastore/test/TestDataStoreModule.kt @@ -21,8 +21,7 @@ import androidx.datastore.core.DataStoreFactory import com.google.samples.apps.nowinandroid.core.datastore.UserPreferences import com.google.samples.apps.nowinandroid.core.datastore.UserPreferencesSerializer import com.google.samples.apps.nowinandroid.core.datastore.di.DataStoreModule -import com.google.samples.apps.nowinandroid.core.network.Dispatcher -import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.IO +import com.google.samples.apps.nowinandroid.core.network.di.ApplicationScope import dagger.Module import dagger.Provides import dagger.hilt.components.SingletonComponent @@ -41,12 +40,12 @@ object TestDataStoreModule { @Provides @Singleton fun providesUserPreferencesDataStore( - @Dispatcher(IO) ioScope: CoroutineScope, + @ApplicationScope scope: CoroutineScope, userPreferencesSerializer: UserPreferencesSerializer, tmpFolder: TemporaryFolder, ): DataStore = tmpFolder.testUserPreferencesDataStore( - coroutineScope = ioScope, + coroutineScope = scope, userPreferencesSerializer = userPreferencesSerializer, ) } diff --git a/core/datastore/src/main/java/com/google/samples/apps/nowinandroid/core/datastore/di/DataStoreModule.kt b/core/datastore/src/main/java/com/google/samples/apps/nowinandroid/core/datastore/di/DataStoreModule.kt index 89a6eb734..ac9eaf767 100644 --- a/core/datastore/src/main/java/com/google/samples/apps/nowinandroid/core/datastore/di/DataStoreModule.kt +++ b/core/datastore/src/main/java/com/google/samples/apps/nowinandroid/core/datastore/di/DataStoreModule.kt @@ -25,11 +25,13 @@ import com.google.samples.apps.nowinandroid.core.datastore.UserPreferences import com.google.samples.apps.nowinandroid.core.datastore.UserPreferencesSerializer import com.google.samples.apps.nowinandroid.core.network.Dispatcher import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.IO +import com.google.samples.apps.nowinandroid.core.network.di.ApplicationScope import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import javax.inject.Singleton @@ -41,12 +43,13 @@ object DataStoreModule { @Singleton fun providesUserPreferencesDataStore( @ApplicationContext context: Context, - @Dispatcher(IO) ioScope: CoroutineScope, + @Dispatcher(IO) ioDispatcher: CoroutineDispatcher, + @ApplicationScope scope: CoroutineScope, userPreferencesSerializer: UserPreferencesSerializer, ): DataStore = DataStoreFactory.create( serializer = userPreferencesSerializer, - scope = ioScope, + scope = CoroutineScope(scope.coroutineContext + ioDispatcher), migrations = listOf( IntToStringIdsMigration, ), diff --git a/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/di/TestDispatchersModule.kt b/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/di/TestDispatchersModule.kt index a5eb506ae..f2134105a 100644 --- a/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/di/TestDispatchersModule.kt +++ b/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/di/TestDispatchersModule.kt @@ -17,6 +17,7 @@ package com.google.samples.apps.nowinandroid.core.testing.di import com.google.samples.apps.nowinandroid.core.network.Dispatcher +import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.Default import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.IO import com.google.samples.apps.nowinandroid.core.network.di.DispatchersModule import dagger.Module @@ -35,4 +36,10 @@ object TestDispatchersModule { @Provides @Dispatcher(IO) fun providesIODispatcher(testDispatcher: TestDispatcher): CoroutineDispatcher = testDispatcher + + @Provides + @Dispatcher(Default) + fun providesDefaultDispatcher( + testDispatcher: TestDispatcher, + ): CoroutineDispatcher = testDispatcher }