From ed5edeb5fbfd650ec72dd461c1d27c5c45b14f85 Mon Sep 17 00:00:00 2001 From: Vicky Chijwani Date: Fri, 22 Feb 2019 00:46:20 +0530 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=94=A7=20Integrate=20Firebase=20Analy?= =?UTF-8?q?tics=20(in=20addition=20to=20Fabric=20Answers,=20which=20will?= =?UTF-8?q?=20be=20removed=20eventually)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/.gitignore | 1 + app/build.gradle | 3 + .../spectre/SpectreApplication.java | 7 +- .../spectre/analytics/AnalyticsService.java | 133 +++++++++---- .../spectre/util/BundleBuilder.kt | 183 ++++++++++++++++++ build.gradle | 2 + 6 files changed, 280 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/me/vickychijwani/spectre/util/BundleBuilder.kt diff --git a/app/.gitignore b/app/.gitignore index dce4db21..2d50982f 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1,4 +1,5 @@ /build /crashlytics.properties /fabric.properties +/google-services.json diff --git a/app/build.gradle b/app/build.gradle index 22374dcf..b1eeb9a5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -151,6 +151,7 @@ dependencies { implementation("com.crashlytics.sdk.android:answers:1.3.7@aar") { transitive = true } + implementation 'com.google.firebase:firebase-core:16.0.7' implementation "com.github.hotchemi:permissionsdispatcher:$rootProject.ext.permissionsDispatcherVersion" kapt "com.github.hotchemi:permissionsdispatcher-processor:$rootProject.ext.permissionsDispatcherVersion" implementation "com.github.slugify:slugify:2.1.9" @@ -183,3 +184,5 @@ dependencies { androidTestImplementation "com.android.support.test.espresso:espresso-intents:$rootProject.ext.espressoVersion" androidTestImplementation "com.android.support.test.espresso:espresso-contrib:$rootProject.ext.espressoVersion" } + +apply plugin: 'com.google.gms.google-services' diff --git a/app/src/main/java/me/vickychijwani/spectre/SpectreApplication.java b/app/src/main/java/me/vickychijwani/spectre/SpectreApplication.java index 3465ac43..f6a47274 100644 --- a/app/src/main/java/me/vickychijwani/spectre/SpectreApplication.java +++ b/app/src/main/java/me/vickychijwani/spectre/SpectreApplication.java @@ -46,9 +46,6 @@ public class SpectreApplication extends Application { protected OkHttpClient mOkHttpClient = null; protected Picasso mPicasso = null; - @SuppressWarnings("FieldCanBeLocal") - private AnalyticsService mAnalyticsService = null; - // FIXME hacks private LoginOrchestrator.HACKListener mHACKListener; @@ -61,6 +58,7 @@ public void onCreate() { Log.i(TAG, "APP LAUNCHED"); BusProvider.getBus().register(this); + AnalyticsService.start(this, BusProvider.getBus()); sInstance = this; RxJavaPlugins.setErrorHandler(this::uncaughtRxException); @@ -73,9 +71,6 @@ public void onCreate() { NetworkService networkService = new NetworkService(); mHACKListener = networkService; networkService.start(mOkHttpClient); - - mAnalyticsService = new AnalyticsService(BusProvider.getBus()); - mAnalyticsService.start(); } private void setupMetadataRealm() { diff --git a/app/src/main/java/me/vickychijwani/spectre/analytics/AnalyticsService.java b/app/src/main/java/me/vickychijwani/spectre/analytics/AnalyticsService.java index 89bfa636..e2cc7206 100644 --- a/app/src/main/java/me/vickychijwani/spectre/analytics/AnalyticsService.java +++ b/app/src/main/java/me/vickychijwani/spectre/analytics/AnalyticsService.java @@ -1,11 +1,13 @@ package me.vickychijwani.spectre.analytics; +import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import com.crashlytics.android.answers.LoginEvent; +import com.google.firebase.analytics.FirebaseAnalytics; import com.squareup.otto.Bus; import com.squareup.otto.Subscribe; @@ -15,43 +17,39 @@ import me.vickychijwani.spectre.event.LoginDoneEvent; import me.vickychijwani.spectre.event.LoginErrorEvent; import me.vickychijwani.spectre.event.LogoutStatusEvent; +import me.vickychijwani.spectre.util.BundleBuilder; import me.vickychijwani.spectre.util.log.Log; public class AnalyticsService { private static final String TAG = AnalyticsService.class.getSimpleName(); - private final Bus mEventBus; + private static FirebaseAnalytics sFirebaseAnalytics; - public AnalyticsService(Bus eventBus) { - mEventBus = eventBus; - } - - public void start() { - getBus().register(this); - getBus().post(new LoadGhostVersionEvent(true)); - } + private static Bus sEventBus; - public void stop() { - getBus().unregister(this); - } + private static Listener sEventBusListener; - @Subscribe - public void onLoginDoneEvent(LoginDoneEvent event) { - logLogin(event.blogUrl, true); + private AnalyticsService() {} - // user just logged in, now's a good time to check this - getBus().post(new LoadGhostVersionEvent(true)); - } + /** + * Ideally the `context` parameter should be an Activity context in order for page views to be + * recorded automatically, but we don't care about that particular feature, and initializing + * with the app context is certainly more convenient. + * @param context - to initialize Firebase Analytics. NOTE: NO REFERENCE to the object is stored + * @param eventBus - to listen for analytics triggers and respond + */ + public static void start(Context context, Bus eventBus) { + sFirebaseAnalytics = FirebaseAnalytics.getInstance(context); + sEventBus = eventBus; + sEventBusListener = new Listener(sEventBus); - @Subscribe - public void onLoginErrorEvent(LoginErrorEvent event) { - logLogin(event.blogUrl, false); + sEventBus.register(sEventBusListener); + sEventBus.post(new LoadGhostVersionEvent(true)); } - @Subscribe - public void onGhostVersionLoadedEvent(GhostVersionLoadedEvent event) { - logGhostVersion(event.version); + public static void stop() { + sEventBus.unregister(sEventBusListener); } private static void logGhostVersion(@Nullable String ghostVersion) { @@ -61,6 +59,10 @@ private static void logGhostVersion(@Nullable String ghostVersion) { Log.i(TAG, "GHOST VERSION = %s", ghostVersion); Answers.getInstance().logCustom(new CustomEvent("Ghost Version") .putCustomAttribute("version", ghostVersion)); + sFirebaseAnalytics.logEvent("ghost_version", new BundleBuilder() + .put("version", ghostVersion) + .build()); + sFirebaseAnalytics.setUserProperty("ghost_version", ghostVersion); } private static void logLogin(@Nullable String blogUrl, boolean success) { @@ -72,31 +74,45 @@ private static void logLogin(@Nullable String blogUrl, boolean success) { Answers.getInstance().logLogin(new LoginEvent() .putCustomAttribute("URL", blogUrl) .putSuccess(success)); + sFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.LOGIN, new BundleBuilder() + .put("url", blogUrl) + // Param.SUCCESS expects a long value: https://firebase.google.com/docs/reference/android/com/google/firebase/analytics/FirebaseAnalytics.Param#SUCCESS + .put(FirebaseAnalytics.Param.SUCCESS, success ? 1L : 0L) + .build()); } - public static void logGhostV0Error() { - Log.i(TAG, "GHOST VERSION 0.x ERROR - UPGRADE REQUIRED"); - Answers.getInstance().logCustom(new CustomEvent("Ghost v0.x error")); - } - - @Subscribe - public void onLogoutStatusEvent(LogoutStatusEvent logoutEvent) { - if (logoutEvent.succeeded) { + private static void logLogout(boolean logoutSucceeded) { + if (logoutSucceeded) { Log.i(TAG, "LOGOUT SUCCEEDED"); Answers.getInstance().logCustom(new CustomEvent("Logout")); + sFirebaseAnalytics.logEvent("logout", new BundleBuilder().build()); } } + public static void logGhostV0Error() { + Log.i(TAG, "GHOST VERSION 0.x ERROR - UPGRADE REQUIRED"); + Answers.getInstance().logCustom(new CustomEvent("Ghost v0.x error")); + sFirebaseAnalytics.logEvent("ghost_v0_error", new BundleBuilder().build()); + } + public static void logMetadataDbSchemaVersion(@NonNull String metadataDbSchemaVersion) { Log.i(TAG, "METADATA DB SCHEMA VERSION = %s", metadataDbSchemaVersion); Answers.getInstance().logCustom(new CustomEvent("Metadata DB Schema Version") .putCustomAttribute("version", metadataDbSchemaVersion)); + sFirebaseAnalytics.logEvent("metadata_db", new BundleBuilder() + .put("schema_version", metadataDbSchemaVersion) + .build()); + sFirebaseAnalytics.setUserProperty("metadata_db_version", metadataDbSchemaVersion); } public static void logDbSchemaVersion(@NonNull String dbSchemaVersion) { Log.i(TAG, "DB SCHEMA VERSION = %s", dbSchemaVersion); Answers.getInstance().logCustom(new CustomEvent("DB Schema Version") .putCustomAttribute("version", dbSchemaVersion)); + sFirebaseAnalytics.logEvent("data_db", new BundleBuilder() + .put("schema_version", dbSchemaVersion) + .build()); + sFirebaseAnalytics.setUserProperty("blog_db_schema_version", dbSchemaVersion); } @@ -153,26 +169,57 @@ public static void logConflictResolved() { logPostAction("Conflict resolved", null); } - @Subscribe - public void onFileUploadedEvent(FileUploadedEvent event) { - logPostAction("Image uploaded", null); - } - private static void logPostAction(@NonNull String postAction, @Nullable String postUrl) { - CustomEvent postStatsEvent = new CustomEvent("Post Actions") + CustomEvent postActionEvent = new CustomEvent("Post Actions") .putCustomAttribute("Scenario", postAction); + BundleBuilder postActionBundle = new BundleBuilder() + .put("scenario", postAction); if (postUrl != null) { // FIXME this is a huge hack, also Fabric only shows 10 of these per day - postStatsEvent.putCustomAttribute("URL", postUrl); + postActionEvent.putCustomAttribute("URL", postUrl); + postActionBundle.put("url", postUrl); } Log.i(TAG, "POST ACTION: %s", postAction); - Answers.getInstance().logCustom(postStatsEvent); + Answers.getInstance().logCustom(postActionEvent); + + sFirebaseAnalytics.logEvent("post_action", postActionBundle.build()); } - // misc private methods - private Bus getBus() { - return mEventBus; + private static class Listener { + private final Bus mEventBus; + + Listener(Bus eventBus) { + mEventBus = eventBus; + } + + @Subscribe + public void onLoginDoneEvent(LoginDoneEvent event) { + logLogin(event.blogUrl, true); + + // user just logged in, now's a good time to check this + mEventBus.post(new LoadGhostVersionEvent(true)); + } + + @Subscribe + public void onLoginErrorEvent(LoginErrorEvent event) { + logLogin(event.blogUrl, false); + } + + @Subscribe + public void onGhostVersionLoadedEvent(GhostVersionLoadedEvent event) { + logGhostVersion(event.version); + } + + @Subscribe + public void onLogoutStatusEvent(LogoutStatusEvent logoutEvent) { + logLogout(logoutEvent.succeeded); + } + + @Subscribe + public void onFileUploadedEvent(FileUploadedEvent event) { + logPostAction("Image uploaded", null); + } } } diff --git a/app/src/main/java/me/vickychijwani/spectre/util/BundleBuilder.kt b/app/src/main/java/me/vickychijwani/spectre/util/BundleBuilder.kt new file mode 100644 index 00000000..8f04b14b --- /dev/null +++ b/app/src/main/java/me/vickychijwani/spectre/util/BundleBuilder.kt @@ -0,0 +1,183 @@ +package me.vickychijwani.spectre.util + +import android.os.Bundle +import android.os.Parcelable +import android.util.SparseArray +import java.io.Serializable +import java.util.* + +/** + * Fluent API for [android.os.Bundle] + * `Bundle bundle = new BundleBuilder().put(....).put(....).get();` + */ +class BundleBuilder { + + private val bundle: Bundle = Bundle() + + fun put(key: String, value: Boolean): BundleBuilder { + bundle.putBoolean(key, value) + return this + } + + fun put(key: String, value: BooleanArray): BundleBuilder { + bundle.putBooleanArray(key, value) + return this + } + + fun put(key: String, value: Int): BundleBuilder { + bundle.putInt(key, value) + return this + } + + fun put(key: String, value: IntArray): BundleBuilder { + bundle.putIntArray(key, value) + return this + } + + fun putIntegerArrayList(key: String, value: ArrayList): BundleBuilder { + bundle.putIntegerArrayList(key, value) + return this + } + + fun put(key: String, value: Bundle): BundleBuilder { + bundle.putBundle(key, value) + return this + } + + fun put(key: String, value: Byte): BundleBuilder { + bundle.putByte(key, value) + return this + } + + fun put(key: String, value: ByteArray): BundleBuilder { + bundle.putByteArray(key, value) + return this + } + + fun put(key: String, value: String): BundleBuilder { + bundle.putString(key, value) + return this + } + + fun put(key: String, value: Array): BundleBuilder { + bundle.putStringArray(key, value) + return this + } + + fun putStringArrayList(key: String, value: ArrayList): BundleBuilder { + bundle.putStringArrayList(key, value) + return this + } + + fun put(key: String, value: Long): BundleBuilder { + bundle.putLong(key, value) + return this + } + + fun put(key: String, value: LongArray): BundleBuilder { + bundle.putLongArray(key, value) + return this + } + + fun put(key: String, value: Float): BundleBuilder { + bundle.putFloat(key, value) + return this + } + + fun put(key: String, value: FloatArray): BundleBuilder { + bundle.putFloatArray(key, value) + return this + } + + fun put(key: String, value: Char): BundleBuilder { + bundle.putChar(key, value) + return this + } + + fun put(key: String, value: CharArray): BundleBuilder { + bundle.putCharArray(key, value) + return this + } + + fun put(key: String, value: CharSequence): BundleBuilder { + bundle.putCharSequence(key, value) + return this + } + + fun put(key: String, value: Array): BundleBuilder { + bundle.putCharSequenceArray(key, value) + return this + } + + fun putCharSequenceArrayList(key: String, value: ArrayList): BundleBuilder { + bundle.putCharSequenceArrayList(key, value) + return this + } + + fun put(key: String, value: Double): BundleBuilder { + bundle.putDouble(key, value) + return this + } + + fun put(key: String, value: DoubleArray): BundleBuilder { + bundle.putDoubleArray(key, value) + return this + } + + fun put(key: String, value: Parcelable): BundleBuilder { + bundle.putParcelable(key, value) + return this + } + + fun put(key: String, value: Array): BundleBuilder { + bundle.putParcelableArray(key, value) + return this + } + + fun putParcelableArrayList(key: String, value: ArrayList): BundleBuilder { + bundle.putParcelableArrayList(key, value) + return this + } + + fun putSparseParcelableArray(key: String, value: SparseArray): BundleBuilder { + bundle.putSparseParcelableArray(key, value) + return this + } + + fun put(key: String, value: Short): BundleBuilder { + bundle.putShort(key, value) + return this + } + + fun put(key: String, value: ShortArray): BundleBuilder { + bundle.putShortArray(key, value) + return this + } + + fun put(key: String, value: Serializable): BundleBuilder { + bundle.putSerializable(key, value) + return this + } + + fun putAll(map: Bundle): BundleBuilder { + bundle.putAll(map) + return this + } + + /** + * Get the underlying bundle. + */ + fun build(): Bundle { + return bundle + } + + companion object { + /** + * Initialize a BundleBuilder that is copied form the given bundle. The bundle that is passed will not be modified. + */ + fun copyFrom(bundle: Bundle): BundleBuilder { + return BundleBuilder().putAll(bundle) + } + } + +} diff --git a/build.gradle b/build.gradle index c11f34f7..600c5371 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:3.1.3' + classpath 'com.google.gms:google-services:4.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" // NOTE: Do not place your application dependencies here; they belong @@ -18,6 +19,7 @@ buildscript { allprojects { repositories { jcenter() + google() } } From 085c190f7cd158c7c5965c00d9428b0a3c860487 Mon Sep 17 00:00:00 2001 From: Vicky Chijwani Date: Fri, 22 Feb 2019 01:01:19 +0530 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=94=A7=20Integrate=20Firebase=20Perfo?= =?UTF-8?q?rmance=20Monitoring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 2 ++ build.gradle | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index b1eeb9a5..84aee863 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,6 +15,7 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: 'realm-android' apply plugin: 'io.fabric' +apply plugin: 'com.google.firebase.firebase-perf' android { compileSdkVersion 26 @@ -152,6 +153,7 @@ dependencies { transitive = true } implementation 'com.google.firebase:firebase-core:16.0.7' + implementation 'com.google.firebase:firebase-perf:16.2.3' implementation "com.github.hotchemi:permissionsdispatcher:$rootProject.ext.permissionsDispatcherVersion" kapt "com.github.hotchemi:permissionsdispatcher-processor:$rootProject.ext.permissionsDispatcherVersion" implementation "com.github.slugify:slugify:2.1.9" diff --git a/build.gradle b/build.gradle index 600c5371..41dc0577 100644 --- a/build.gradle +++ b/build.gradle @@ -8,8 +8,9 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:3.1.3' - classpath 'com.google.gms:google-services:4.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" + classpath 'com.google.gms:google-services:4.2.0' + classpath 'com.google.firebase:firebase-plugins:1.1.5' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From ec9d5eb1943ccec46011c670a72f4292bbef93f2 Mon Sep 17 00:00:00 2001 From: Vicky Chijwani Date: Fri, 22 Feb 2019 01:28:43 +0530 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=94=A7=20Update=20CircleCI=20config?= =?UTF-8?q?=20to=20deal=20with=20google-services.json=20for=20Firebase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .circleci/config.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index e7cf1827..4a55fabd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,6 +19,10 @@ jobs: - restore_cache: key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} + - run: + name: Setup Firebase configuration (google-services.json) + command: echo $GOOGLE_SERVICES_JSON_BASE64 | base64 -di > app/google-services.json + - run: name: Download Dependencies command: ./gradlew androidDependencies