Aptabase hashes its user_id from IP + user agent with a salt that rotates daily, so events can never be linked across days and no retention or MAU figure can come out of the export. In 19 days of data not one of 82 user_ids appears on two dates, which is the artefact, not the behaviour. Instead of an identity, every event now carries this install's own counters, kept in UserDefaults/DataStore: tenure_days, launch_no, active_days and dormant_days. Only derived day counts leave the device, so the privacy position is unchanged. They make the curve countable in the export: launch_no == 1 marks exactly one launch per install, dormant_days >= 1 exactly one launch per calendar day, so D_k is the share of installs seen again with tenure_days == k. Event date minus tenure_days is the install date, which gives full cohort tables. Installs predating the counters have no install date and report tenure_days == -1 forever, so they can be excluded instead of inflating the new-install cohort. The Kotlin counter arithmetic sits in a pure advance() so it can be tested without a Context; this adds the app module's first JVM test source set.
60 lines
3.9 KiB
TOML
60 lines
3.9 KiB
TOML
[versions]
|
|
agp = "8.10.1"
|
|
kotlin = "2.1.0"
|
|
ksp = "2.1.0-1.0.29"
|
|
coreKtx = "1.15.0"
|
|
lifecycle = "2.8.7"
|
|
activityCompose = "1.9.3"
|
|
composeBom = "2024.12.01"
|
|
navigationCompose = "2.8.5"
|
|
room = "2.6.1"
|
|
datastore = "1.1.1"
|
|
retrofit = "2.11.0"
|
|
okhttp = "4.12.0"
|
|
serialization = "1.7.3"
|
|
retrofitSerialization = "1.0.0"
|
|
coil = "2.7.0"
|
|
playReview = "2.0.2"
|
|
benchmark = "1.4.1"
|
|
profileinstaller = "1.4.1"
|
|
|
|
[libraries]
|
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
|
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
|
|
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" }
|
|
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" }
|
|
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
|
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
|
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
|
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
|
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
|
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
|
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
|
androidx-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" }
|
|
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
|
|
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
|
|
androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
|
|
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
|
|
androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" }
|
|
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
|
|
retrofit-serialization = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "retrofitSerialization" }
|
|
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
|
|
okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
|
|
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" }
|
|
coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }
|
|
play-review-ktx = { group = "com.google.android.play", name = "review-ktx", version.ref = "playReview" }
|
|
androidx-profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "profileinstaller" }
|
|
androidx-benchmark-macro-junit4 = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "benchmark" }
|
|
androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version = "1.2.1" }
|
|
junit = { group = "junit", name = "junit", version = "4.13.2" }
|
|
androidx-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version = "2.3.0" }
|
|
|
|
[plugins]
|
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
|
android-test = { id = "com.android.test", version.ref = "agp" }
|
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
|
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
|
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
|
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
|
baselineprofile = { id = "androidx.baselineprofile", version.ref = "benchmark" }
|