From 395b9853df15de34ad76d80e054221583e2a8376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Mlynari=C4=8D?= Date: Mon, 29 Jan 2024 15:59:12 +0100 Subject: [PATCH 01/16] Add Composition Tracing Change-Id: I3ad60930de2fab71a700a0c2df3f0a1f566cafd8 --- app/build.gradle.kts | 1 + gradle/libs.versions.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 38166f7ff..aded864ee 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -104,6 +104,7 @@ dependencies { implementation(libs.androidx.core.splashscreen) implementation(libs.androidx.tracing.ktx) implementation(libs.androidx.lifecycle.runtimeCompose) + implementation(libs.androidx.compose.runtime.tracing) implementation(libs.androidx.compose.material3.windowSizeClass) implementation(libs.androidx.navigation.compose) implementation(libs.androidx.profileinstaller) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 795510bce..1ef4cc225 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -72,6 +72,7 @@ androidx-compose-material-iconsExtended = { group = "androidx.compose.material", androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-compose-material3-windowSizeClass = { group = "androidx.compose.material3", name = "material3-window-size-class" } androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime" } +androidx-compose-runtime-tracing = { group = "androidx.compose.runtime", name = "runtime-tracing", version.ref = "androidxComposeRuntimeTracing" } androidx-compose-ui-test = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-compose-ui-testManifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } From cb00d2c8cbd9c783f220bc99ad1a7e3d72e24007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Mlynari=C4=8D?= Date: Mon, 29 Jan 2024 22:58:47 +0100 Subject: [PATCH 02/16] Add TimeZoneMonitor to prevent multiple TimeZoneBroadcastReceivers This way, we can save ~1ms per composed item on screen. Change-Id: Ib9ada3cea53304fca4fb2b36c48c175845bc683d --- .../apps/nowinandroid/ui/NavigationUiTest.kt | 86 +++++------------ .../apps/nowinandroid/ui/NiaAppStateTest.kt | 31 ++++++ .../samples/apps/nowinandroid/MainActivity.kt | 27 ++++-- .../samples/apps/nowinandroid/ui/NiaApp.kt | 31 +++--- .../apps/nowinandroid/ui/NiaAppState.kt | 28 ++++-- .../ui/NiaAppScreenSizesScreenshotTests.kt | 8 +- .../foryou/ScrollForYouFeedBenchmark.kt | 4 + .../apps/nowinandroid/AndroidCompose.kt | 10 +- compose_compiler_config.conf | 6 ++ .../data/test/DefaultZoneIdTimeZoneMonitor.kt | 27 ++++++ .../core/data/test/TestDataModule.kt | 4 + .../nowinandroid/core/data/di/DataModule.kt | 5 + .../core/data/util/TimeZoneMonitor.kt | 94 +++++++++++++++++++ .../core/testing/util/TestTimeZoneMonitor.kt | 41 ++++++++ .../nowinandroid/core/ui/LocalTimeZone.kt | 26 +++++ .../nowinandroid/core/ui/NewsResourceCard.kt | 29 +----- .../core/ui/TimeZoneBroadcastReceiver.kt | 50 ---------- gradle/libs.versions.toml | 6 +- 18 files changed, 337 insertions(+), 176 deletions(-) create mode 100644 compose_compiler_config.conf create mode 100644 core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/DefaultZoneIdTimeZoneMonitor.kt create mode 100644 core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt create mode 100644 core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/TestTimeZoneMonitor.kt create mode 100644 core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/LocalTimeZone.kt delete mode 100644 core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/TimeZoneBroadcastReceiver.kt diff --git a/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NavigationUiTest.kt b/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NavigationUiTest.kt index d92390918..5d2e12b5c 100644 --- a/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NavigationUiTest.kt +++ b/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NavigationUiTest.kt @@ -19,14 +19,17 @@ package com.google.samples.apps.nowinandroid.ui import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi import androidx.compose.material3.windowsizeclass.WindowSizeClass +import androidx.compose.runtime.Composable import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import com.google.accompanist.testharness.TestHarness import com.google.samples.apps.nowinandroid.core.data.repository.CompositeUserNewsResourceRepository import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor +import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor import com.google.samples.apps.nowinandroid.core.rules.GrantPostNotificationsPermissionRule import com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepository import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository @@ -81,6 +84,9 @@ class NavigationUiTest { @Inject lateinit var networkMonitor: NetworkMonitor + @Inject + lateinit var timeZoneMonitor: TimeZoneMonitor + @Before fun setup() { hiltRule.inject() @@ -91,13 +97,7 @@ class NavigationUiTest { composeTestRule.setContent { TestHarness(size = DpSize(400.dp, 400.dp)) { BoxWithConstraints { - NiaApp( - windowSizeClass = WindowSizeClass.calculateFromSize( - DpSize(maxWidth, maxHeight), - ), - networkMonitor = networkMonitor, - userNewsResourceRepository = userNewsResourceRepository, - ) + NiaApp(fakeAppState(maxWidth, maxHeight)) } } } @@ -111,13 +111,7 @@ class NavigationUiTest { composeTestRule.setContent { TestHarness(size = DpSize(610.dp, 400.dp)) { BoxWithConstraints { - NiaApp( - windowSizeClass = WindowSizeClass.calculateFromSize( - DpSize(maxWidth, maxHeight), - ), - networkMonitor = networkMonitor, - userNewsResourceRepository = userNewsResourceRepository, - ) + NiaApp(fakeAppState(maxWidth, maxHeight)) } } } @@ -131,13 +125,7 @@ class NavigationUiTest { composeTestRule.setContent { TestHarness(size = DpSize(900.dp, 400.dp)) { BoxWithConstraints { - NiaApp( - windowSizeClass = WindowSizeClass.calculateFromSize( - DpSize(maxWidth, maxHeight), - ), - networkMonitor = networkMonitor, - userNewsResourceRepository = userNewsResourceRepository, - ) + NiaApp(fakeAppState(maxWidth, maxHeight)) } } } @@ -151,13 +139,7 @@ class NavigationUiTest { composeTestRule.setContent { TestHarness(size = DpSize(400.dp, 500.dp)) { BoxWithConstraints { - NiaApp( - windowSizeClass = WindowSizeClass.calculateFromSize( - DpSize(maxWidth, maxHeight), - ), - networkMonitor = networkMonitor, - userNewsResourceRepository = userNewsResourceRepository, - ) + NiaApp(fakeAppState(maxWidth, maxHeight)) } } } @@ -171,13 +153,7 @@ class NavigationUiTest { composeTestRule.setContent { TestHarness(size = DpSize(610.dp, 500.dp)) { BoxWithConstraints { - NiaApp( - windowSizeClass = WindowSizeClass.calculateFromSize( - DpSize(maxWidth, maxHeight), - ), - networkMonitor = networkMonitor, - userNewsResourceRepository = userNewsResourceRepository, - ) + NiaApp(fakeAppState(maxWidth, maxHeight)) } } } @@ -191,13 +167,7 @@ class NavigationUiTest { composeTestRule.setContent { TestHarness(size = DpSize(900.dp, 500.dp)) { BoxWithConstraints { - NiaApp( - windowSizeClass = WindowSizeClass.calculateFromSize( - DpSize(maxWidth, maxHeight), - ), - networkMonitor = networkMonitor, - userNewsResourceRepository = userNewsResourceRepository, - ) + NiaApp(fakeAppState(maxWidth, maxHeight)) } } } @@ -211,13 +181,7 @@ class NavigationUiTest { composeTestRule.setContent { TestHarness(size = DpSize(400.dp, 1000.dp)) { BoxWithConstraints { - NiaApp( - windowSizeClass = WindowSizeClass.calculateFromSize( - DpSize(maxWidth, maxHeight), - ), - networkMonitor = networkMonitor, - userNewsResourceRepository = userNewsResourceRepository, - ) + NiaApp(fakeAppState(maxWidth, maxHeight)) } } } @@ -231,13 +195,7 @@ class NavigationUiTest { composeTestRule.setContent { TestHarness(size = DpSize(610.dp, 1000.dp)) { BoxWithConstraints { - NiaApp( - windowSizeClass = WindowSizeClass.calculateFromSize( - DpSize(maxWidth, maxHeight), - ), - networkMonitor = networkMonitor, - userNewsResourceRepository = userNewsResourceRepository, - ) + NiaApp(fakeAppState(maxWidth, maxHeight)) } } } @@ -251,13 +209,7 @@ class NavigationUiTest { composeTestRule.setContent { TestHarness(size = DpSize(900.dp, 1000.dp)) { BoxWithConstraints { - NiaApp( - windowSizeClass = WindowSizeClass.calculateFromSize( - DpSize(maxWidth, maxHeight), - ), - networkMonitor = networkMonitor, - userNewsResourceRepository = userNewsResourceRepository, - ) + NiaApp(fakeAppState(maxWidth, maxHeight)) } } } @@ -265,4 +217,12 @@ class NavigationUiTest { composeTestRule.onNodeWithTag("NiaNavRail").assertIsDisplayed() composeTestRule.onNodeWithTag("NiaBottomBar").assertDoesNotExist() } + + @Composable + private fun fakeAppState(maxWidth: Dp, maxHeight: Dp) = rememberNiaAppState( + windowSizeClass = WindowSizeClass.calculateFromSize(DpSize(maxWidth, maxHeight)), + networkMonitor = networkMonitor, + userNewsResourceRepository = userNewsResourceRepository, + timeZoneMonitor = timeZoneMonitor, + ) } diff --git a/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppStateTest.kt b/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppStateTest.kt index 1560a74eb..18afc6a09 100644 --- a/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppStateTest.kt +++ b/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppStateTest.kt @@ -34,12 +34,14 @@ import com.google.samples.apps.nowinandroid.core.data.repository.CompositeUserNe import com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepository import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository import com.google.samples.apps.nowinandroid.core.testing.util.TestNetworkMonitor +import com.google.samples.apps.nowinandroid.core.testing.util.TestTimeZoneMonitor import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test +import java.time.ZoneId import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -59,6 +61,8 @@ class NiaAppStateTest { // Create the test dependencies. private val networkMonitor = TestNetworkMonitor() + private val timeZoneMonitor = TestTimeZoneMonitor() + private val userNewsResourceRepository = CompositeUserNewsResourceRepository(TestNewsRepository(), TestUserDataRepository()) @@ -78,6 +82,7 @@ class NiaAppStateTest { windowSizeClass = getCompactWindowClass(), networkMonitor = networkMonitor, userNewsResourceRepository = userNewsResourceRepository, + timeZoneMonitor = timeZoneMonitor, ) } @@ -100,6 +105,7 @@ class NiaAppStateTest { windowSizeClass = getCompactWindowClass(), networkMonitor = networkMonitor, userNewsResourceRepository = userNewsResourceRepository, + timeZoneMonitor = timeZoneMonitor, ) } @@ -118,6 +124,7 @@ class NiaAppStateTest { windowSizeClass = getCompactWindowClass(), networkMonitor = networkMonitor, userNewsResourceRepository = userNewsResourceRepository, + timeZoneMonitor = timeZoneMonitor, ) } @@ -134,6 +141,7 @@ class NiaAppStateTest { windowSizeClass = WindowSizeClass.calculateFromSize(DpSize(800.dp, 800.dp)), networkMonitor = networkMonitor, userNewsResourceRepository = userNewsResourceRepository, + timeZoneMonitor = timeZoneMonitor, ) } @@ -150,6 +158,7 @@ class NiaAppStateTest { windowSizeClass = WindowSizeClass.calculateFromSize(DpSize(900.dp, 1200.dp)), networkMonitor = networkMonitor, userNewsResourceRepository = userNewsResourceRepository, + timeZoneMonitor = timeZoneMonitor, ) } @@ -166,6 +175,7 @@ class NiaAppStateTest { windowSizeClass = WindowSizeClass.calculateFromSize(DpSize(900.dp, 1200.dp)), networkMonitor = networkMonitor, userNewsResourceRepository = userNewsResourceRepository, + timeZoneMonitor = timeZoneMonitor, ) } @@ -177,6 +187,27 @@ class NiaAppStateTest { ) } + @Test + fun niaAppState_differentTZ_withTimeZoneMonitorChange() = runTest(UnconfinedTestDispatcher()) { + composeTestRule.setContent { + state = NiaAppState( + navController = NavHostController(LocalContext.current), + coroutineScope = backgroundScope, + windowSizeClass = getCompactWindowClass(), + networkMonitor = networkMonitor, + userNewsResourceRepository = userNewsResourceRepository, + timeZoneMonitor = timeZoneMonitor, + ) + } + val changedTz = ZoneId.of("Europe/Prague") + backgroundScope.launch { state.currentTimeZone.collect() } + timeZoneMonitor.setTimeZone(changedTz) + assertEquals( + changedTz, + state.currentTimeZone.value, + ) + } + private fun getCompactWindowClass() = WindowSizeClass.calculateFromSize(DpSize(500.dp, 300.dp)) } diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt index 6ce134ef4..779b6edbe 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt @@ -28,6 +28,7 @@ import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -42,10 +43,13 @@ import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper import com.google.samples.apps.nowinandroid.core.analytics.LocalAnalyticsHelper import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourceRepository import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor +import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand +import com.google.samples.apps.nowinandroid.core.ui.LocalTimeZone import com.google.samples.apps.nowinandroid.ui.NiaApp +import com.google.samples.apps.nowinandroid.ui.rememberNiaAppState import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.onEach @@ -67,6 +71,9 @@ class MainActivity : ComponentActivity() { @Inject lateinit var networkMonitor: NetworkMonitor + @Inject + lateinit var timeZoneMonitor: TimeZoneMonitor + @Inject lateinit var analyticsHelper: AnalyticsHelper @@ -126,17 +133,25 @@ class MainActivity : ComponentActivity() { onDispose {} } - CompositionLocalProvider(LocalAnalyticsHelper provides analyticsHelper) { + val appState = rememberNiaAppState( + windowSizeClass = calculateWindowSizeClass(this), + networkMonitor = networkMonitor, + userNewsResourceRepository = userNewsResourceRepository, + timeZoneMonitor = timeZoneMonitor, + ) + + val currentTimeZone by appState.currentTimeZone.collectAsState() + + CompositionLocalProvider( + LocalAnalyticsHelper provides analyticsHelper, + LocalTimeZone provides currentTimeZone, + ) { NiaTheme( darkTheme = darkTheme, androidTheme = shouldUseAndroidTheme(uiState), disableDynamicTheming = shouldDisableDynamicTheming(uiState), ) { - NiaApp( - networkMonitor = networkMonitor, - windowSizeClass = calculateWindowSizeClass(this), - userNewsResourceRepository = userNewsResourceRepository, - ) + NiaApp(appState) } } } diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt index 2beda99ea..b2eabe2ed 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt @@ -39,7 +39,6 @@ import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarResult.ActionPerformed import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.windowsizeclass.WindowSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -62,8 +61,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavDestination import androidx.navigation.NavDestination.Companion.hierarchy import com.google.samples.apps.nowinandroid.R -import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourceRepository -import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaBackground import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaGradientBackground import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaNavigationBar @@ -85,16 +82,7 @@ import com.google.samples.apps.nowinandroid.feature.settings.R as settingsR ExperimentalComposeUiApi::class, ) @Composable -fun NiaApp( - windowSizeClass: WindowSizeClass, - networkMonitor: NetworkMonitor, - userNewsResourceRepository: UserNewsResourceRepository, - appState: NiaAppState = rememberNiaAppState( - networkMonitor = networkMonitor, - windowSizeClass = windowSizeClass, - userNewsResourceRepository = userNewsResourceRepository, - ), -) { +fun NiaApp(appState: NiaAppState) { val shouldShowGradientBackground = appState.currentTopLevelDestination == TopLevelDestination.FOR_YOU var showSettingsDialog by rememberSaveable { mutableStateOf(false) } @@ -195,13 +183,16 @@ fun NiaApp( ) } - NiaNavHost(appState = appState, onShowSnackbar = { message, action -> - snackbarHostState.showSnackbar( - message = message, - actionLabel = action, - duration = Short, - ) == ActionPerformed - }) + NiaNavHost( + appState = appState, + onShowSnackbar = { message, action -> + snackbarHostState.showSnackbar( + message = message, + actionLabel = action, + duration = Short, + ) == ActionPerformed + }, + ) } // TODO: We may want to add padding or spacer when the snackbar is shown so that diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt index 7b66efb06..ffd0c16ec 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt @@ -32,6 +32,7 @@ import androidx.navigation.navOptions import androidx.tracing.trace import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourceRepository import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor +import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor import com.google.samples.apps.nowinandroid.core.ui.TrackDisposableJank import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.BOOKMARKS_ROUTE import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.navigateToBookmarks @@ -50,12 +51,14 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import java.time.ZoneId @Composable fun rememberNiaAppState( windowSizeClass: WindowSizeClass, networkMonitor: NetworkMonitor, userNewsResourceRepository: UserNewsResourceRepository, + timeZoneMonitor: TimeZoneMonitor, coroutineScope: CoroutineScope = rememberCoroutineScope(), navController: NavHostController = rememberNavController(), ): NiaAppState { @@ -66,13 +69,15 @@ fun rememberNiaAppState( windowSizeClass, networkMonitor, userNewsResourceRepository, + timeZoneMonitor, ) { NiaAppState( - navController, - coroutineScope, - windowSizeClass, - networkMonitor, - userNewsResourceRepository, + navController = navController, + coroutineScope = coroutineScope, + windowSizeClass = windowSizeClass, + networkMonitor = networkMonitor, + userNewsResourceRepository = userNewsResourceRepository, + timeZoneMonitor = timeZoneMonitor, ) } } @@ -80,10 +85,11 @@ fun rememberNiaAppState( @Stable class NiaAppState( val navController: NavHostController, - val coroutineScope: CoroutineScope, + coroutineScope: CoroutineScope, val windowSizeClass: WindowSizeClass, networkMonitor: NetworkMonitor, userNewsResourceRepository: UserNewsResourceRepository, + private val timeZoneMonitor: TimeZoneMonitor, ) { val currentDestination: NavDestination? @Composable get() = navController @@ -127,12 +133,20 @@ class NiaAppState( FOR_YOU.takeIf { forYouNewsResources.any { !it.hasBeenViewed } }, BOOKMARKS.takeIf { bookmarkedNewsResources.any { !it.hasBeenViewed } }, ) - }.stateIn( + } + .stateIn( coroutineScope, SharingStarted.WhileSubscribed(5_000), initialValue = emptySet(), ) + val currentTimeZone = timeZoneMonitor.currentZoneId + .stateIn( + coroutineScope, + SharingStarted.WhileSubscribed(5_000), + ZoneId.systemDefault(), + ) + /** * UI logic for navigating to a top level destination in the app. Top level destinations have * only one copy of the destination of the back stack, and save and restore state whenever you diff --git a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt index dcbc1e5c0..f3514b02f 100644 --- a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt +++ b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt @@ -37,6 +37,7 @@ import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepositor import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourceRepository import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor +import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor import com.google.samples.apps.nowinandroid.core.testing.util.DefaultRoborazziOptions import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity import dagger.hilt.android.testing.BindValue @@ -93,6 +94,9 @@ class NiaAppScreenSizesScreenshotTests { @Inject lateinit var networkMonitor: NetworkMonitor + @Inject + lateinit var timeZoneMonitor: TimeZoneMonitor + @Inject lateinit var userDataRepository: UserDataRepository @@ -140,13 +144,15 @@ class NiaAppScreenSizesScreenshotTests { ) { TestHarness(size = DpSize(width, height)) { BoxWithConstraints { - NiaApp( + val fakeAppState = rememberNiaAppState( windowSizeClass = WindowSizeClass.calculateFromSize( DpSize(maxWidth, maxHeight), ), networkMonitor = networkMonitor, userNewsResourceRepository = userNewsResourceRepository, + timeZoneMonitor = timeZoneMonitor, ) + NiaApp(fakeAppState) } } } diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/foryou/ScrollForYouFeedBenchmark.kt b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/foryou/ScrollForYouFeedBenchmark.kt index 6d0091cd4..24a50dd79 100644 --- a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/foryou/ScrollForYouFeedBenchmark.kt +++ b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/foryou/ScrollForYouFeedBenchmark.kt @@ -38,6 +38,9 @@ class ScrollForYouFeedBenchmark { @Test fun scrollFeedCompilationBaselineProfile() = scrollFeed(CompilationMode.Partial()) + @Test + fun scrollFeedCompilationFull() = scrollFeed(CompilationMode.Full()) + private fun scrollFeed(compilationMode: CompilationMode) = benchmarkRule.measureRepeated( packageName = PACKAGE_NAME, metrics = listOf(FrameTimingMetric()), @@ -55,3 +58,4 @@ class ScrollForYouFeedBenchmark { forYouScrollFeedDownUp() } } + diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidCompose.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidCompose.kt index 039987cac..72d37db1b 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidCompose.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidCompose.kt @@ -53,7 +53,8 @@ internal fun Project.configureAndroidCompose( tasks.withType().configureEach { kotlinOptions { - freeCompilerArgs = freeCompilerArgs + buildComposeMetricsParameters() + freeCompilerArgs += buildComposeMetricsParameters() + freeCompilerArgs += stabilityConfiguration() } } } @@ -68,7 +69,7 @@ private fun Project.buildComposeMetricsParameters(): List { val metricsFolder = buildDir.resolve("compose-metrics").resolve(relativePath) metricParameters.add("-P") metricParameters.add( - "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" + metricsFolder.absolutePath + "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" + metricsFolder.absolutePath, ) } @@ -83,3 +84,8 @@ private fun Project.buildComposeMetricsParameters(): List { } return metricParameters.toList() } + +private fun Project.stabilityConfiguration() = listOf( + "-P", + "plugin:androidx.compose.compiler.plugins.kotlin:stabilityConfigurationPath=${project.rootDir.absolutePath}/compose_compiler_config.conf", +) diff --git a/compose_compiler_config.conf b/compose_compiler_config.conf new file mode 100644 index 000000000..2341256f4 --- /dev/null +++ b/compose_compiler_config.conf @@ -0,0 +1,6 @@ +// This file contains classes (with possible wildcards) that the Compose Compiler will treat as stable. +// It allows us to define classes that our not part of our codebase without wrapping them in a stable class. +// For more information, check https://developer.android.com/jetpack/compose/performance/stability/fix#configuration-file + +java.time.ZoneId +java.time.ZoneOffset diff --git a/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/DefaultZoneIdTimeZoneMonitor.kt b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/DefaultZoneIdTimeZoneMonitor.kt new file mode 100644 index 000000000..ee8fe97a3 --- /dev/null +++ b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/DefaultZoneIdTimeZoneMonitor.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2024 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.core.data.test + +import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import java.time.ZoneId +import javax.inject.Inject + +class DefaultZoneIdTimeZoneMonitor @Inject constructor() : TimeZoneMonitor { + override val currentZoneId: Flow = flowOf(ZoneId.of("Europe/Warsaw")) +} diff --git a/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/TestDataModule.kt b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/TestDataModule.kt index 2ec2bcf9c..5bd2b8a43 100644 --- a/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/TestDataModule.kt +++ b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/TestDataModule.kt @@ -28,6 +28,7 @@ import com.google.samples.apps.nowinandroid.core.data.repository.fake.FakeSearch import com.google.samples.apps.nowinandroid.core.data.repository.fake.FakeTopicsRepository import com.google.samples.apps.nowinandroid.core.data.repository.fake.FakeUserDataRepository import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor +import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor import dagger.Binds import dagger.Module import dagger.hilt.components.SingletonComponent @@ -68,4 +69,7 @@ interface TestDataModule { fun bindsNetworkMonitor( networkMonitor: AlwaysOnlineNetworkMonitor, ): NetworkMonitor + + @Binds + fun binds(impl: DefaultZoneIdTimeZoneMonitor): TimeZoneMonitor } diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/di/DataModule.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/di/DataModule.kt index e135d7f58..fa4bde8b8 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/di/DataModule.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/di/DataModule.kt @@ -28,6 +28,8 @@ import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepositor import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository import com.google.samples.apps.nowinandroid.core.data.util.ConnectivityManagerNetworkMonitor import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor +import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneBroadcastMonitor +import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -66,4 +68,7 @@ abstract class DataModule { internal abstract fun bindsNetworkMonitor( networkMonitor: ConnectivityManagerNetworkMonitor, ): NetworkMonitor + + @Binds + internal abstract fun binds(impl: TimeZoneBroadcastMonitor): TimeZoneMonitor } diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt new file mode 100644 index 000000000..33b37934a --- /dev/null +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2024 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.core.data.util + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Build.VERSION +import android.os.Build.VERSION_CODES +import androidx.core.os.trace +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.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.shareIn +import java.time.ZoneId +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Utility for reporting current timezone the device has set. + * It always emits at least once with default setting and then for each TZ change. + */ +interface TimeZoneMonitor { + val currentZoneId: Flow +} + +@Singleton +internal class TimeZoneBroadcastMonitor @Inject constructor( + @ApplicationContext private val context: Context, + @ApplicationScope appScope: CoroutineScope, + @Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher, +) : TimeZoneMonitor { + + override val currentZoneId: SharedFlow = + callbackFlow { + // Send the default time zone first. + trySend(ZoneId.systemDefault()) + + // Registers BroadcastReceiver for the TimeZone changes + val receiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action != Intent.ACTION_TIMEZONE_CHANGED) return + + val zoneIdFromIntent = if (VERSION.SDK_INT < VERSION_CODES.R) { + null + } else { + // Starting Android R we also get the new TimeZone. + intent.getStringExtra(Intent.EXTRA_TIMEZONE)?.let { zoneId -> + // We need to convert it from java.util.Timezone to java.time.ZoneId + ZoneId.of(zoneId, ZoneId.SHORT_IDS) + } + } + + // If there isn't a zoneId in the intent, fallback to the systemDefault, which should also reflect the change + trySend(zoneIdFromIntent ?: ZoneId.systemDefault()) + } + } + + trace("TimeZoneBroadcastReceiver.register") { + context.registerReceiver(receiver, IntentFilter(Intent.ACTION_TIMEZONE_CHANGED)) + } + + awaitClose { + context.unregisterReceiver(receiver) + } + } + .flowOn(ioDispatcher) + // Sharing the callback to prevent multiple BroadcastReceivers being registered + .shareIn(appScope, SharingStarted.WhileSubscribed(5_000), 1) +} diff --git a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/TestTimeZoneMonitor.kt b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/TestTimeZoneMonitor.kt new file mode 100644 index 000000000..8eb438fb1 --- /dev/null +++ b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/TestTimeZoneMonitor.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2022 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.core.testing.util + +import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import java.time.ZoneId + +class TestTimeZoneMonitor : TimeZoneMonitor { + + private val timeZoneFlow = MutableStateFlow(defaultTimeZone) + + override val currentZoneId: Flow = timeZoneFlow + + /** + * A test-only API to set the from tests. + */ + fun setTimeZone(zoneId: ZoneId) { + timeZoneFlow.value = zoneId + } + + companion object { + val defaultTimeZone: ZoneId = ZoneId.of("Europe/Warsaw") + } +} + diff --git a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/LocalTimeZone.kt b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/LocalTimeZone.kt new file mode 100644 index 000000000..cda5040bd --- /dev/null +++ b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/LocalTimeZone.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2024 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.core.ui + +import androidx.compose.runtime.compositionLocalOf +import java.time.ZoneId + +/** + * TimeZone that can be provided with the TimeZoneMonitor. + * This way, it's not needed to pass every single composable the time zone to show in UI. + */ +val LocalTimeZone = compositionLocalOf { ZoneId.systemDefault() } diff --git a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt index 9eca6b141..c922d9687 100644 --- a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt +++ b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt @@ -40,7 +40,6 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -49,7 +48,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -71,7 +69,6 @@ import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource import kotlinx.datetime.Instant import kotlinx.datetime.toJavaInstant -import java.time.ZoneId import java.time.format.DateTimeFormatter import java.time.format.FormatStyle import java.util.Locale @@ -244,27 +241,11 @@ fun NotificationDot( } @Composable -fun dateFormatted(publishDate: Instant): String { - var zoneId by remember { mutableStateOf(ZoneId.systemDefault()) } - - val context = LocalContext.current - - DisposableEffect(context) { - val receiver = TimeZoneBroadcastReceiver( - onTimeZoneChanged = { zoneId = ZoneId.systemDefault() }, - ) - receiver.register(context) - onDispose { - receiver.unregister(context) - } - } - - return DateTimeFormatter - .ofLocalizedDate(FormatStyle.MEDIUM) - .withLocale(Locale.getDefault()) - .withZone(zoneId) - .format(publishDate.toJavaInstant()) -} +fun dateFormatted(publishDate: Instant): String = DateTimeFormatter + .ofLocalizedDate(FormatStyle.MEDIUM) + .withLocale(Locale.getDefault()) + .withZone(LocalTimeZone.current) + .format(publishDate.toJavaInstant()) @Composable fun NewsResourceMetaData( diff --git a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/TimeZoneBroadcastReceiver.kt b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/TimeZoneBroadcastReceiver.kt deleted file mode 100644 index f7ae813c4..000000000 --- a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/TimeZoneBroadcastReceiver.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2022 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.core.ui - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter - -class TimeZoneBroadcastReceiver( - val onTimeZoneChanged: () -> Unit, -) : BroadcastReceiver() { - private var registered = false - - override fun onReceive(context: Context, intent: Intent) { - if (intent.action == Intent.ACTION_TIMEZONE_CHANGED) { - onTimeZoneChanged() - } - } - - fun register(context: Context) { - if (!registered) { - val filter = IntentFilter() - filter.addAction(Intent.ACTION_TIMEZONE_CHANGED) - context.registerReceiver(this, filter) - registered = true - } - } - - fun unregister(context: Context) { - if (registered) { - context.unregisterReceiver(this) - registered = false - } - } -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1ef4cc225..5bcdaf41c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ androidxActivity = "1.8.0" androidxAppCompat = "1.6.1" androidxBrowser = "1.6.0" androidxComposeBom = "2023.10.01" -androidxComposeCompiler = "1.5.7" +androidxComposeCompiler = "1.5.8" androidxComposeRuntimeTracing = "1.0.0-beta01" androidxCore = "1.12.0" androidxCoreSplashscreen = "1.0.1" @@ -40,11 +40,11 @@ hilt = "2.50" hiltExt = "1.1.0" jacoco = "0.8.7" junit4 = "4.13.2" -kotlin = "1.9.21" +kotlin = "1.9.22" kotlinxCoroutines = "1.7.3" kotlinxDatetime = "0.5.0" kotlinxSerializationJson = "1.6.0" -ksp = "1.9.21-1.0.16" +ksp = "1.9.22-1.0.16" okhttp = "4.12.0" protobuf = "3.24.4" protobufPlugin = "0.9.4" From 0b3ece1bb9629a7440aceab98e5d5b9e046831af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Mlynari=C4=8D?= Date: Tue, 30 Jan 2024 13:34:01 +0100 Subject: [PATCH 03/16] Fix spotless Change-Id: I6583fa9ed7c1563f6164653be9bbaf7904ec0215 --- .../apps/nowinandroid/foryou/ScrollForYouFeedBenchmark.kt | 1 - .../apps/nowinandroid/core/testing/util/TestTimeZoneMonitor.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/foryou/ScrollForYouFeedBenchmark.kt b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/foryou/ScrollForYouFeedBenchmark.kt index 24a50dd79..c74d79307 100644 --- a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/foryou/ScrollForYouFeedBenchmark.kt +++ b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/foryou/ScrollForYouFeedBenchmark.kt @@ -58,4 +58,3 @@ class ScrollForYouFeedBenchmark { forYouScrollFeedDownUp() } } - diff --git a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/TestTimeZoneMonitor.kt b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/TestTimeZoneMonitor.kt index 8eb438fb1..1d7f21543 100644 --- a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/TestTimeZoneMonitor.kt +++ b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/TestTimeZoneMonitor.kt @@ -38,4 +38,3 @@ class TestTimeZoneMonitor : TimeZoneMonitor { val defaultTimeZone: ZoneId = ZoneId.of("Europe/Warsaw") } } - From 1e925ed99fecb43973ba53168fc3a1266e369d34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Mlynari=C4=8D?= Date: Tue, 30 Jan 2024 20:26:17 +0100 Subject: [PATCH 04/16] Conflate TimeZoneMonitor emissions Co-authored-by: Alex Vanyo --- .../samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt index 33b37934a..04b2ef352 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt @@ -88,6 +88,7 @@ internal class TimeZoneBroadcastMonitor @Inject constructor( context.unregisterReceiver(receiver) } } + .conflate() .flowOn(ioDispatcher) // Sharing the callback to prevent multiple BroadcastReceivers being registered .shareIn(appScope, SharingStarted.WhileSubscribed(5_000), 1) From 44ae059a2be020018e926f7a787566b241d7b3bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Mlynari=C4=8D?= Date: Tue, 30 Jan 2024 20:29:12 +0100 Subject: [PATCH 05/16] Rename LocalTimeZone -> LocalZoneId + collects with lifecycle Change-Id: I429c563765a489965d0a1741e36c98a30e5d0ef3 --- .../com/google/samples/apps/nowinandroid/MainActivity.kt | 8 ++++---- .../core/ui/{LocalTimeZone.kt => LocalZoneId.kt} | 4 ++-- .../samples/apps/nowinandroid/core/ui/NewsResourceCard.kt | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) rename core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/{LocalTimeZone.kt => LocalZoneId.kt} (87%) diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt index 779b6edbe..0bf45f125 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt @@ -28,12 +28,12 @@ import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.metrics.performance.JankStats @@ -47,7 +47,7 @@ import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand -import com.google.samples.apps.nowinandroid.core.ui.LocalTimeZone +import com.google.samples.apps.nowinandroid.core.ui.LocalZoneId import com.google.samples.apps.nowinandroid.ui.NiaApp import com.google.samples.apps.nowinandroid.ui.rememberNiaAppState import dagger.hilt.android.AndroidEntryPoint @@ -140,11 +140,11 @@ class MainActivity : ComponentActivity() { timeZoneMonitor = timeZoneMonitor, ) - val currentTimeZone by appState.currentTimeZone.collectAsState() + val currentZoneId by appState.currentTimeZone.collectAsStateWithLifecycle() CompositionLocalProvider( LocalAnalyticsHelper provides analyticsHelper, - LocalTimeZone provides currentTimeZone, + LocalZoneId provides currentZoneId, ) { NiaTheme( darkTheme = darkTheme, diff --git a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/LocalTimeZone.kt b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/LocalZoneId.kt similarity index 87% rename from core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/LocalTimeZone.kt rename to core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/LocalZoneId.kt index cda5040bd..54aa7931d 100644 --- a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/LocalTimeZone.kt +++ b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/LocalZoneId.kt @@ -20,7 +20,7 @@ import androidx.compose.runtime.compositionLocalOf import java.time.ZoneId /** - * TimeZone that can be provided with the TimeZoneMonitor. + * ZoneId that can be provided with the TimeZoneMonitor. * This way, it's not needed to pass every single composable the time zone to show in UI. */ -val LocalTimeZone = compositionLocalOf { ZoneId.systemDefault() } +val LocalZoneId = compositionLocalOf { ZoneId.systemDefault() } diff --git a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt index c922d9687..9c4377b9d 100644 --- a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt +++ b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt @@ -244,7 +244,7 @@ fun NotificationDot( fun dateFormatted(publishDate: Instant): String = DateTimeFormatter .ofLocalizedDate(FormatStyle.MEDIUM) .withLocale(Locale.getDefault()) - .withZone(LocalTimeZone.current) + .withZone(LocalZoneId.current) .format(publishDate.toJavaInstant()) @Composable From a16d5f726c987641f78500e0004ff7ff028eb716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Mlynari=C4=8D?= Date: Tue, 30 Jan 2024 20:29:34 +0100 Subject: [PATCH 06/16] More readable zoneId creation Change-Id: I912dc021554e4886a5d09e63799aa5c81e8fd16a --- .../samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt index 04b2ef352..99613d0f6 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt @@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.shareIn import java.time.ZoneId +import java.util.TimeZone import javax.inject.Inject import javax.inject.Singleton @@ -72,6 +73,7 @@ internal class TimeZoneBroadcastMonitor @Inject constructor( intent.getStringExtra(Intent.EXTRA_TIMEZONE)?.let { zoneId -> // We need to convert it from java.util.Timezone to java.time.ZoneId ZoneId.of(zoneId, ZoneId.SHORT_IDS) + TimeZone.getTimeZone(zoneId).toZoneId() } } From ea1573fcc1d61713839ed5d510b4fc8d8f36b211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Mlynari=C4=8D?= Date: Wed, 31 Jan 2024 11:29:14 +0100 Subject: [PATCH 07/16] Use kotlinx.datetime.Timezone instead of java ZoneId Change-Id: I62c4d044b319a9b59e06bb42fd12971d992e8628 --- .../apps/nowinandroid/ui/NiaAppStateTest.kt | 4 ++-- .../samples/apps/nowinandroid/MainActivity.kt | 4 ++-- .../apps/nowinandroid/ui/NiaAppState.kt | 6 +++--- .../data/test/DefaultZoneIdTimeZoneMonitor.kt | 4 ++-- .../core/data/util/TimeZoneMonitor.kt | 19 ++++++++++--------- .../core/testing/util/TestTimeZoneMonitor.kt | 8 ++++---- .../ui/{LocalZoneId.kt => LocalTimeZone.kt} | 6 +++--- .../nowinandroid/core/ui/NewsResourceCard.kt | 3 ++- 8 files changed, 28 insertions(+), 26 deletions(-) rename core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/{LocalZoneId.kt => LocalTimeZone.kt} (83%) diff --git a/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppStateTest.kt b/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppStateTest.kt index 18afc6a09..732e527bb 100644 --- a/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppStateTest.kt +++ b/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppStateTest.kt @@ -39,9 +39,9 @@ import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest +import kotlinx.datetime.TimeZone import org.junit.Rule import org.junit.Test -import java.time.ZoneId import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -199,7 +199,7 @@ class NiaAppStateTest { timeZoneMonitor = timeZoneMonitor, ) } - val changedTz = ZoneId.of("Europe/Prague") + val changedTz = TimeZone.of("Europe/Prague") backgroundScope.launch { state.currentTimeZone.collect() } timeZoneMonitor.setTimeZone(changedTz) assertEquals( diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt index 0bf45f125..afe3b51eb 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt @@ -47,7 +47,7 @@ import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand -import com.google.samples.apps.nowinandroid.core.ui.LocalZoneId +import com.google.samples.apps.nowinandroid.core.ui.LocalTimeZone import com.google.samples.apps.nowinandroid.ui.NiaApp import com.google.samples.apps.nowinandroid.ui.rememberNiaAppState import dagger.hilt.android.AndroidEntryPoint @@ -144,7 +144,7 @@ class MainActivity : ComponentActivity() { CompositionLocalProvider( LocalAnalyticsHelper provides analyticsHelper, - LocalZoneId provides currentZoneId, + LocalTimeZone provides currentZoneId, ) { NiaTheme( darkTheme = darkTheme, diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt index ffd0c16ec..ec2be6511 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt @@ -51,7 +51,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn -import java.time.ZoneId +import kotlinx.datetime.TimeZone @Composable fun rememberNiaAppState( @@ -140,11 +140,11 @@ class NiaAppState( initialValue = emptySet(), ) - val currentTimeZone = timeZoneMonitor.currentZoneId + val currentTimeZone = timeZoneMonitor.currentTimeZone .stateIn( coroutineScope, SharingStarted.WhileSubscribed(5_000), - ZoneId.systemDefault(), + TimeZone.currentSystemDefault(), ) /** diff --git a/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/DefaultZoneIdTimeZoneMonitor.kt b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/DefaultZoneIdTimeZoneMonitor.kt index ee8fe97a3..5a21ae337 100644 --- a/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/DefaultZoneIdTimeZoneMonitor.kt +++ b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/DefaultZoneIdTimeZoneMonitor.kt @@ -19,9 +19,9 @@ package com.google.samples.apps.nowinandroid.core.data.test import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf -import java.time.ZoneId +import kotlinx.datetime.TimeZone import javax.inject.Inject class DefaultZoneIdTimeZoneMonitor @Inject constructor() : TimeZoneMonitor { - override val currentZoneId: Flow = flowOf(ZoneId.of("Europe/Warsaw")) + override val currentTimeZone: Flow = flowOf(TimeZone.of("Europe/Warsaw")) } diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt index 99613d0f6..f7cc73565 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt @@ -34,10 +34,11 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.shareIn +import kotlinx.datetime.TimeZone import java.time.ZoneId -import java.util.TimeZone import javax.inject.Inject import javax.inject.Singleton @@ -46,7 +47,7 @@ import javax.inject.Singleton * It always emits at least once with default setting and then for each TZ change. */ interface TimeZoneMonitor { - val currentZoneId: Flow + val currentTimeZone: Flow } @Singleton @@ -56,10 +57,10 @@ internal class TimeZoneBroadcastMonitor @Inject constructor( @Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher, ) : TimeZoneMonitor { - override val currentZoneId: SharedFlow = - callbackFlow { + override val currentTimeZone: SharedFlow = + callbackFlow { // Send the default time zone first. - trySend(ZoneId.systemDefault()) + trySend(TimeZone.currentSystemDefault()) // Registers BroadcastReceiver for the TimeZone changes val receiver = object : BroadcastReceiver() { @@ -70,15 +71,15 @@ internal class TimeZoneBroadcastMonitor @Inject constructor( null } else { // Starting Android R we also get the new TimeZone. - intent.getStringExtra(Intent.EXTRA_TIMEZONE)?.let { zoneId -> + intent.getStringExtra(Intent.EXTRA_TIMEZONE)?.let { timeZoneId -> // We need to convert it from java.util.Timezone to java.time.ZoneId - ZoneId.of(zoneId, ZoneId.SHORT_IDS) - TimeZone.getTimeZone(zoneId).toZoneId() + val zoneId = ZoneId.of(timeZoneId, ZoneId.SHORT_IDS) + TimeZone.of(zoneId.id) } } // If there isn't a zoneId in the intent, fallback to the systemDefault, which should also reflect the change - trySend(zoneIdFromIntent ?: ZoneId.systemDefault()) + trySend(zoneIdFromIntent ?: TimeZone.currentSystemDefault()) } } diff --git a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/TestTimeZoneMonitor.kt b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/TestTimeZoneMonitor.kt index 1d7f21543..cc71ab2ca 100644 --- a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/TestTimeZoneMonitor.kt +++ b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/TestTimeZoneMonitor.kt @@ -19,22 +19,22 @@ package com.google.samples.apps.nowinandroid.core.testing.util import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -import java.time.ZoneId +import kotlinx.datetime.TimeZone class TestTimeZoneMonitor : TimeZoneMonitor { private val timeZoneFlow = MutableStateFlow(defaultTimeZone) - override val currentZoneId: Flow = timeZoneFlow + override val currentTimeZone: Flow = timeZoneFlow /** * A test-only API to set the from tests. */ - fun setTimeZone(zoneId: ZoneId) { + fun setTimeZone(zoneId: TimeZone) { timeZoneFlow.value = zoneId } companion object { - val defaultTimeZone: ZoneId = ZoneId.of("Europe/Warsaw") + val defaultTimeZone: TimeZone = TimeZone.of("Europe/Warsaw") } } diff --git a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/LocalZoneId.kt b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/LocalTimeZone.kt similarity index 83% rename from core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/LocalZoneId.kt rename to core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/LocalTimeZone.kt index 54aa7931d..2d9948488 100644 --- a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/LocalZoneId.kt +++ b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/LocalTimeZone.kt @@ -17,10 +17,10 @@ package com.google.samples.apps.nowinandroid.core.ui import androidx.compose.runtime.compositionLocalOf -import java.time.ZoneId +import kotlinx.datetime.TimeZone /** - * ZoneId that can be provided with the TimeZoneMonitor. + * TimeZone that can be provided with the TimeZoneMonitor. * This way, it's not needed to pass every single composable the time zone to show in UI. */ -val LocalZoneId = compositionLocalOf { ZoneId.systemDefault() } +val LocalTimeZone = compositionLocalOf { TimeZone.currentSystemDefault() } diff --git a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt index 9c4377b9d..2b22c7ea2 100644 --- a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt +++ b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt @@ -69,6 +69,7 @@ import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource import kotlinx.datetime.Instant import kotlinx.datetime.toJavaInstant +import kotlinx.datetime.toJavaZoneId import java.time.format.DateTimeFormatter import java.time.format.FormatStyle import java.util.Locale @@ -244,7 +245,7 @@ fun NotificationDot( fun dateFormatted(publishDate: Instant): String = DateTimeFormatter .ofLocalizedDate(FormatStyle.MEDIUM) .withLocale(Locale.getDefault()) - .withZone(LocalZoneId.current) + .withZone(LocalTimeZone.current.toJavaZoneId()) .format(publishDate.toJavaInstant()) @Composable From 6ec3b3c7a232d8c9abc108fa90e1cdb827ce9a0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Mlynari=C4=8D?= Date: Wed, 31 Jan 2024 11:32:49 +0100 Subject: [PATCH 08/16] Use androidx.tracing everywhere Change-Id: I9a77c4434d271e2e23700823f9f513aa61d177f8 --- .../src/main/kotlin/AndroidFeatureConventionPlugin.kt | 2 ++ .../src/main/kotlin/AndroidLibraryConventionPlugin.kt | 3 +++ .../apps/nowinandroid/core/data/util/TimeZoneMonitor.kt | 4 ++-- .../samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt | 2 +- gradle/libs.versions.toml | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt index 7a334beb3..8a76a1ac9 100644 --- a/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt @@ -44,6 +44,8 @@ class AndroidFeatureConventionPlugin : Plugin { add("implementation", libs.findLibrary("androidx.hilt.navigation.compose").get()) add("implementation", libs.findLibrary("androidx.lifecycle.runtimeCompose").get()) add("implementation", libs.findLibrary("androidx.lifecycle.viewModelCompose").get()) + + add("implementation", libs.findLibrary("androidx.tracing.ktx").get()) } } } diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt index 8eb329f58..d442d94ef 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -21,6 +21,7 @@ import com.google.samples.apps.nowinandroid.configureGradleManagedDevices import com.google.samples.apps.nowinandroid.configureKotlinAndroid import com.google.samples.apps.nowinandroid.configurePrintApksTask import com.google.samples.apps.nowinandroid.disableUnnecessaryAndroidTests +import com.google.samples.apps.nowinandroid.libs import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.configure @@ -51,6 +52,8 @@ class AndroidLibraryConventionPlugin : Plugin { } dependencies { add("testImplementation", kotlin("test")) + + add("implementation", libs.findLibrary("androidx.tracing.ktx").get()) } } } diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt index f7cc73565..d75f81efc 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt @@ -22,7 +22,7 @@ import android.content.Intent import android.content.IntentFilter import android.os.Build.VERSION import android.os.Build.VERSION_CODES -import androidx.core.os.trace +import androidx.tracing.trace 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 @@ -58,7 +58,7 @@ internal class TimeZoneBroadcastMonitor @Inject constructor( ) : TimeZoneMonitor { override val currentTimeZone: SharedFlow = - callbackFlow { + callbackFlow { // Send the default time zone first. trySend(TimeZone.currentSystemDefault()) diff --git a/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt b/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt index 30134715b..e8b19594f 100644 --- a/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt +++ b/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt @@ -81,7 +81,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.max import androidx.compose.ui.unit.sp -import androidx.compose.ui.util.trace +import androidx.tracing.trace import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.google.accompanist.permissions.ExperimentalPermissionsApi diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5bcdaf41c..d85a8c3ad 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,7 +24,7 @@ androidxTestCore = "1.5.0" androidxTestExt = "1.1.5" androidxTestRules = "1.5.0" androidxTestRunner = "1.5.2" -androidxTracing = "1.1.0" +androidxTracing = "1.3.0-alpha02" androidxUiAutomator = "2.2.0" androidxWindowManager = "1.2.0" androidxWork = "2.9.0" From 6faf3310a13cd64175fab18a53bd09eb60020f12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Mlynari=C4=8D?= Date: Wed, 31 Jan 2024 12:11:22 +0100 Subject: [PATCH 09/16] Fix spotless Change-Id: I26a64df036d17bcd455e782306fb31c099dd53ed --- .../samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt b/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt index e8b19594f..e1418d747 100644 --- a/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt +++ b/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt @@ -81,9 +81,9 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.max import androidx.compose.ui.unit.sp -import androidx.tracing.trace import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.tracing.trace import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.PermissionStatus.Denied import com.google.accompanist.permissions.rememberPermissionState From f8e797fd2698b84eb62974e537b9981f7812079e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Mlynari=C4=8D?= Date: Tue, 6 Feb 2024 08:53:04 +0100 Subject: [PATCH 10/16] Rename currentTimezone variable Change-Id: I92630f6851b0253294a8d3ec2e5b3cbe7db74b4d --- .../com/google/samples/apps/nowinandroid/MainActivity.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt index afe3b51eb..ad95c297f 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt @@ -140,11 +140,11 @@ class MainActivity : ComponentActivity() { timeZoneMonitor = timeZoneMonitor, ) - val currentZoneId by appState.currentTimeZone.collectAsStateWithLifecycle() + val currentTimeZone by appState.currentTimeZone.collectAsStateWithLifecycle() CompositionLocalProvider( LocalAnalyticsHelper provides analyticsHelper, - LocalTimeZone provides currentZoneId, + LocalTimeZone provides currentTimeZone, ) { NiaTheme( darkTheme = darkTheme, From 08bfc548326de731c3facb1eaa613e1a9f97edc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Mlynari=C4=8D?= Date: Tue, 6 Feb 2024 08:56:43 +0100 Subject: [PATCH 11/16] Update dependency guard baseline Change-Id: I2557753f1eb4c6845aca6473a6aba5b7e758ebbf --- .../dependencies/releaseRuntimeClasspath.txt | 7 ++++--- app/dependencies/prodReleaseRuntimeClasspath.txt | 10 ++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt b/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt index 10f2dfa32..e59cc53bc 100644 --- a/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt +++ b/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt @@ -70,7 +70,8 @@ androidx.profileinstaller:profileinstaller:1.3.1 androidx.savedstate:savedstate-ktx:1.2.1 androidx.savedstate:savedstate:1.2.1 androidx.startup:startup-runtime:1.1.1 -androidx.tracing:tracing:1.0.0 +androidx.tracing:tracing-ktx:1.3.0-alpha02 +androidx.tracing:tracing:1.3.0-alpha02 androidx.vectordrawable:vectordrawable-animated:1.1.0 androidx.vectordrawable:vectordrawable:1.1.0 androidx.versionedparcelable:versionedparcelable:1.1.1 @@ -90,10 +91,10 @@ io.coil-kt:coil-compose-base:2.5.0 io.coil-kt:coil-compose:2.5.0 io.coil-kt:coil:2.5.0 javax.inject:javax.inject:1 -org.jetbrains.kotlin:kotlin-stdlib-common:1.9.21 +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.22 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10 -org.jetbrains.kotlin:kotlin-stdlib:1.9.21 +org.jetbrains.kotlin:kotlin-stdlib:1.9.22 org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3 org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3 org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 diff --git a/app/dependencies/prodReleaseRuntimeClasspath.txt b/app/dependencies/prodReleaseRuntimeClasspath.txt index e9e3ebf8e..6eb888042 100644 --- a/app/dependencies/prodReleaseRuntimeClasspath.txt +++ b/app/dependencies/prodReleaseRuntimeClasspath.txt @@ -32,6 +32,7 @@ androidx.compose.material:material-ripple:1.5.4 androidx.compose.runtime:runtime-android:1.5.4 androidx.compose.runtime:runtime-saveable-android:1.5.4 androidx.compose.runtime:runtime-saveable:1.5.4 +androidx.compose.runtime:runtime-tracing:1.0.0-beta01 androidx.compose.runtime:runtime:1.5.4 androidx.compose.ui:ui-android:1.5.4 androidx.compose.ui:ui-geometry-android:1.5.4 @@ -105,8 +106,9 @@ androidx.savedstate:savedstate:1.2.1 androidx.sqlite:sqlite-framework:2.4.0 androidx.sqlite:sqlite:2.4.0 androidx.startup:startup-runtime:1.1.1 -androidx.tracing:tracing-ktx:1.1.0 -androidx.tracing:tracing:1.1.0 +androidx.tracing:tracing-ktx:1.3.0-alpha02 +androidx.tracing:tracing-perfetto:1.0.0 +androidx.tracing:tracing:1.3.0-alpha02 androidx.vectordrawable:vectordrawable-animated:1.1.0 androidx.vectordrawable:vectordrawable:1.1.0 androidx.versionedparcelable:versionedparcelable:1.1.1 @@ -185,10 +187,10 @@ io.github.aakira:napier-android:1.4.1 io.github.aakira:napier:1.4.1 javax.inject:javax.inject:1 org.checkerframework:checker-qual:3.12.0 -org.jetbrains.kotlin:kotlin-stdlib-common:1.9.21 +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.22 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10 -org.jetbrains.kotlin:kotlin-stdlib:1.9.21 +org.jetbrains.kotlin:kotlin-stdlib:1.9.22 org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3 org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3 org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 From 12a3a6eddea9ddf686a82b70c2149146d8df9682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Mlynari=C4=8D?= Date: Tue, 6 Feb 2024 15:04:24 +0100 Subject: [PATCH 12/16] Use trySend multiple times Change-Id: Ie00c4180d07fc4bb9d457647f3b7ddadd5a3c032 --- .../com/google/samples/apps/nowinandroid/ui/NiaAppState.kt | 2 +- .../apps/nowinandroid/core/data/util/TimeZoneMonitor.kt | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt index ec2be6511..d423adfbf 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt @@ -89,7 +89,7 @@ class NiaAppState( val windowSizeClass: WindowSizeClass, networkMonitor: NetworkMonitor, userNewsResourceRepository: UserNewsResourceRepository, - private val timeZoneMonitor: TimeZoneMonitor, + timeZoneMonitor: TimeZoneMonitor, ) { val currentDestination: NavDestination? @Composable get() = navController diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt index d75f81efc..6e206e67c 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt @@ -35,6 +35,7 @@ import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.shareIn import kotlinx.datetime.TimeZone @@ -87,10 +88,16 @@ internal class TimeZoneBroadcastMonitor @Inject constructor( context.registerReceiver(receiver, IntentFilter(Intent.ACTION_TIMEZONE_CHANGED)) } + // Send here again, because registering the Broadcast Receiver can take up to several milliseconds. + // This way, we can reduce the likelihood that a TZ change wouldn't be caught with the Broadcast Receiver. + trySend(TimeZone.currentSystemDefault()) + awaitClose { context.unregisterReceiver(receiver) } } + // We use to prevent multiple emissions of the same type, because we use trySend multiple times. + .distinctUntilChanged() .conflate() .flowOn(ioDispatcher) // Sharing the callback to prevent multiple BroadcastReceivers being registered From 9daedc9690f4af3df92b472219736e8bd09be6e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Mlynari=C4=8D?= Date: Tue, 6 Feb 2024 15:07:12 +0100 Subject: [PATCH 13/16] Improve converting to kotlin timezone Change-Id: I2af929c341cb274ae6b93bb8dffe696de6ed1c63 --- .../apps/nowinandroid/core/data/util/TimeZoneMonitor.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt index 6e206e67c..031bc9388 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt @@ -39,6 +39,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.shareIn import kotlinx.datetime.TimeZone +import kotlinx.datetime.toKotlinTimeZone import java.time.ZoneId import javax.inject.Inject import javax.inject.Singleton @@ -75,7 +76,8 @@ internal class TimeZoneBroadcastMonitor @Inject constructor( intent.getStringExtra(Intent.EXTRA_TIMEZONE)?.let { timeZoneId -> // We need to convert it from java.util.Timezone to java.time.ZoneId val zoneId = ZoneId.of(timeZoneId, ZoneId.SHORT_IDS) - TimeZone.of(zoneId.id) + // Convert to kotlinx.datetime.TimeZone + zoneId.toKotlinTimeZone() } } From ea5c6922667698d14267a918c0ba049b37b9d449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Alc=C3=A9rreca?= Date: Wed, 7 Feb 2024 10:28:34 +0100 Subject: [PATCH 14/16] Dpm --- .github/workflows/Build.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/Build.yaml b/.github/workflows/Build.yaml index fa3a77f55..68b1f5f64 100644 --- a/.github/workflows/Build.yaml +++ b/.github/workflows/Build.yaml @@ -176,6 +176,12 @@ jobs: - name: Build projects before running emulator run: ./gradlew packageDemoDebug packageDemoDebugAndroidTest + - name: Print available space + run: df -h + + - name: Print disk usage + run: du -h -d 2 /home/runner/ + - name: Run instrumentation tests uses: reactivecircus/android-emulator-runner@v2 with: From 949929366455fda5f60a125539b10425adb9df81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Alc=C3=A9rreca?= Date: Wed, 7 Feb 2024 10:51:52 +0100 Subject: [PATCH 15/16] Figuring out what is using 66Gb in the runner --- .github/workflows/Build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Build.yaml b/.github/workflows/Build.yaml index 68b1f5f64..d79dd2a5e 100644 --- a/.github/workflows/Build.yaml +++ b/.github/workflows/Build.yaml @@ -180,7 +180,7 @@ jobs: run: df -h - name: Print disk usage - run: du -h -d 2 /home/runner/ + run: du -h -d 2 / - name: Run instrumentation tests uses: reactivecircus/android-emulator-runner@v2 From 8b56fd31605d2ab38f3b598cb82002e6ca45f343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Mlynari=C4=8D?= Date: Tue, 20 Feb 2024 13:58:30 +0100 Subject: [PATCH 16/16] Remove disk usage testing Change-Id: I57bb3778dc41a7c21b8e923e05ee1c68eb15def3 --- .github/workflows/Build.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/Build.yaml b/.github/workflows/Build.yaml index 930c62d39..5f501b6c0 100644 --- a/.github/workflows/Build.yaml +++ b/.github/workflows/Build.yaml @@ -183,12 +183,6 @@ jobs: - name: Build projects before running emulator run: ./gradlew packageDemoDebug packageDemoDebugAndroidTest - - name: Print available space - run: df -h - - - name: Print disk usage - run: du -h -d 2 / - - name: Run instrumentation tests uses: reactivecircus/android-emulator-runner@v2 with: