From 63da71ecfe668c4357fe01fb923ceff2e29463ce Mon Sep 17 00:00:00 2001 From: Don Turner Date: Wed, 11 Jan 2023 20:45:17 +0000 Subject: [PATCH 01/20] Copy google-services.json from prebuilts folder Change-Id: I6b23a1e12dce1341c7d43e5560c91fa5a148681e --- build_android_release.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build_android_release.sh b/build_android_release.sh index dfdf37500..4a7c7688f 100755 --- a/build_android_release.sh +++ b/build_android_release.sh @@ -25,8 +25,11 @@ export JAVA_HOME="$(cd $DIR/../../../prebuilts/studio/jdk/jdk11/linux && pwd )" echo "JAVA_HOME=$JAVA_HOME" export ANDROID_HOME="$(cd $DIR/../../../prebuilts/fullsdk/linux && pwd )" - echo "ANDROID_HOME=$ANDROID_HOME" + +echo "Copying google-services.json" +cp $DIR/../nowinandroid-prebuilts/google-services.json $DIR/app + cd $DIR # Build From 7fbedcd89db88d3d7913cd44dd9d0124ea49f5b8 Mon Sep 17 00:00:00 2001 From: Don Turner Date: Wed, 11 Jan 2023 20:53:34 +0000 Subject: [PATCH 02/20] Remove API 23 from list of APIs to test in Firebase Test Lab (it's flaky) Change-Id: I054bd9009c3a3358a2f578cd520ecf73961412dd --- kokoro/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kokoro/build.sh b/kokoro/build.sh index c283382d7..5c50a814f 100755 --- a/kokoro/build.sh +++ b/kokoro/build.sh @@ -20,7 +20,7 @@ set -e set -x deviceIds=${1:-'Nexus5,Pixel2,Pixel3,Nexus9'} -osVersionIds=${2:-'23,27,30'} +osVersionIds=${2:-'27,30'} GRADLE_FLAGS=() if [[ -n "$GRADLE_DEBUG" ]]; then From f316be1d692fdb9e13596f0ba5cc7d537a2cdfa1 Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Thu, 1 Dec 2022 09:11:02 +0000 Subject: [PATCH 03/20] Add Firebase dependencies This sets up the project for using Firebase Crashlytics and Performance Monitoring. Change-Id: I8d14cfd2e5c2ba1911f2c3175adc20d6714addb6 --- app/build.gradle.kts | 2 +- build-logic/convention/build.gradle.kts | 6 +-- .../kotlin/AndroidHiltConventionPlugin.kt | 3 -- .../main/kotlin/FirebaseConventionPlugin.kt | 45 +++++++++++++++++++ .../kotlin/FirebasePerfConventionPlugin.kt | 29 ------------ build.gradle.kts | 9 ++++ gradle/libs.versions.toml | 12 +++++ 7 files changed, 70 insertions(+), 36 deletions(-) create mode 100644 build-logic/convention/src/main/kotlin/FirebaseConventionPlugin.kt delete mode 100644 build-logic/convention/src/main/kotlin/FirebasePerfConventionPlugin.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3cc48f284..09efa62fd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,8 +20,8 @@ plugins { id("nowinandroid.android.application.compose") id("nowinandroid.android.application.jacoco") id("nowinandroid.android.hilt") + id("nowinandroid.firebase") id("jacoco") - id("nowinandroid.firebase-perf") } android { diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index 453085807..d1f3b3ab9 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -68,9 +68,9 @@ gradlePlugin { id = "nowinandroid.android.hilt" implementationClass = "AndroidHiltConventionPlugin" } - register("firebase-perf") { - id = "nowinandroid.firebase-perf" - implementationClass = "FirebasePerfConventionPlugin" + register("firebase") { + id = "nowinandroid.firebase" + implementationClass = "FirebaseConventionPlugin" } } } diff --git a/build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt index 772064942..29cb748c2 100644 --- a/build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt @@ -14,14 +14,11 @@ * limitations under the License. */ -import com.android.build.api.variant.LibraryAndroidComponentsExtension -import com.google.samples.apps.nowinandroid.configureJacoco import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.artifacts.VersionCatalogsExtension import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.getByType -import org.gradle.kotlin.dsl.kotlin class AndroidHiltConventionPlugin : Plugin { override fun apply(target: Project) { diff --git a/build-logic/convention/src/main/kotlin/FirebaseConventionPlugin.kt b/build-logic/convention/src/main/kotlin/FirebaseConventionPlugin.kt new file mode 100644 index 000000000..70054408d --- /dev/null +++ b/build-logic/convention/src/main/kotlin/FirebaseConventionPlugin.kt @@ -0,0 +1,45 @@ +/* + * 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. + */ + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.getByType + +class FirebaseConventionPlugin : Plugin { + override fun apply(target: Project) { + // Only apply this to Google Play releases. + if (!target.hasProperty("use-google-services")) + return + with(target) { + with(pluginManager) { + apply("com.google.gms.google-services") + apply("com.google.firebase.firebase-perf") + apply("com.google.firebase.crashlytics") + } + + val libs = extensions.getByType().named("libs") + dependencies { + val bom = libs.findLibrary("firebase-bom").get() + add("releaseImplementation", platform(bom)) + "releaseImplementation"(libs.findLibrary("firebase.analytics").get()) + "releaseImplementation"(libs.findLibrary("firebase.crashlytics").get()) + "releaseImplementation"(libs.findLibrary("firebase.performance").get()) + } + } + } +} diff --git a/build-logic/convention/src/main/kotlin/FirebasePerfConventionPlugin.kt b/build-logic/convention/src/main/kotlin/FirebasePerfConventionPlugin.kt deleted file mode 100644 index 48f750678..000000000 --- a/build-logic/convention/src/main/kotlin/FirebasePerfConventionPlugin.kt +++ /dev/null @@ -1,29 +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. - */ - -import org.gradle.api.Plugin -import org.gradle.api.Project - -class FirebasePerfConventionPlugin : Plugin { - override fun apply(target: Project) { - with(target) { - pluginManager.findPlugin("com.google.firebase.firebase-perf").apply { - version = "1.4.1" - } - } - } - -} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 6f6d375d7..bd395ceac 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,12 +23,21 @@ buildscript { maven { url = uri("../nowinandroid-prebuilts/m2repository") } } + dependencies { + if (project.hasProperty("use-google-services")) { + classpath(libs.firebase.crashlytics.gradle) + } + } } +// Lists all plugins used throughout the project without applying them. plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.jvm) apply false alias(libs.plugins.kotlin.serialization) apply false + alias(libs.plugins.firebase.crashlytics) apply false + alias(libs.plugins.firebase.perf) apply false + alias(libs.plugins.gms) apply false alias(libs.plugins.hilt) apply false alias(libs.plugins.secrets) apply false } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 887769cbf..4d6b694cc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,6 +28,10 @@ androidxUiAutomator = "2.2.0" androidxWindowManager = "1.0.0" androidxWork = "2.7.1" coil = "2.2.2" +firebaseBom = "31.0.3" +firebaseCrashlyticsPlugin = "2.9.2" +firebasePerfPlugin = "1.4.2" +gmsPlugin = "4.3.14" hilt = "2.44.2" hiltExt = "1.0.0" jacoco = "0.8.7" @@ -96,6 +100,11 @@ androidx-work-testing = { group = "androidx.work", name = "work-testing", versio coil-kt = { group = "io.coil-kt", name = "coil", version.ref = "coil" } coil-kt-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" } coil-kt-svg = { group = "io.coil-kt", name = "coil-svg", version.ref = "coil" } +firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref="firebaseBom"} +firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics-ktx"} +firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics-ktx"} +firebase-crashlytics-gradle = { group = "com.google.firebase", name="firebase-crashlytics-gradle", version.ref = "firebaseCrashlyticsPlugin"} +firebase-performance = { group = "com.google.firebase", name = "firebase-perf-ktx"} hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } hilt-android-testing = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" } hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" } @@ -126,6 +135,9 @@ kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-pl android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" } android-test = { id = "com.android.test", version.ref = "androidGradlePlugin" } +firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebaseCrashlyticsPlugin"} +firebase-perf = { id = "com.google.firebase.firebase-perf", version.ref = "firebasePerfPlugin"} +gms = { id = "com.google.gms.google-services", version.ref = "gmsPlugin"} hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } From ec9e641e351eb09ee86a5663a03e32c5773d0fd6 Mon Sep 17 00:00:00 2001 From: Don Turner Date: Wed, 18 Jan 2023 16:40:56 +0000 Subject: [PATCH 04/20] Adding a file which can be modified to trigger an internal build Change-Id: Iac2732df6717be0900c6df631a913a490a93d2f7 --- .google/BUILDME | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .google/BUILDME diff --git a/.google/BUILDME b/.google/BUILDME new file mode 100644 index 000000000..6d57608d7 --- /dev/null +++ b/.google/BUILDME @@ -0,0 +1,2 @@ +# This file can be used to trigger an internal build by changing the number below +1 From ee94c22b619d7b2d521bc19571a02e0975533960 Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Wed, 18 Jan 2023 21:08:59 +0000 Subject: [PATCH 05/20] Increment internal build trigger Change-Id: I96a03fb6f0c42a7198fbf4a2049ef7120fae205a --- .google/BUILDME | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.google/BUILDME b/.google/BUILDME index 6d57608d7..d6b23eab3 100644 --- a/.google/BUILDME +++ b/.google/BUILDME @@ -1,2 +1,2 @@ # This file can be used to trigger an internal build by changing the number below -1 +2 From 5210c29eb3fef076df1245c133e1b308fa44240e Mon Sep 17 00:00:00 2001 From: Don Turner Date: Wed, 18 Jan 2023 21:13:23 +0000 Subject: [PATCH 06/20] New build trigger Change-Id: I80a70e20124054348844ecd87e42ecd540826161 --- .google/BUILDME | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.google/BUILDME b/.google/BUILDME index d6b23eab3..5295ed188 100644 --- a/.google/BUILDME +++ b/.google/BUILDME @@ -1,2 +1,2 @@ # This file can be used to trigger an internal build by changing the number below -2 +3 From 68afef6c26a29a6d22e51cb5adacd8b9dda42b48 Mon Sep 17 00:00:00 2001 From: Don Turner Date: Thu, 19 Jan 2023 09:44:20 +0000 Subject: [PATCH 07/20] Revert "New build trigger" This reverts commit 5210c29eb3fef076df1245c133e1b308fa44240e. Reason for revert: Trigger another build (wow, this is such a great process!) Change-Id: Iee146e0069f92f338789016f81c91dad18f5f7b3 --- .google/BUILDME | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.google/BUILDME b/.google/BUILDME index 5295ed188..d6b23eab3 100644 --- a/.google/BUILDME +++ b/.google/BUILDME @@ -1,2 +1,2 @@ # This file can be used to trigger an internal build by changing the number below -3 +2 From 43f0b9f693de21a9534c2d8ad49cb1e32289c6ec Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Thu, 19 Jan 2023 12:35:38 +0000 Subject: [PATCH 08/20] Revert "Revert "New build trigger"" This reverts commit 68afef6c26a29a6d22e51cb5adacd8b9dda42b48. Reason for revert: Reverting the revert to kick the build. Change-Id: Icc1602b188fcc19cc88ba019e5a96a827e69543f --- .google/BUILDME | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.google/BUILDME b/.google/BUILDME index d6b23eab3..5295ed188 100644 --- a/.google/BUILDME +++ b/.google/BUILDME @@ -1,2 +1,2 @@ # This file can be used to trigger an internal build by changing the number below -2 +3 From 71303b6f6126968e4a6a923d9ee9c93a08aa2454 Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Thu, 26 Jan 2023 09:00:22 +0000 Subject: [PATCH 09/20] Revert "Revert "Revert "New build trigger""" This reverts commit 43f0b9f693de21a9534c2d8ad49cb1e32289c6ec. Reason for revert: Kicking the build again Change-Id: I316e46a870f63e9561a1e0c1fb0a8114bbe81d98 --- .google/BUILDME | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.google/BUILDME b/.google/BUILDME index 5295ed188..d6b23eab3 100644 --- a/.google/BUILDME +++ b/.google/BUILDME @@ -1,2 +1,2 @@ # This file can be used to trigger an internal build by changing the number below -3 +2 From 6dd82ea03ea395bf42c3031f36d2cc935fa210cf Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Mon, 30 Jan 2023 15:22:48 +0000 Subject: [PATCH 10/20] Enable use-google-services in release builds Change-Id: Idb1ca4a4e218e493cac1c26b3dd2582a19914cfe --- build_android_release.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build_android_release.sh b/build_android_release.sh index 4a7c7688f..eccdc45ef 100755 --- a/build_android_release.sh +++ b/build_android_release.sh @@ -33,7 +33,7 @@ cp $DIR/../nowinandroid-prebuilts/google-services.json $DIR/app cd $DIR # Build -GRADLE_PARAMS=" --stacktrace" +GRADLE_PARAMS=" --stacktrace -Puse-google-services" $DIR/gradlew :app:clean :app:assemble ${GRADLE_PARAMS} BUILD_RESULT=$? @@ -68,4 +68,4 @@ cp $APP_OUT/bundle/prodRelease/app-prod-release.aab $DIST_DIR/app-prod-release.a #cp $APP_OUT/mapping/prodRelease/mapping.txt $DIST_DIR/mobile-release-aab-mapping.txt BUILD_RESULT=$? -exit $BUILD_RESULT \ No newline at end of file +exit $BUILD_RESULT From 8d0f28170d8b713bcfa37a42cc724f2abf766a55 Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Mon, 30 Jan 2023 16:43:43 +0000 Subject: [PATCH 11/20] Build release artifacts separately, using google-services Change-Id: I17e3fb3bfa598ae729623be685af65dffd366e95 --- build_android_release.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build_android_release.sh b/build_android_release.sh index eccdc45ef..2fe22a57a 100755 --- a/build_android_release.sh +++ b/build_android_release.sh @@ -33,10 +33,13 @@ cp $DIR/../nowinandroid-prebuilts/google-services.json $DIR/app cd $DIR # Build -GRADLE_PARAMS=" --stacktrace -Puse-google-services" +GRADLE_PARAMS=" --stacktrace" $DIR/gradlew :app:clean :app:assemble ${GRADLE_PARAMS} BUILD_RESULT=$? +GRADLE_RELEASE_PARAMS="$GRADLE_PARAMS -Puse-google-services" +$DIR/gradlew :app:assembleDemoRelease :app:assembleProdRelease ${GRADLE_RELEASE_PARAMS} + # Demo debug cp $APP_OUT/apk/demo/debug/app-demo-debug.apk $DIST_DIR From ef4b189f7e45f2ce71e7decde4c9b8f1dec10e56 Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Tue, 31 Jan 2023 09:31:15 +0000 Subject: [PATCH 12/20] Build aab with play services enabled Change-Id: I8715fb7f95ece046f48629c97d207e2cb52e0fb2 --- build_android_release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_android_release.sh b/build_android_release.sh index 2fe22a57a..d1b5cb30a 100755 --- a/build_android_release.sh +++ b/build_android_release.sh @@ -38,7 +38,7 @@ $DIR/gradlew :app:clean :app:assemble ${GRADLE_PARAMS} BUILD_RESULT=$? GRADLE_RELEASE_PARAMS="$GRADLE_PARAMS -Puse-google-services" -$DIR/gradlew :app:assembleDemoRelease :app:assembleProdRelease ${GRADLE_RELEASE_PARAMS} +$DIR/gradlew :app:assembleDemoRelease :app:assembleProdRelease :app:bundleDemoRelease :app:bundleProdRelease ${GRADLE_RELEASE_PARAMS} # Demo debug cp $APP_OUT/apk/demo/debug/app-demo-debug.apk $DIST_DIR From 19684abc5f3b5a1dc6a1951de0a73b139843b82b Mon Sep 17 00:00:00 2001 From: Don Turner Date: Tue, 31 Jan 2023 16:52:41 +0000 Subject: [PATCH 13/20] Remove .prod from the applicationIdSuffix in preparation for Play launch Change-Id: I30f7120bf6ce3f101ca9fc1ad28d3cecf2048f9a --- .../kotlin/com/google/samples/apps/nowinandroid/NiaFlavor.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaFlavor.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaFlavor.kt index 5cafdf7ce..dec592542 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaFlavor.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaFlavor.kt @@ -16,8 +16,8 @@ enum class FlavorDimension { // These two product flavors reflect this behaviour. @Suppress("EnumEntryName") enum class NiaFlavor(val dimension: FlavorDimension, val applicationIdSuffix: String? = null) { - demo(FlavorDimension.contentType), - prod(FlavorDimension.contentType, ".prod") + demo(FlavorDimension.contentType, applicationIdSuffix = ".demo"), + prod(FlavorDimension.contentType, ) } fun Project.configureFlavors( From ab470ea99beaddd2781d9e859951c705b16ecf49 Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Tue, 31 Jan 2023 18:04:31 +0000 Subject: [PATCH 14/20] Fix exit codes Change-Id: Ib4b07ffe2cf43b37f81e42cda903114260afebbd --- build_android_release.sh | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/build_android_release.sh b/build_android_release.sh index d1b5cb30a..02af96608 100755 --- a/build_android_release.sh +++ b/build_android_release.sh @@ -32,14 +32,11 @@ cp $DIR/../nowinandroid-prebuilts/google-services.json $DIR/app cd $DIR -# Build -GRADLE_PARAMS=" --stacktrace" -$DIR/gradlew :app:clean :app:assemble ${GRADLE_PARAMS} +# Build the prodRelease variant +GRADLE_PARAMS=" --stacktrace -Puse-google-services" +$DIR/gradlew :app:clean :app:assembleProdRelease :app:bundleProdRelease ${GRADLE_PARAMS} BUILD_RESULT=$? -GRADLE_RELEASE_PARAMS="$GRADLE_PARAMS -Puse-google-services" -$DIR/gradlew :app:assembleDemoRelease :app:assembleProdRelease :app:bundleDemoRelease :app:bundleProdRelease ${GRADLE_RELEASE_PARAMS} - # Demo debug cp $APP_OUT/apk/demo/debug/app-demo-debug.apk $DIST_DIR @@ -57,18 +54,19 @@ cp $APP_OUT/apk/prod/release/app-prod-release.apk $DIST_DIR/app-prod-release.apk # Don't clean here, otherwise all apks are gone. $DIR/gradlew :app:bundle ${GRADLE_PARAMS} -# Demo debug -cp $APP_OUT/bundle/demoDebug/app-demo-debug.aab $DIST_DIR/app-demo-debug.aab - -# Demo release -cp $APP_OUT/bundle/demoRelease/app-demo-release.aab $DIST_DIR/app-demo-release.aab - # Prod debug cp $APP_OUT/bundle/prodDebug/app-prod-debug.aab $DIST_DIR/app-prod-debug.aab # Prod release cp $APP_OUT/bundle/prodRelease/app-prod-release.aab $DIST_DIR/app-prod-release.aab #cp $APP_OUT/mapping/prodRelease/mapping.txt $DIST_DIR/mobile-release-aab-mapping.txt -BUILD_RESULT=$? - -exit $BUILD_RESULT +COPY_RESULT=$? + +if [ $BUILD_RESULT -eq 0 ] && [ $RELEASE_BUILD_RESULT -eq 0 ] && [ $COPY_RESULT -eq 0 ] +then + echo "All parts successful" + exit 0 +else + echo "Something failed. Check logs for details." + exit 1 +fi From 3f935bd958b5ad80b245b61bf6d6d75e503e2290 Mon Sep 17 00:00:00 2001 From: Don Turner Date: Tue, 31 Jan 2023 18:19:10 +0000 Subject: [PATCH 15/20] Copy local.properties during build Change-Id: I48fdf41747663462fb00979eaf3037310fe257de --- build_android_release.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build_android_release.sh b/build_android_release.sh index 02af96608..a2fd82f9c 100755 --- a/build_android_release.sh +++ b/build_android_release.sh @@ -30,6 +30,9 @@ echo "ANDROID_HOME=$ANDROID_HOME" echo "Copying google-services.json" cp $DIR/../nowinandroid-prebuilts/google-services.json $DIR/app +echo "Copying local.properties" +cp $DIR/../nowinandroid-prebuilts/local.properties $DIR + cd $DIR # Build the prodRelease variant From 3cf2a06a858ec0f2b894b799f55c93e90e2192ab Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Wed, 1 Feb 2023 10:09:06 +0000 Subject: [PATCH 16/20] Simplify build script Change-Id: Ia2444cdab959fa0f0e401dd98e1b6b8bccd6b4f1 --- build_android_release.sh | 35 +++++------------------------------ 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/build_android_release.sh b/build_android_release.sh index a2fd82f9c..c7e5fc835 100755 --- a/build_android_release.sh +++ b/build_android_release.sh @@ -40,36 +40,11 @@ GRADLE_PARAMS=" --stacktrace -Puse-google-services" $DIR/gradlew :app:clean :app:assembleProdRelease :app:bundleProdRelease ${GRADLE_PARAMS} BUILD_RESULT=$? -# Demo debug -cp $APP_OUT/apk/demo/debug/app-demo-debug.apk $DIST_DIR - -# Demo release -cp $APP_OUT/apk/demo/release/app-demo-release.apk $DIST_DIR - -# Prod debug -cp $APP_OUT/apk/prod/debug/app-prod-debug.apk $DIST_DIR/app-prod-debug.apk - -# Prod release +# Prod release apk cp $APP_OUT/apk/prod/release/app-prod-release.apk $DIST_DIR/app-prod-release.apk -#cp $APP_OUT/mapping/release/mapping.txt $DIST_DIR/mobile-release-apk-mapping.txt - -# Build App Bundles -# Don't clean here, otherwise all apks are gone. -$DIR/gradlew :app:bundle ${GRADLE_PARAMS} - -# Prod debug -cp $APP_OUT/bundle/prodDebug/app-prod-debug.aab $DIST_DIR/app-prod-debug.aab - -# Prod release +# Prod release bundle cp $APP_OUT/bundle/prodRelease/app-prod-release.aab $DIST_DIR/app-prod-release.aab -#cp $APP_OUT/mapping/prodRelease/mapping.txt $DIST_DIR/mobile-release-aab-mapping.txt -COPY_RESULT=$? +# Prod release bundle mapping +cp $APP_OUT/mapping/prodRelease/mapping.txt $DIST_DIR/mobile-release-aab-mapping.txt -if [ $BUILD_RESULT -eq 0 ] && [ $RELEASE_BUILD_RESULT -eq 0 ] && [ $COPY_RESULT -eq 0 ] -then - echo "All parts successful" - exit 0 -else - echo "Something failed. Check logs for details." - exit 1 -fi +exit $BUILD_RESULT From 2a4d0434a5c8a794f86410850e25d66e846eb97f Mon Sep 17 00:00:00 2001 From: Don Turner Date: Tue, 7 Feb 2023 22:23:50 +0000 Subject: [PATCH 17/20] Add Firebase analytics. See go/nia-firebase-services. Change-Id: I0cbbda0bba761d8019241f6165db231fe94fb689 --- app/benchmark-rules.pro | 14 +- app/build.gradle.kts | 4 +- app/google-services.json | 125 ++++++++++++++++++ app/src/main/AndroidManifest.xml | 4 + .../samples/apps/nowinandroid/MainActivity.kt | 26 ++-- app/src/prod/AndroidManifest.xml | 26 ++++ build-logic/convention/build.gradle.kts | 13 +- .../AndroidApplicationConventionPlugin.kt | 4 +- ...oidApplicationFirebaseConventionPlugin.kt} | 29 ++-- ...droidApplicationFlavorsConventionPlugin.kt | 31 +++++ .../kotlin/AndroidFeatureConventionPlugin.kt | 1 + .../kotlin/AndroidLibraryConventionPlugin.kt | 1 - build.gradle.kts | 6 - core/analytics/.gitignore | 1 + core/analytics/build.gradle.kts | 33 +++++ .../core/analytics/AnalyticsModule.kt | 29 ++++ core/analytics/src/main/AndroidManifest.xml | 17 +++ .../core/analytics/AnalyticsEvent.kt | 58 ++++++++ .../core/analytics/AnalyticsHelper.kt | 25 ++++ .../core/analytics/NoOpAnalyticsHelper.kt | 24 ++++ .../core/analytics/StubAnalyticsHelper.kt | 34 +++++ .../nowinandroid/core/analytics/UiHelpers.kt | 28 ++++ .../core/analytics/AnalyticsModule.kt | 29 ++++ .../core/analytics/FirebaseAnalyticsHelper.kt | 44 ++++++ core/data/build.gradle.kts | 1 + .../data/repository/AnalyticsExtensions.kt | 84 ++++++++++++ .../OfflineFirstUserDataRepository.kt | 31 ++++- .../OfflineFirstUserDataRepositoryTest.kt | 4 + core/ui/build.gradle.kts | 1 + .../core/ui/AnalyticsExtensions.kt | 63 +++++++++ .../apps/nowinandroid/core/ui/NewsFeed.kt | 10 +- .../core/ui/NewsResourceCardList.kt | 6 + .../feature/bookmarks/BookmarksScreen.kt | 2 + .../feature/foryou/ForYouScreen.kt | 2 + .../feature/interests/InterestsScreen.kt | 2 + .../feature/settings/SettingsDialog.kt | 2 + .../nowinandroid/feature/topic/TopicScreen.kt | 2 + .../feature/topic/TopicViewModel.kt | 2 + .../feature/topic/TopicViewModelTest.kt | 4 + gradle/libs.versions.toml | 3 +- settings.gradle.kts | 2 + sync/work/build.gradle.kts | 1 + .../sync/workers/AnalyticsExtensions.kt | 32 +++++ .../nowinandroid/sync/workers/SyncWorker.kt | 6 + 44 files changed, 826 insertions(+), 40 deletions(-) create mode 100644 app/google-services.json create mode 100644 app/src/prod/AndroidManifest.xml rename build-logic/convention/src/main/kotlin/{FirebaseConventionPlugin.kt => AndroidApplicationFirebaseConventionPlugin.kt} (53%) create mode 100644 build-logic/convention/src/main/kotlin/AndroidApplicationFlavorsConventionPlugin.kt create mode 100644 core/analytics/.gitignore create mode 100644 core/analytics/build.gradle.kts create mode 100644 core/analytics/src/demo/java/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsModule.kt create mode 100644 core/analytics/src/main/AndroidManifest.xml create mode 100644 core/analytics/src/main/java/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsEvent.kt create mode 100644 core/analytics/src/main/java/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsHelper.kt create mode 100644 core/analytics/src/main/java/com/google/samples/apps/nowinandroid/core/analytics/NoOpAnalyticsHelper.kt create mode 100644 core/analytics/src/main/java/com/google/samples/apps/nowinandroid/core/analytics/StubAnalyticsHelper.kt create mode 100644 core/analytics/src/main/java/com/google/samples/apps/nowinandroid/core/analytics/UiHelpers.kt create mode 100644 core/analytics/src/prod/java/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsModule.kt create mode 100644 core/analytics/src/prod/java/com/google/samples/apps/nowinandroid/core/analytics/FirebaseAnalyticsHelper.kt create mode 100644 core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/AnalyticsExtensions.kt create mode 100644 core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/AnalyticsExtensions.kt create mode 100644 sync/work/src/main/java/com/google/samples/apps/nowinandroid/sync/workers/AnalyticsExtensions.kt diff --git a/app/benchmark-rules.pro b/app/benchmark-rules.pro index ddecd591b..96b67f2d1 100644 --- a/app/benchmark-rules.pro +++ b/app/benchmark-rules.pro @@ -3,4 +3,16 @@ # Obsfuscation must be disabled for the build variant that generates Baseline Profile, otherwise # wrong symbols would be generated. The generated Baseline Profile will be properly applied when generated # without obfuscation and your app is being obfuscated. --dontobfuscate \ No newline at end of file +-dontobfuscate + +# Please add these rules to your existing keep rules in order to suppress warnings. +# This is generated automatically by the Android Gradle plugin. +-dontwarn org.bouncycastle.jsse.BCSSLParameters +-dontwarn org.bouncycastle.jsse.BCSSLSocket +-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider +-dontwarn org.conscrypt.Conscrypt$Version +-dontwarn org.conscrypt.Conscrypt +-dontwarn org.conscrypt.ConscryptHostnameVerifier +-dontwarn org.openjsse.javax.net.ssl.SSLParameters +-dontwarn org.openjsse.javax.net.ssl.SSLSocket +-dontwarn org.openjsse.net.ssl.OpenJSSE \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ab9d35284..1f34810ed 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -19,10 +19,11 @@ import com.android.build.api.dsl.ManagedVirtualDevice plugins { id("nowinandroid.android.application") id("nowinandroid.android.application.compose") + id("nowinandroid.android.application.flavors") id("nowinandroid.android.application.jacoco") id("nowinandroid.android.hilt") - id("nowinandroid.firebase") id("jacoco") + id("nowinandroid.android.application.firebase") } android { @@ -101,6 +102,7 @@ dependencies { implementation(project(":core:designsystem")) implementation(project(":core:data")) implementation(project(":core:model")) + implementation(project(":core:analytics")) implementation(project(":sync:work")) diff --git a/app/google-services.json b/app/google-services.json new file mode 100644 index 000000000..69f0b5da3 --- /dev/null +++ b/app/google-services.json @@ -0,0 +1,125 @@ +{ + "project_info": { + "project_number": "YourProjectId", + "project_id": "abc", + "storage_bucket": "abc" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "Your:App:Id", + "android_client_info": { + "package_name": "com.google.samples.apps.nowinandroid" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "APlaceholderAPIKeyWith-ThirtyNineCharsX" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "Your:App:Id", + "android_client_info": { + "package_name": "com.google.samples.apps.nowinandroid.demo.debug" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "APlaceholderAPIKeyWith-ThirtyNineCharsX" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "Your:App:Id", + "android_client_info": { + "package_name": "com.google.samples.apps.nowinandroid.demo.benchmark" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "APlaceholderAPIKeyWith-ThirtyNineCharsX" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "Your:App:Id", + "android_client_info": { + "package_name": "com.google.samples.apps.nowinandroid.benchmark" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "APlaceholderAPIKeyWith-ThirtyNineCharsX" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "Your:App:Id", + "android_client_info": { + "package_name": "com.google.samples.apps.nowinandroid.debug" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "APlaceholderAPIKeyWith-ThirtyNineCharsX" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "Your:App:Id", + "android_client_info": { + "package_name": "com.google.samples.apps.nowinandroid.demo" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "APlaceholderAPIKeyWith-ThirtyNineCharsX" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + + "configuration_version": "1" +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5c3b889d2..23b2e7862 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -39,6 +39,10 @@ + + + diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivity.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivity.kt index e46d2156a..5fc9d0525 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivity.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivity.kt @@ -24,6 +24,7 @@ import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi 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.getValue import androidx.compose.runtime.mutableStateOf @@ -37,6 +38,8 @@ import androidx.metrics.performance.JankStats import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.samples.apps.nowinandroid.MainActivityUiState.Loading import com.google.samples.apps.nowinandroid.MainActivityUiState.Success +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.util.NetworkMonitor import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig @@ -61,6 +64,9 @@ class MainActivity : ComponentActivity() { @Inject lateinit var networkMonitor: NetworkMonitor + @Inject + lateinit var analyticsHelper: AnalyticsHelper + val viewModel: MainActivityViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { @@ -104,15 +110,17 @@ class MainActivity : ComponentActivity() { onDispose {} } - NiaTheme( - darkTheme = darkTheme, - androidTheme = shouldUseAndroidTheme(uiState), - disableDynamicTheming = shouldDisableDynamicTheming(uiState), - ) { - NiaApp( - networkMonitor = networkMonitor, - windowSizeClass = calculateWindowSizeClass(this), - ) + CompositionLocalProvider(LocalAnalyticsHelper provides analyticsHelper) { + NiaTheme( + darkTheme = darkTheme, + androidTheme = shouldUseAndroidTheme(uiState), + disableDynamicTheming = shouldDisableDynamicTheming(uiState), + ) { + NiaApp( + networkMonitor = networkMonitor, + windowSizeClass = calculateWindowSizeClass(this), + ) + } } } } diff --git a/app/src/prod/AndroidManifest.xml b/app/src/prod/AndroidManifest.xml new file mode 100644 index 000000000..2f8a8592a --- /dev/null +++ b/app/src/prod/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + \ No newline at end of file diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index 11b8a5de1..9d7af2372 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -28,6 +28,8 @@ java { dependencies { compileOnly(libs.android.gradlePlugin) compileOnly(libs.kotlin.gradlePlugin) + compileOnly(libs.firebase.performance.gradle) + compileOnly(libs.firebase.crashlytics.gradle) } gradlePlugin { @@ -68,9 +70,14 @@ gradlePlugin { id = "nowinandroid.android.hilt" implementationClass = "AndroidHiltConventionPlugin" } - register("firebase") { - id = "nowinandroid.firebase" - implementationClass = "FirebaseConventionPlugin" + register("androidFirebase") { + id = "nowinandroid.android.application.firebase" + implementationClass = "AndroidApplicationFirebaseConventionPlugin" } + register("androidFlavors") { + id = "nowinandroid.android.application.flavors" + implementationClass = "AndroidApplicationFlavorsConventionPlugin" + } + } } diff --git a/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt index 612eb6ad4..dfbea8394 100644 --- a/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt @@ -14,9 +14,8 @@ * limitations under the License. */ -import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.android.build.api.dsl.ApplicationExtension -import com.google.samples.apps.nowinandroid.configureFlavors +import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.google.samples.apps.nowinandroid.configureKotlinAndroid import com.google.samples.apps.nowinandroid.configurePrintApksTask import org.gradle.api.Plugin @@ -34,7 +33,6 @@ class AndroidApplicationConventionPlugin : Plugin { extensions.configure { configureKotlinAndroid(this) defaultConfig.targetSdk = 33 - configureFlavors(this) } extensions.configure { configurePrintApksTask(this) diff --git a/build-logic/convention/src/main/kotlin/FirebaseConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationFirebaseConventionPlugin.kt similarity index 53% rename from build-logic/convention/src/main/kotlin/FirebaseConventionPlugin.kt rename to build-logic/convention/src/main/kotlin/AndroidApplicationFirebaseConventionPlugin.kt index 70054408d..598da727d 100644 --- a/build-logic/convention/src/main/kotlin/FirebaseConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidApplicationFirebaseConventionPlugin.kt @@ -14,17 +14,17 @@ * limitations under the License. */ +import com.android.build.api.variant.ApplicationAndroidComponentsExtension +import com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsExtension import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.getByType -class FirebaseConventionPlugin : Plugin { +class AndroidApplicationFirebaseConventionPlugin : Plugin { override fun apply(target: Project) { - // Only apply this to Google Play releases. - if (!target.hasProperty("use-google-services")) - return with(target) { with(pluginManager) { apply("com.google.gms.google-services") @@ -35,10 +35,23 @@ class FirebaseConventionPlugin : Plugin { val libs = extensions.getByType().named("libs") dependencies { val bom = libs.findLibrary("firebase-bom").get() - add("releaseImplementation", platform(bom)) - "releaseImplementation"(libs.findLibrary("firebase.analytics").get()) - "releaseImplementation"(libs.findLibrary("firebase.crashlytics").get()) - "releaseImplementation"(libs.findLibrary("firebase.performance").get()) + add("implementation", platform(bom)) + "implementation"(libs.findLibrary("firebase.analytics").get()) + "implementation"(libs.findLibrary("firebase.performance").get()) + "implementation"(libs.findLibrary("firebase.crashlytics").get()) + } + + extensions.configure { + finalizeDsl { + it.buildTypes.forEach { buildType -> + // Disable the Crashlytics mapping file upload. This feature should only be + // enabled if a Firebase backend is available and configured in + // google-services.json. + buildType.configure { + mappingFileUploadEnabled = false + } + } + } } } } diff --git a/build-logic/convention/src/main/kotlin/AndroidApplicationFlavorsConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationFlavorsConventionPlugin.kt new file mode 100644 index 000000000..46b019d7a --- /dev/null +++ b/build-logic/convention/src/main/kotlin/AndroidApplicationFlavorsConventionPlugin.kt @@ -0,0 +1,31 @@ +/* + * 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. + */ + +import com.android.build.api.dsl.ApplicationExtension +import com.google.samples.apps.nowinandroid.configureFlavors +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure + +class AndroidApplicationFlavorsConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + extensions.configure { + configureFlavors(this) + } + } + } +} \ No newline at end of file diff --git a/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt index a637b78c0..9c5ab9d44 100644 --- a/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt @@ -46,6 +46,7 @@ class AndroidFeatureConventionPlugin : Plugin { add("implementation", project(":core:data")) add("implementation", project(":core:common")) add("implementation", project(":core:domain")) + add("implementation", project(":core:analytics")) add("testImplementation", kotlin("test")) add("testImplementation", project(":core:testing")) diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt index f7d6d184a..6132e07b5 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -14,7 +14,6 @@ * limitations under the License. */ -import com.android.build.api.variant.AndroidComponentsExtension import com.android.build.api.variant.LibraryAndroidComponentsExtension import com.android.build.gradle.LibraryExtension import com.google.samples.apps.nowinandroid.configureFlavors diff --git a/build.gradle.kts b/build.gradle.kts index bd395ceac..ca7937fb5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,12 +22,6 @@ buildscript { // Android Build Server maven { url = uri("../nowinandroid-prebuilts/m2repository") } } - - dependencies { - if (project.hasProperty("use-google-services")) { - classpath(libs.firebase.crashlytics.gradle) - } - } } // Lists all plugins used throughout the project without applying them. diff --git a/core/analytics/.gitignore b/core/analytics/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/core/analytics/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/analytics/build.gradle.kts b/core/analytics/build.gradle.kts new file mode 100644 index 000000000..e42499769 --- /dev/null +++ b/core/analytics/build.gradle.kts @@ -0,0 +1,33 @@ +/* + * 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. + */ +plugins { + id("nowinandroid.android.library") + id("nowinandroid.android.library.compose") + id("nowinandroid.android.hilt") +} + +android { + namespace = "com.google.samples.apps.nowinandroid.core.analytics" +} + +dependencies { + implementation(libs.kotlinx.coroutines.android) + implementation(libs.androidx.compose.runtime) + implementation(libs.androidx.core.ktx) + + implementation(platform(libs.firebase.bom)) + implementation(libs.firebase.analytics) +} diff --git a/core/analytics/src/demo/java/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsModule.kt b/core/analytics/src/demo/java/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsModule.kt new file mode 100644 index 000000000..78ebec9e5 --- /dev/null +++ b/core/analytics/src/demo/java/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsModule.kt @@ -0,0 +1,29 @@ +/* + * 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.core.analytics + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +abstract class AnalyticsModule { + @Binds + abstract fun bindsAnalyticsHelper(analyticsHelperImpl: StubAnalyticsHelper): AnalyticsHelper +} diff --git a/core/analytics/src/main/AndroidManifest.xml b/core/analytics/src/main/AndroidManifest.xml new file mode 100644 index 000000000..a522a4c23 --- /dev/null +++ b/core/analytics/src/main/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + \ No newline at end of file diff --git a/core/analytics/src/main/java/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsEvent.kt b/core/analytics/src/main/java/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsEvent.kt new file mode 100644 index 000000000..97ae76b56 --- /dev/null +++ b/core/analytics/src/main/java/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsEvent.kt @@ -0,0 +1,58 @@ +/* + * 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.core.analytics + +/** + * Represents an analytics event. + * + * @param type - the event type. Wherever possible use one of the standard + * event `Types`, however, if there is no suitable event type already defined, a custom event can be + * defined as long as it is configured in your backend analytics system (for example, by creating a + * Firebase Analytics custom event). + * + * @param extras - list of parameters which supply additional context to the event. See `Param`. + */ +data class AnalyticsEvent( + val type: String, + val extras: List = emptyList(), +) { + // Standard analytics types. + class Types { + companion object { + const val SCREEN_VIEW = "screen_view" // (extras: SCREEN_NAME) + } + } + + /** + * A key-value pair used to supply extra context to an analytics event. + * + * @param key - the parameter key. Wherever possible use one of the standard `ParamKeys`, + * however, if no suitable key is available you can define your own as long as it is configured + * in your backend analytics system (for example, by creating a Firebase Analytics custom + * parameter). + * + * @param value - the parameter value. + */ + data class Param(val key: String, val value: String) + + // Standard parameter keys. + class ParamKeys { + companion object { + const val SCREEN_NAME = "screen_name" + } + } +} diff --git a/core/analytics/src/main/java/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsHelper.kt b/core/analytics/src/main/java/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsHelper.kt new file mode 100644 index 000000000..f9e6dad44 --- /dev/null +++ b/core/analytics/src/main/java/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsHelper.kt @@ -0,0 +1,25 @@ +/* + * 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.core.analytics + +/** + * Interface for logging analytics events. See `FirebaseAnalyticsHelper` and + * `StubAnalyticsHelper` for implementations. + */ +interface AnalyticsHelper { + fun logEvent(event: AnalyticsEvent) +} diff --git a/core/analytics/src/main/java/com/google/samples/apps/nowinandroid/core/analytics/NoOpAnalyticsHelper.kt b/core/analytics/src/main/java/com/google/samples/apps/nowinandroid/core/analytics/NoOpAnalyticsHelper.kt new file mode 100644 index 000000000..16a193439 --- /dev/null +++ b/core/analytics/src/main/java/com/google/samples/apps/nowinandroid/core/analytics/NoOpAnalyticsHelper.kt @@ -0,0 +1,24 @@ +/* + * 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.core.analytics + +/** + * Implementation of AnalyticsHelper which does nothing. Useful for tests and previews. + */ +class NoOpAnalyticsHelper : AnalyticsHelper { + override fun logEvent(event: AnalyticsEvent) = Unit +} diff --git a/core/analytics/src/main/java/com/google/samples/apps/nowinandroid/core/analytics/StubAnalyticsHelper.kt b/core/analytics/src/main/java/com/google/samples/apps/nowinandroid/core/analytics/StubAnalyticsHelper.kt new file mode 100644 index 000000000..2ff022287 --- /dev/null +++ b/core/analytics/src/main/java/com/google/samples/apps/nowinandroid/core/analytics/StubAnalyticsHelper.kt @@ -0,0 +1,34 @@ +/* + * 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.core.analytics + +import android.util.Log +import javax.inject.Inject +import javax.inject.Singleton + +private const val TAG = "StubAnalyticsHelper" + +/** + * An implementation of AnalyticsHelper just writes the events to logcat. Used in builds where no + * analytics events should be sent to a backend. + */ +@Singleton +class StubAnalyticsHelper @Inject constructor() : AnalyticsHelper { + override fun logEvent(event: AnalyticsEvent) { + Log.d(TAG, "Received analytics event: $event") + } +} diff --git a/core/analytics/src/main/java/com/google/samples/apps/nowinandroid/core/analytics/UiHelpers.kt b/core/analytics/src/main/java/com/google/samples/apps/nowinandroid/core/analytics/UiHelpers.kt new file mode 100644 index 000000000..b0e5d29d8 --- /dev/null +++ b/core/analytics/src/main/java/com/google/samples/apps/nowinandroid/core/analytics/UiHelpers.kt @@ -0,0 +1,28 @@ +/* + * 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.core.analytics + +import androidx.compose.runtime.staticCompositionLocalOf + +/** + * Global key used to obtain access to the AnalyticsHelper through a CompositionLocal. + */ +val LocalAnalyticsHelper = staticCompositionLocalOf { + // Provide a default AnalyticsHelper which does nothing. This is so that tests and previews + // do not have to provide one. For real app builds provide a different implementation. + NoOpAnalyticsHelper() +} diff --git a/core/analytics/src/prod/java/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsModule.kt b/core/analytics/src/prod/java/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsModule.kt new file mode 100644 index 000000000..e947d036b --- /dev/null +++ b/core/analytics/src/prod/java/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsModule.kt @@ -0,0 +1,29 @@ +/* + * 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.core.analytics + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +abstract class AnalyticsModule { + @Binds + abstract fun bindsAnalyticsHelper(analyticsHelperImpl: FirebaseAnalyticsHelper): AnalyticsHelper +} diff --git a/core/analytics/src/prod/java/com/google/samples/apps/nowinandroid/core/analytics/FirebaseAnalyticsHelper.kt b/core/analytics/src/prod/java/com/google/samples/apps/nowinandroid/core/analytics/FirebaseAnalyticsHelper.kt new file mode 100644 index 000000000..96b9ce67d --- /dev/null +++ b/core/analytics/src/prod/java/com/google/samples/apps/nowinandroid/core/analytics/FirebaseAnalyticsHelper.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.core.analytics + +import com.google.firebase.analytics.ktx.analytics +import com.google.firebase.analytics.ktx.logEvent +import com.google.firebase.ktx.Firebase +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Implementation of `AnalyticsHelper` which logs events to a Firebase backend. + */ +@Singleton +class FirebaseAnalyticsHelper @Inject constructor() : AnalyticsHelper { + + private val firebaseAnalytics = Firebase.analytics + + override fun logEvent(event: AnalyticsEvent) { + firebaseAnalytics.logEvent(event.type) { + for (extra in event.extras) { + // Truncate parameter keys and values according to firebase maximum length values. + param( + key = extra.key.take(40), + value = extra.value.take(100), + ) + } + } + } +} diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts index 717082bfe..5b468c43e 100644 --- a/core/data/build.gradle.kts +++ b/core/data/build.gradle.kts @@ -35,6 +35,7 @@ dependencies { implementation(project(":core:database")) implementation(project(":core:datastore")) implementation(project(":core:network")) + implementation(project(":core:analytics")) testImplementation(project(":core:testing")) testImplementation(project(":core:datastore-test")) diff --git a/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/AnalyticsExtensions.kt b/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/AnalyticsExtensions.kt new file mode 100644 index 000000000..d36f509d9 --- /dev/null +++ b/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/AnalyticsExtensions.kt @@ -0,0 +1,84 @@ +/* + * 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.core.data.repository + +import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsEvent +import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsEvent.Param +import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper + +fun AnalyticsHelper.logNewsResourceBookmarkToggled(newsResourceId: String, isBookmarked: Boolean) { + val eventType = if (isBookmarked) "news_resource_saved" else "news_resource_unsaved" + val paramKey = if (isBookmarked) "saved_news_resource_id" else "unsaved_news_resource_id" + logEvent( + AnalyticsEvent( + type = eventType, + extras = listOf( + Param(key = paramKey, value = newsResourceId), + ), + ), + ) +} + +fun AnalyticsHelper.logTopicFollowToggled(followedTopicId: String, isFollowed: Boolean) { + val eventType = if (isFollowed) "topic_followed" else "topic_unfollowed" + val paramKey = if (isFollowed) "followed_topic_id" else "unfollowed_topic_id" + logEvent( + AnalyticsEvent( + type = eventType, + extras = listOf( + Param(key = paramKey, value = followedTopicId), + ), + ), + ) +} + +fun AnalyticsHelper.logThemeChanged(themeName: String) = + logEvent( + AnalyticsEvent( + type = "theme_changed", + extras = listOf( + Param(key = "theme_name", value = themeName), + ), + ), + ) + +fun AnalyticsHelper.logDarkThemeConfigChanged(darkThemeConfigName: String) = + logEvent( + AnalyticsEvent( + type = "dark_theme_config_changed", + extras = listOf( + Param(key = "dark_theme_config", value = darkThemeConfigName), + ), + ), + ) + +fun AnalyticsHelper.logDynamicColorPreferenceChanged(useDynamicColor: Boolean) = + logEvent( + AnalyticsEvent( + type = "dynamic_color_preference_changed", + extras = listOf( + Param(key = "dynamic_color_preference", value = useDynamicColor.toString()), + ), + ), + ) + +fun AnalyticsHelper.logOnboardingStateChanged(shouldHideOnboarding: Boolean) { + val eventType = if (shouldHideOnboarding) "onboarding_complete" else "onboarding_reset" + logEvent( + AnalyticsEvent(type = eventType), + ) +} diff --git a/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepository.kt b/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepository.kt index 200ca4a3d..334209538 100644 --- a/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepository.kt +++ b/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepository.kt @@ -16,6 +16,8 @@ package com.google.samples.apps.nowinandroid.core.data.repository +import androidx.annotation.VisibleForTesting +import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper import com.google.samples.apps.nowinandroid.core.datastore.NiaPreferencesDataSource import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand @@ -25,29 +27,46 @@ import javax.inject.Inject class OfflineFirstUserDataRepository @Inject constructor( private val niaPreferencesDataSource: NiaPreferencesDataSource, + private val analyticsHelper: AnalyticsHelper, ) : UserDataRepository { override val userData: Flow = niaPreferencesDataSource.userData + @VisibleForTesting override suspend fun setFollowedTopicIds(followedTopicIds: Set) = niaPreferencesDataSource.setFollowedTopicIds(followedTopicIds) - override suspend fun toggleFollowedTopicId(followedTopicId: String, followed: Boolean) = + override suspend fun toggleFollowedTopicId(followedTopicId: String, followed: Boolean) { niaPreferencesDataSource.toggleFollowedTopicId(followedTopicId, followed) + analyticsHelper.logTopicFollowToggled(followedTopicId, followed) + } - override suspend fun updateNewsResourceBookmark(newsResourceId: String, bookmarked: Boolean) = + override suspend fun updateNewsResourceBookmark(newsResourceId: String, bookmarked: Boolean) { niaPreferencesDataSource.toggleNewsResourceBookmark(newsResourceId, bookmarked) + analyticsHelper.logNewsResourceBookmarkToggled( + newsResourceId = newsResourceId, + isBookmarked = bookmarked, + ) + } - override suspend fun setThemeBrand(themeBrand: ThemeBrand) = + override suspend fun setThemeBrand(themeBrand: ThemeBrand) { niaPreferencesDataSource.setThemeBrand(themeBrand) + analyticsHelper.logThemeChanged(themeBrand.name) + } - override suspend fun setDarkThemeConfig(darkThemeConfig: DarkThemeConfig) = + override suspend fun setDarkThemeConfig(darkThemeConfig: DarkThemeConfig) { niaPreferencesDataSource.setDarkThemeConfig(darkThemeConfig) + analyticsHelper.logDarkThemeConfigChanged(darkThemeConfig.name) + } - override suspend fun setDynamicColorPreference(useDynamicColor: Boolean) = + override suspend fun setDynamicColorPreference(useDynamicColor: Boolean) { niaPreferencesDataSource.setDynamicColorPreference(useDynamicColor) + analyticsHelper.logDynamicColorPreferenceChanged(useDynamicColor) + } - override suspend fun setShouldHideOnboarding(shouldHideOnboarding: Boolean) = + override suspend fun setShouldHideOnboarding(shouldHideOnboarding: Boolean) { niaPreferencesDataSource.setShouldHideOnboarding(shouldHideOnboarding) + analyticsHelper.logOnboardingStateChanged(shouldHideOnboarding) + } } diff --git a/core/data/src/test/java/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepositoryTest.kt b/core/data/src/test/java/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepositoryTest.kt index 055d8e074..61569d650 100644 --- a/core/data/src/test/java/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepositoryTest.kt +++ b/core/data/src/test/java/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepositoryTest.kt @@ -16,6 +16,7 @@ package com.google.samples.apps.nowinandroid.core.data.repository +import com.google.samples.apps.nowinandroid.core.analytics.NoOpAnalyticsHelper import com.google.samples.apps.nowinandroid.core.datastore.NiaPreferencesDataSource import com.google.samples.apps.nowinandroid.core.datastore.test.testUserPreferencesDataStore import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig @@ -37,6 +38,8 @@ class OfflineFirstUserDataRepositoryTest { private lateinit var niaPreferencesDataSource: NiaPreferencesDataSource + private val analyticsHelper = NoOpAnalyticsHelper() + @get:Rule val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build() @@ -48,6 +51,7 @@ class OfflineFirstUserDataRepositoryTest { subject = OfflineFirstUserDataRepository( niaPreferencesDataSource = niaPreferencesDataSource, + analyticsHelper, ) } diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 39f9bcff1..0438b8f36 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -30,6 +30,7 @@ dependencies { implementation(project(":core:designsystem")) implementation(project(":core:model")) implementation(project(":core:domain")) + implementation(project(":core:analytics")) implementation(libs.androidx.browser) implementation(libs.androidx.core.ktx) diff --git a/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/AnalyticsExtensions.kt b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/AnalyticsExtensions.kt new file mode 100644 index 000000000..38bce838a --- /dev/null +++ b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/AnalyticsExtensions.kt @@ -0,0 +1,63 @@ +/* + * 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.core.ui + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsEvent +import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsEvent.Param +import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsEvent.ParamKeys +import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsEvent.Types +import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper +import com.google.samples.apps.nowinandroid.core.analytics.LocalAnalyticsHelper + +/** + * Classes and functions associated with analytics events for the UI. + */ +fun AnalyticsHelper.logScreenView(screenName: String) { + logEvent( + AnalyticsEvent( + type = Types.SCREEN_VIEW, + extras = listOf( + Param(ParamKeys.SCREEN_NAME, screenName), + ), + ), + ) +} + +fun AnalyticsHelper.logNewsResourceOpened(newsResourceId: String, newsResourceTitle: String) { + logEvent( + event = AnalyticsEvent( + type = "news_resource_opened", + extras = listOf( + Param("opened_news_resource", newsResourceId), + ), + ), + ) +} + +/** + * A side-effect which records a screen view event. + */ +@Composable +fun TrackScreenViewEvent( + screenName: String, + analyticsHelper: AnalyticsHelper = LocalAnalyticsHelper.current, +) = DisposableEffect(Unit) { + analyticsHelper.logScreenView(screenName) + onDispose {} +} diff --git a/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt index 73c1a21e4..bad60961b 100644 --- a/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt +++ b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import com.google.samples.apps.nowinandroid.core.analytics.LocalAnalyticsHelper import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.domain.model.UserNewsResource @@ -56,12 +57,19 @@ fun LazyGridScope.newsFeed( mutableStateOf(Uri.parse(userNewsResource.url)) } val context = LocalContext.current + val analyticsHelper = LocalAnalyticsHelper.current val backgroundColor = MaterialTheme.colorScheme.background.toArgb() NewsResourceCardExpanded( userNewsResource = userNewsResource, isBookmarked = userNewsResource.isSaved, - onClick = { launchCustomChromeTab(context, resourceUrl, backgroundColor) }, + onClick = { + analyticsHelper.logNewsResourceOpened( + newsResourceId = userNewsResource.id, + newsResourceTitle = userNewsResource.title, + ) + launchCustomChromeTab(context, resourceUrl, backgroundColor) + }, onToggleBookmark = { onNewsResourcesCheckedChanged( userNewsResource.id, diff --git a/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardList.kt b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardList.kt index a63bae657..87f110b36 100644 --- a/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardList.kt +++ b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardList.kt @@ -23,6 +23,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext +import com.google.samples.apps.nowinandroid.core.analytics.LocalAnalyticsHelper import com.google.samples.apps.nowinandroid.core.domain.model.UserNewsResource /** @@ -45,12 +46,17 @@ fun LazyListScope.userNewsResourceCardItems( val resourceUrl = Uri.parse(userNewsResource.url) val backgroundColor = MaterialTheme.colorScheme.background.toArgb() val context = LocalContext.current + val analyticsHelper = LocalAnalyticsHelper.current NewsResourceCardExpanded( userNewsResource = userNewsResource, isBookmarked = userNewsResource.isSaved, onToggleBookmark = { onToggleBookmark(userNewsResource) }, onClick = { + analyticsHelper.logNewsResourceOpened( + newsResourceId = userNewsResource.id, + newsResourceTitle = userNewsResource.title, + ) when (onItemClick) { null -> launchCustomChromeTab(context, resourceUrl, backgroundColor) else -> onItemClick(userNewsResource) diff --git a/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt b/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt index 1169f5777..a5d290897 100644 --- a/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt +++ b/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt @@ -59,6 +59,7 @@ import com.google.samples.apps.nowinandroid.core.domain.model.UserNewsResource import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState.Loading import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState.Success +import com.google.samples.apps.nowinandroid.core.ui.TrackScreenViewEvent import com.google.samples.apps.nowinandroid.core.ui.TrackScrollJank import com.google.samples.apps.nowinandroid.core.ui.UserNewsResourcePreviewParameterProvider import com.google.samples.apps.nowinandroid.core.ui.newsFeed @@ -95,6 +96,7 @@ internal fun BookmarksScreen( EmptyState(modifier) } } + TrackScreenViewEvent(screenName = "Saved") } @Composable diff --git a/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt b/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt index 492660142..52de0204b 100644 --- a/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt +++ b/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt @@ -85,6 +85,7 @@ import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.domain.model.UserNewsResource import com.google.samples.apps.nowinandroid.core.ui.DevicePreviews import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState +import com.google.samples.apps.nowinandroid.core.ui.TrackScreenViewEvent import com.google.samples.apps.nowinandroid.core.ui.TrackScrollJank import com.google.samples.apps.nowinandroid.core.ui.UserNewsResourcePreviewParameterProvider import com.google.samples.apps.nowinandroid.core.ui.newsFeed @@ -207,6 +208,7 @@ internal fun ForYouScreen( ) } } + TrackScreenViewEvent(screenName = "ForYou") } /** diff --git a/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt b/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt index fd45c7608..c8558cb2b 100644 --- a/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt +++ b/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt @@ -33,6 +33,7 @@ import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic import com.google.samples.apps.nowinandroid.core.ui.DevicePreviews import com.google.samples.apps.nowinandroid.core.ui.FollowableTopicPreviewParameterProvider +import com.google.samples.apps.nowinandroid.core.ui.TrackScreenViewEvent @OptIn(ExperimentalLifecycleComposeApi::class) @Composable @@ -78,6 +79,7 @@ internal fun InterestsScreen( is InterestsUiState.Empty -> InterestsEmptyScreen() } } + TrackScreenViewEvent(screenName = "Interests") } @Composable diff --git a/feature/settings/src/main/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt b/feature/settings/src/main/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt index bed230d0d..f6620d00e 100644 --- a/feature/settings/src/main/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt +++ b/feature/settings/src/main/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt @@ -61,6 +61,7 @@ import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig.LIGH import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand.ANDROID import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand.DEFAULT +import com.google.samples.apps.nowinandroid.core.ui.TrackScreenViewEvent import com.google.samples.apps.nowinandroid.feature.settings.R.string import com.google.samples.apps.nowinandroid.feature.settings.SettingsUiState.Loading import com.google.samples.apps.nowinandroid.feature.settings.SettingsUiState.Success @@ -134,6 +135,7 @@ fun SettingsDialog( Divider(Modifier.padding(top = 8.dp)) LinksPanel() } + TrackScreenViewEvent(screenName = "Settings") }, confirmButton = { Text( diff --git a/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt b/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt index e7b218072..37e76daec 100644 --- a/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt +++ b/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt @@ -55,6 +55,7 @@ import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic import com.google.samples.apps.nowinandroid.core.domain.model.UserNewsResource import com.google.samples.apps.nowinandroid.core.ui.DevicePreviews +import com.google.samples.apps.nowinandroid.core.ui.TrackScreenViewEvent import com.google.samples.apps.nowinandroid.core.ui.TrackScrollJank import com.google.samples.apps.nowinandroid.core.ui.UserNewsResourcePreviewParameterProvider import com.google.samples.apps.nowinandroid.core.ui.userNewsResourceCardItems @@ -71,6 +72,7 @@ internal fun TopicRoute( val topicUiState: TopicUiState by viewModel.topicUiState.collectAsStateWithLifecycle() val newsUiState: NewsUiState by viewModel.newUiState.collectAsStateWithLifecycle() + TrackScreenViewEvent(screenName = "Topic: ${viewModel.topicId}") TopicScreen( topicUiState = topicUiState, newsUiState = newsUiState, diff --git a/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt b/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt index c0c6bbafd..c58d40b5e 100644 --- a/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt +++ b/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt @@ -50,6 +50,8 @@ class TopicViewModel @Inject constructor( private val topicArgs: TopicArgs = TopicArgs(savedStateHandle, stringDecoder) + val topicId = topicArgs.topicId + val topicUiState: StateFlow = topicUiState( topicId = topicArgs.topicId, userDataRepository = userDataRepository, diff --git a/feature/topic/src/test/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt b/feature/topic/src/test/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt index a8f1b0a88..dfed60385 100644 --- a/feature/topic/src/test/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt +++ b/feature/topic/src/test/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt @@ -70,6 +70,10 @@ class TopicViewModelTest { ) } + @Test + fun topicId_matchesTopicIdFromSavedStateHandle() = + assertEquals(testInputTopics[0].topic.id, viewModel.topicId) + @Test fun uiStateTopic_whenSuccess_matchesTopicFromRepository() = runTest { val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.topicUiState.collect() } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 00fdd422f..2716a1f77 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,7 +28,7 @@ androidxUiAutomator = "2.2.0" androidxWindowManager = "1.0.0" androidxWork = "2.7.1" coil = "2.2.2" -firebaseBom = "31.0.3" +firebaseBom = "31.2.0" firebaseCrashlyticsPlugin = "2.9.2" firebasePerfPlugin = "1.4.2" gmsPlugin = "4.3.14" @@ -105,6 +105,7 @@ firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics-ktx"} firebase-crashlytics-gradle = { group = "com.google.firebase", name="firebase-crashlytics-gradle", version.ref = "firebaseCrashlyticsPlugin"} firebase-performance = { group = "com.google.firebase", name = "firebase-perf-ktx"} +firebase-performance-gradle = { group = "com.google.firebase", name = "perf-plugin", version.ref = "firebasePerfPlugin"} hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } hilt-android-testing = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" } hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 809efd27d..857f9d56c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -46,6 +46,8 @@ include(":core:model") include(":core:network") include(":core:ui") include(":core:testing") +include(":core:analytics") + include(":feature:foryou") include(":feature:interests") include(":feature:bookmarks") diff --git a/sync/work/build.gradle.kts b/sync/work/build.gradle.kts index 70f6b2e89..a3b589db3 100644 --- a/sync/work/build.gradle.kts +++ b/sync/work/build.gradle.kts @@ -31,6 +31,7 @@ dependencies { implementation(project(":core:model")) implementation(project(":core:data")) implementation(project(":core:datastore")) + implementation(project(":core:analytics")) implementation(libs.kotlinx.coroutines.android) diff --git a/sync/work/src/main/java/com/google/samples/apps/nowinandroid/sync/workers/AnalyticsExtensions.kt b/sync/work/src/main/java/com/google/samples/apps/nowinandroid/sync/workers/AnalyticsExtensions.kt new file mode 100644 index 000000000..d5250b330 --- /dev/null +++ b/sync/work/src/main/java/com/google/samples/apps/nowinandroid/sync/workers/AnalyticsExtensions.kt @@ -0,0 +1,32 @@ +/* + * 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.sync.workers + +import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsEvent +import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper + +fun AnalyticsHelper.logSyncStarted() = + logEvent( + AnalyticsEvent(type = "network_sync_started"), + ) + +fun AnalyticsHelper.logSyncFinished(syncedSuccessfully: Boolean) { + val eventType = if (syncedSuccessfully) "network_sync_successful" else "network_sync_failed" + logEvent( + AnalyticsEvent(type = eventType), + ) +} diff --git a/sync/work/src/main/java/com/google/samples/apps/nowinandroid/sync/workers/SyncWorker.kt b/sync/work/src/main/java/com/google/samples/apps/nowinandroid/sync/workers/SyncWorker.kt index c6ac6fb65..211940ddb 100644 --- a/sync/work/src/main/java/com/google/samples/apps/nowinandroid/sync/workers/SyncWorker.kt +++ b/sync/work/src/main/java/com/google/samples/apps/nowinandroid/sync/workers/SyncWorker.kt @@ -24,6 +24,7 @@ import androidx.work.ForegroundInfo import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OutOfQuotaPolicy import androidx.work.WorkerParameters +import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper import com.google.samples.apps.nowinandroid.core.data.Synchronizer import com.google.samples.apps.nowinandroid.core.data.repository.NewsRepository import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository @@ -52,6 +53,7 @@ class SyncWorker @AssistedInject constructor( private val topicRepository: TopicsRepository, private val newsRepository: NewsRepository, @Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher, + private val analyticsHelper: AnalyticsHelper, ) : CoroutineWorker(appContext, workerParams), Synchronizer { override suspend fun getForegroundInfo(): ForegroundInfo = @@ -59,12 +61,16 @@ class SyncWorker @AssistedInject constructor( override suspend fun doWork(): Result = withContext(ioDispatcher) { traceAsync("Sync", 0) { + analyticsHelper.logSyncStarted() + // First sync the repositories in parallel val syncedSuccessfully = awaitAll( async { topicRepository.sync() }, async { newsRepository.sync() }, ).all { it } + analyticsHelper.logSyncFinished(syncedSuccessfully) + if (syncedSuccessfully) { Result.success() } else { From 11a4bbaa48431e4a5d1d14fcccc3ef380085552e Mon Sep 17 00:00:00 2001 From: Don Turner Date: Thu, 9 Feb 2023 18:03:48 +0000 Subject: [PATCH 18/20] Remove AD_ID permission Change-Id: Iaf691c8ff1e8fbbbcd39b068d8bfdc29c8f6a58c --- app/src/main/AndroidManifest.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 23b2e7862..99c233910 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,6 +19,13 @@ + + + + + From 75f8fc7e0834dfb7efa630bea9fe70c2df350a29 Mon Sep 17 00:00:00 2001 From: Don Turner Date: Thu, 9 Feb 2023 18:11:07 +0000 Subject: [PATCH 19/20] Bump versionCode to 5 Change-Id: I4d6dc6b8d1a903172b7f11ab6166f6c631b1319b --- app/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a8bb4365b..b7cb7d1d7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -29,8 +29,8 @@ plugins { android { defaultConfig { applicationId = "com.google.samples.apps.nowinandroid" - versionCode = 4 - versionName = "0.0.4" // X.Y.Z; X = Major, Y = minor, Z = Patch level + versionCode = 5 + versionName = "0.0.5" // X.Y.Z; X = Major, Y = minor, Z = Patch level // Custom test runner to set up Hilt dependency graph testInstrumentationRunner = "com.google.samples.apps.nowinandroid.core.testing.NiaTestRunner" From bdb05c00d9fe319afda098be291d36c7795db470 Mon Sep 17 00:00:00 2001 From: Don Turner Date: Thu, 9 Feb 2023 19:33:53 +0000 Subject: [PATCH 20/20] Revert "Revert "Revert "Revert "New build trigger"""" This reverts commit 71303b6f6126968e4a6a923d9ee9c93a08aa2454. Reason for revert: Stupid build is stuck again. Change-Id: I6cfb412e0ce46233b9e6ee5ed031019381ce5600 --- .google/BUILDME | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.google/BUILDME b/.google/BUILDME index d6b23eab3..5295ed188 100644 --- a/.google/BUILDME +++ b/.google/BUILDME @@ -1,2 +1,2 @@ # This file can be used to trigger an internal build by changing the number below -2 +3