-
Notifications
You must be signed in to change notification settings - Fork 0
feat: lifecycle storage + bundle metadata foundation [1/4] #30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
metarouter-sdk/src/main/java/com/metarouter/analytics/lifecycle/LifecycleEventNames.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package com.metarouter.analytics.lifecycle | ||
|
|
||
| /** | ||
| * Centralized event-name constants for the four application-lifecycle events. | ||
| * Single source of truth shared by the tracker (emission) and any future | ||
| * coordinator / observer wiring. | ||
| */ | ||
| internal object LifecycleEventNames { | ||
| const val INSTALLED = "Application Installed" | ||
| const val UPDATED = "Application Updated" | ||
| const val OPENED = "Application Opened" | ||
| const val BACKGROUNDED = "Application Backgrounded" | ||
| } | ||
|
|
||
| /** | ||
| * Centralized property-name constants for lifecycle event payloads. | ||
| */ | ||
| internal object LifecycleEventProperties { | ||
| const val VERSION = "version" | ||
| const val BUILD = "build" | ||
| const val PREVIOUS_VERSION = "previous_version" | ||
| const val PREVIOUS_BUILD = "previous_build" | ||
| const val FROM_BACKGROUND = "from_background" | ||
| const val URL = "url" | ||
| const val REFERRING_APPLICATION = "referring_application" | ||
| } | ||
|
|
||
| internal const val LIFECYCLE_UNKNOWN = "unknown" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
metarouter-sdk/src/main/java/com/metarouter/analytics/storage/LifecycleStorage.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| package com.metarouter.analytics.storage | ||
|
|
||
| import android.content.Context | ||
|
|
||
| /** | ||
| * Persistent storage for the last-seen app `version` and `build`. | ||
| * | ||
| * Lives in a dedicated `SharedPreferences` file so [IdentityStorage.clear] | ||
| * (and `MetaRouterAnalyticsClient.reset()`) cannot wipe install/update state. | ||
| */ | ||
| class LifecycleStorage(context: Context) { | ||
|
|
||
| private val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) | ||
|
|
||
| fun getVersion(): String? = prefs.getString(KEY_VERSION, null) | ||
|
|
||
| fun getBuild(): String? = prefs.getString(KEY_BUILD, null) | ||
|
|
||
| fun setVersionBuild(version: String, build: String) { | ||
| prefs.edit() | ||
| .putString(KEY_VERSION, version) | ||
| .putString(KEY_BUILD, build) | ||
| .apply() | ||
| } | ||
|
|
||
| fun clear() { | ||
| prefs.edit().clear().apply() | ||
| } | ||
|
|
||
| companion object { | ||
| private const val PREFS_NAME = "com.metarouter.analytics.lifecycle" | ||
|
|
||
| const val KEY_VERSION = "metarouter:lifecycle:version" | ||
| const val KEY_BUILD = "metarouter:lifecycle:build" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 113 additions & 0 deletions
113
metarouter-sdk/src/test/java/com/metarouter/analytics/storage/LifecycleStorageTest.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| package com.metarouter.analytics.storage | ||
|
|
||
| import android.content.Context | ||
| import androidx.test.core.app.ApplicationProvider | ||
| import org.junit.After | ||
| import org.junit.Assert.assertEquals | ||
| import org.junit.Assert.assertNotNull | ||
| import org.junit.Assert.assertNull | ||
| import org.junit.Before | ||
| import org.junit.Test | ||
| import org.junit.runner.RunWith | ||
| import org.robolectric.RobolectricTestRunner | ||
|
|
||
| @RunWith(RobolectricTestRunner::class) | ||
| class LifecycleStorageTest { | ||
|
|
||
| private lateinit var context: Context | ||
| private lateinit var storage: LifecycleStorage | ||
| private lateinit var identityStorage: IdentityStorage | ||
|
|
||
| @Before | ||
| fun setup() { | ||
| context = ApplicationProvider.getApplicationContext() | ||
| storage = LifecycleStorage(context) | ||
| identityStorage = IdentityStorage(context) | ||
| storage.clear() | ||
| identityStorage.clear() | ||
| } | ||
|
|
||
| @After | ||
| fun tearDown() { | ||
| storage.clear() | ||
| identityStorage.clear() | ||
| } | ||
|
|
||
| @Test | ||
| fun `get returns null when not set`() { | ||
| assertNull(storage.getVersion()) | ||
| assertNull(storage.getBuild()) | ||
| } | ||
|
|
||
| @Test | ||
| fun `setVersionBuild persists both fields`() { | ||
| storage.setVersionBuild("1.4.0", "42") | ||
|
|
||
| assertEquals("1.4.0", storage.getVersion()) | ||
| assertEquals("42", storage.getBuild()) | ||
| } | ||
|
|
||
| @Test | ||
| fun `setVersionBuild overwrites previous values`() { | ||
| storage.setVersionBuild("1.0.0", "1") | ||
| storage.setVersionBuild("2.0.0", "2") | ||
|
|
||
| assertEquals("2.0.0", storage.getVersion()) | ||
| assertEquals("2", storage.getBuild()) | ||
| } | ||
|
|
||
| @Test | ||
| fun `data persists across instances`() { | ||
| storage.setVersionBuild("1.4.0", "42") | ||
|
|
||
| val storage2 = LifecycleStorage(context) | ||
| assertEquals("1.4.0", storage2.getVersion()) | ||
| assertEquals("42", storage2.getBuild()) | ||
| } | ||
|
|
||
| @Test | ||
| fun `lifecycle storage is independent of identity storage clear`() { | ||
| storage.setVersionBuild("1.4.0", "42") | ||
| identityStorage.set(IdentityStorage.KEY_ANONYMOUS_ID, "anon-id") | ||
| identityStorage.set(IdentityStorage.KEY_USER_ID, "user-id") | ||
|
|
||
| // Clearing identity storage must NOT touch lifecycle storage | ||
| identityStorage.clear() | ||
|
|
||
| assertNull(identityStorage.get(IdentityStorage.KEY_ANONYMOUS_ID)) | ||
| assertNull(identityStorage.get(IdentityStorage.KEY_USER_ID)) | ||
| assertEquals("1.4.0", storage.getVersion()) | ||
| assertEquals("42", storage.getBuild()) | ||
| } | ||
|
|
||
| @Test | ||
| fun `keys match cross-platform contract`() { | ||
| assertEquals("metarouter:lifecycle:version", LifecycleStorage.KEY_VERSION) | ||
| assertEquals("metarouter:lifecycle:build", LifecycleStorage.KEY_BUILD) | ||
| } | ||
|
|
||
| @Test | ||
| fun `clear removes both fields`() { | ||
| storage.setVersionBuild("1.4.0", "42") | ||
|
|
||
| storage.clear() | ||
|
|
||
| assertNull(storage.getVersion()) | ||
| assertNull(storage.getBuild()) | ||
| } | ||
|
|
||
| @Test | ||
| fun `identityStorage hasAnyValue reflects whether keys exist`() { | ||
| // Empty initially | ||
| assertEquals(false, identityStorage.hasAnyValue()) | ||
|
|
||
| identityStorage.set(IdentityStorage.KEY_ANONYMOUS_ID, "anon") | ||
| assertEquals(true, identityStorage.hasAnyValue()) | ||
|
|
||
| identityStorage.clear() | ||
| assertEquals(false, identityStorage.hasAnyValue()) | ||
|
|
||
| identityStorage.set(IdentityStorage.KEY_USER_ID, "user") | ||
| assertEquals(true, identityStorage.hasAnyValue()) | ||
| } | ||
| } |
57 changes: 57 additions & 0 deletions
57
metarouter-sdk/src/test/java/com/metarouter/analytics/types/AppContextTest.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| package com.metarouter.analytics.types | ||
|
|
||
| import android.content.Context | ||
| import androidx.test.core.app.ApplicationProvider | ||
| import org.junit.Assert.assertEquals | ||
| import org.junit.Assert.assertNotEquals | ||
| import org.junit.Assert.assertTrue | ||
| import org.junit.Test | ||
| import org.junit.runner.RunWith | ||
| import org.robolectric.RobolectricTestRunner | ||
|
|
||
| /** | ||
| * Smoke coverage for the `AppContext.fromContext` factory. The full `DeviceContextProvider` | ||
| * test exercises this transitively, but this slice introduces the factory standalone and | ||
| * deserves direct coverage. | ||
| */ | ||
| @RunWith(RobolectricTestRunner::class) | ||
| class AppContextTest { | ||
|
|
||
| @Test | ||
| fun `fromContext populates namespace from package`() { | ||
| val ctx: Context = ApplicationProvider.getApplicationContext() | ||
| val app = AppContext.fromContext(ctx) | ||
|
|
||
| assertEquals(ctx.packageName, app.namespace) | ||
| } | ||
|
|
||
| @Test | ||
| fun `fromContext returns non-blank fields`() { | ||
| val ctx: Context = ApplicationProvider.getApplicationContext() | ||
| val app = AppContext.fromContext(ctx) | ||
|
|
||
| assertTrue("name should not be blank", app.name.isNotBlank()) | ||
| assertTrue("version should not be blank", app.version.isNotBlank()) | ||
| assertTrue("build should not be blank", app.build.isNotBlank()) | ||
| assertTrue("namespace should not be blank", app.namespace.isNotBlank()) | ||
| } | ||
|
|
||
| @Test | ||
| fun `fromContext is stable across calls`() { | ||
| val ctx: Context = ApplicationProvider.getApplicationContext() | ||
| val first = AppContext.fromContext(ctx) | ||
| val second = AppContext.fromContext(ctx) | ||
|
|
||
| assertEquals(first, second) | ||
| } | ||
|
|
||
| @Test | ||
| fun `equality is structural`() { | ||
| val a = AppContext("App", "1.0", "100", "com.example") | ||
| val b = AppContext("App", "1.0", "100", "com.example") | ||
| val c = AppContext("App", "1.1", "100", "com.example") | ||
|
|
||
| assertEquals(a, b) | ||
| assertNotEquals(a, c) | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these are legacy overloads that are required for previous sdk versions and our need to support 23 as our min SDK version