Reputation: 474
I'm encountering a crash in Firebase Crashlytics on certain devices related to androidx.datastore.core.CorruptionException: Unable to parse preferences proto. However, I am not directly using Proto DataStore in my application. The crash appears in Android 12,13 and 14, 100% on background devices. Below is the stack trace:
Fatal Exception: androidx.datastore.core.CorruptionException: Unable to parse preferences proto.
at androidx.datastore.preferences.PreferencesMapCompat$Companion.readFrom(PreferencesMapCompat.kt:20)
at androidx.datastore.preferences.core.PreferencesSerializer.readFrom(PreferencesSerializer.jvm.kt:7)
at androidx.datastore.core.okio.OkioReadScope.readData$suspendImpl(OkioStorage.kt:86)
at androidx.datastore.core.okio.OkioReadScope.readData(OkioStorage.kt)
...
Caused by androidx.datastore.preferences.protobuf.InvalidProtocolBufferException: Protocol message contained an invalid tag (zero).
at androidx.datastore.preferences.protobuf.CodedInputStream$StreamDecoder.readTag(CodedInputStream.java:25)
at androidx.datastore.preferences.protobuf.CodedInputStreamReader.getFieldNumber(CodedInputStreamReader.java:12)
at androidx.datastore.preferences.protobuf.MessageSchema.mergeFromHelper(MessageSchema.java:15)
at androidx.datastore.preferences.protobuf.MessageSchema.mergeFrom(MessageSchema.java:1)
at androidx.datastore.preferences.protobuf.GeneratedMessageLite.parsePartialFrom(GeneratedMessageLite.java:3)
at androidx.datastore.preferences.protobuf.GeneratedMessageLite.parseFrom(GeneratedMessageLite.java:5)
at androidx.datastore.preferences.PreferencesProto$PreferenceMap.parseFrom(PreferencesProto.java:5)
at androidx.datastore.preferences.PreferencesMapCompat$Companion.readFrom(PreferencesMapCompat.kt:5)
at androidx.datastore.preferences.core.PreferencesSerializer.readFrom(PreferencesSerializer.jvm.kt:7)
at androidx.datastore.core.okio.OkioReadScope.readData$suspendImpl(OkioStorage.kt:86)
at androidx.datastore.core.okio.OkioReadScope.readData(OkioStorage.kt)
at androidx.datastore.core.StorageConnectionKt$readData$2.invokeSuspend(StorageConnection.kt:32)
at androidx.datastore.core.StorageConnectionKt$readData$2.invoke(StorageConnection.kt:1)
at androidx.datastore.core.StorageConnectionKt$readData$2.invoke(StorageConnection.kt:2)
at androidx.datastore.core.okio.OkioStorageConnection.readScope(OkioStorage.kt:97)
at androidx.datastore.core.StorageConnectionKt.readData(StorageConnection.kt:6)
at androidx.datastore.core.DataStoreImpl.readDataFromFileOrDefault(DataStoreImpl.kt:4)
at androidx.datastore.core.DataStoreImpl.readDataOrHandleCorruption(DataStoreImpl.kt:159)
at androidx.datastore.core.DataStoreImpl.access$readDataOrHandleCorruption(DataStoreImpl.kt)
at androidx.datastore.core.DataStoreImpl$InitDataStore$doRun$initData$1.invokeSuspend(DataStoreImpl.kt:129)
at androidx.datastore.core.DataStoreImpl$InitDataStore$doRun$initData$1.invoke(DataStoreImpl.kt:2)
at androidx.datastore.core.DataStoreImpl$InitDataStore$doRun$initData$1.invoke(DataStoreImpl.kt:1)
at androidx.datastore.core.SingleProcessCoordinator.lock(SingleProcessCoordinator.kt:97)
at androidx.datastore.core.DataStoreImpl$InitDataStore.doRun(DataStoreImpl.kt:91)
at androidx.datastore.core.RunOnce.runIfNeeded(DataStoreImpl.kt:130)
at androidx.datastore.core.DataStoreImpl.readAndInitOrPropagateAndThrowFailure(DataStoreImpl.kt:101)
at androidx.datastore.core.DataStoreImpl.handleUpdate(DataStoreImpl.kt:156)
at androidx.datastore.core.DataStoreImpl.access$handleUpdate(DataStoreImpl.kt)
at androidx.datastore.core.DataStoreImpl$writeActor$3.invokeSuspend(DataStoreImpl.kt:34)
at androidx.datastore.core.DataStoreImpl$writeActor$3.invoke(DataStoreImpl.kt:1)
at androidx.datastore.core.DataStoreImpl$writeActor$3.invoke(DataStoreImpl.kt:2)
at androidx.datastore.core.SimpleActor$offer$2.invokeSuspend(SimpleActor.kt:86)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:11)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:98)
at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:3)
at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:2)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:14)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:28)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt)
I’ve reviewed my code and can't locate where Proto DataStore is being used or Shared prefenercens migration, so I’m not sure what could be causing this crash.
Build.gradle:
plugins {
id 'com.android.application'
id 'com.google.gms.google-services'
id 'com.google.firebase.crashlytics'
id 'kotlin-android'
id 'com.google.devtools.ksp'
id 'com.google.firebase.firebase-perf'
id 'kotlin-parcelize'
id 'org.jetbrains.kotlin.android'
id("org.jetbrains.kotlin.plugin.compose") version "$kotlin_version"
}
buildFeatures {
viewBinding true
compose true
buildConfig = true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
coreLibraryDesugaringEnabled true
}
kotlinOptions {
jvmTarget = "17"
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.browser:browser:1.8.0'
implementation 'com.google.firebase:firebase-perf-ktx:21.0.1'
implementation 'com.google.firebase:firebase-config-ktx:22.0.0'
implementation project(path: ':library_lvl')
implementation 'androidx.core:core-ktx:1.13.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
// Media 3 instead of Exoplayer
implementation 'androidx.media3:media3-exoplayer:1.4.1'
implementation "androidx.media3:media3-ui:1.4.1"
// Import the BoM for the Firebase platform
implementation 'com.google.firebase:firebase-crashlytics-ktx:19.1.0'
implementation 'com.google.firebase:firebase-analytics-ktx:22.1.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.6'
implementation 'com.anggrayudi:storage:2.0.0'
//implementation 'com.github.skydoves:powerspinner:1.2.4' // custom spinner
freeImplementation 'com.google.android.gms:play-services-ads:23.3.0' // it was 20.3.0
implementation 'com.github.judemanutd:autostarter:1.1.0' // auto start for some devices
// for app open ads
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.6'
implementation 'androidx.lifecycle:lifecycle-process:2.8.6'
// room
def room_version = "2.6.1"
implementation "androidx.room:room-runtime:$room_version"
ksp "androidx.room:room-compiler:$room_version"
// optional - Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:$room_version"
/** compose **/
// Integration with activities
implementation 'androidx.activity:activity-compose:1.9.2'
implementation 'androidx.activity:activity-ktx:1.9.2'
// Compose Material Design
implementation 'androidx.compose.material:material:1.7.2'
// Animations
implementation 'androidx.compose.animation:animation:1.7.2'
// Tooling support (Previews, etc.)
implementation 'androidx.compose.ui:ui-tooling:1.7.2'
// Integration with ViewModels
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6'
// When using a AppCompat theme
implementation 'androidx.compose.runtime:runtime-livedata:1.7.2'
/*
View model
*/
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.6'
/** Jetpack data store */
implementation "androidx.datastore:datastore-preferences:1.1.1"
// compose navigation ..
implementation "androidx.navigation:navigation-compose:2.8.1"
implementation 'androidx.compose.material3:material3:1.3.0'
// compose permission ..
implementation 'com.google.accompanist:accompanist-permissions:0.36.0'
// optional - helpers for implementing LifecycleOwner in a Service
implementation 'androidx.lifecycle:lifecycle-service:2.8.6'
// Saved state module for ViewModel
implementation('androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.6')
// In app update ..
implementation 'com.google.android.play:app-update:2.1.0'
// For Kotlin users also add the Kotlin extensions library for Play In-App Update:
implementation 'com.google.android.play:app-update-ktx:2.1.0'
// to show drawable NOT FROM Resources in compfose
implementation 'com.google.accompanist:accompanist-drawablepainter:0.36.0'
// permission flow
implementation 'dev.shreyaspatil.permission-flow:permission-flow-compose:2.0.0'
// coil
implementation('io.coil-kt:coil-compose:2.7.0')
implementation('io.coil-kt:coil-video:2.7.0') // for video thumbnail
// for collect with life cycle
implementation 'androidx.lifecycle:lifecycle-runtime-compose:2.8.6'
// implementation 'com.github.tejpratap46:PDFCreatorAndroid:3.0.2' // pdf generation
implementation 'androidx.core:core-splashscreen:1.0.1' // splash screen
// color picker dialog
implementation 'com.godaddy.android.colorpicker:compose-color-picker-android:0.7.0'
implementation 'androidx.paging:paging-compose:3.3.2' // paging library
implementation("com.google.android.play:review-ktx:2.0.1") // in app review
implementation 'androidx.compose.ui:ui-util:1.7.2' // for fast collections functions
implementation 'androidx.compose.ui:ui:1.7.2'
// https://mvnrepository.com/artifact/net.lingala.zip4j/zip4j
implementation 'net.lingala.zip4j:zip4j:2.11.5'
freeImplementation project(path: ':subscription')
implementation 'com.partners.analytics.core.coverage:sdk-init:3.5.10'
implementation 'com.github.SmartToolFactory:Compose-Extended-Gestures:3.1' // motion modifiers
// koin dependency injection
def koinVersion = "4.0.0"
implementation("io.insert-koin:koin-android:$koinVersion")
implementation("io.insert-koin:koin-androidx-compose:$koinVersion")
implementation("io.insert-koin:koin-androidx-workmanager:$koinVersion")
implementation ("io.insert-koin:koin-core-coroutines:$koinVersion")
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.2' // use date time before 26
implementation 'androidx.biometric:biometric:1.2.0-alpha05' // biometric lock
implementation('com.airbnb.android:lottie-compose:6.5.2') //lottie
}
Proguard rules:
-keep class androidx.datastore.** { *; }
-keep class kotlinx.coroutines.** { *; }
-keep class kotlin.** { *; }
Steps I've Taken: Review of the code: I don't directly reference Proto DataStore in my code, neither Firebase migration. Affected libraries: The crash references the PreferencesMapCompat class, which seems related to Preferences DataStore.
How can I resolve this issue, given that I am not using Proto DataStore directly? Is there any chance this could be caused by a third-party library or something specific to the affected devices? Should I add corruption handler to all my app data stores, specially that every feature has its own?
Upvotes: 3
Views: 586
Reputation: 1082
It appears you're using DataStore Preferences (androidx.datastore:datastore-preferences:1.1.1
) based on your stack trace and build.gradle
While you're not directly using Proto DataStore, the Preferences DataStore actually uses Protocol Buffers under the hood for serialization, which explains the protobuf-related error.
Use the solution below to handle the corruption issue properly:
import androidx.datastore.core.CorruptionException
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import timber.log.Timber
import java.io.IOException
class DataStoreManager(
private val dataStore: DataStore<Preferences>
) {
/**
* Wraps DataStore operations with corruption handling
*/
fun <T> safeRead(
getData: (preferences: Preferences) -> T,
defaultValue: T
): Flow<T> {
return dataStore.data
.catch { exception ->
when (exception) {
is IOException -> {
Timber.e(exception, "Error reading preferences")
emit(dataStore.updateData { preferences ->
// Return existing preferences
preferences
})
}
is CorruptionException -> {
Timber.e(exception, "Corruption in preferences, recreating store")
// If corruption is detected, clear all data
emit(dataStore.updateData {
Preferences.empty()
})
}
else -> {
throw exception
}
}
}
.catch { exception ->
// Fallback in case the updateData above also fails
Timber.e(exception, "Fallback: returning default value")
emit(dataStore.updateData { Preferences.empty() })
}
.catch {
// Final fallback - return default value
emit(Preferences.empty())
}
.map { preferences ->
try {
getData(preferences)
} catch (e: Exception) {
Timber.e(e, "Error mapping preferences")
defaultValue
}
}
}
/**
* Wraps DataStore write operations with error handling
*/
suspend fun safeEdit(update: suspend (MutablePreferences) -> Unit) {
try {
dataStore.edit { preferences ->
update(preferences)
}
} catch (e: Exception) {
Timber.e(e, "Error writing to preferences")
// Attempt to recreate store if corrupt
if (e is CorruptionException) {
dataStore.edit {
// Clear and retry the update
preferences -> preferences.clear()
update(preferences)
}
}
}
}
}
You can implement the solution by following these steps:
Wrap your existing DataStore instances with this manager:
val dataStore: DataStore<Preferences> = context.createDataStore(name = "settings")
val dataStoreManager = DataStoreManager(dataStore)
Then replace your direct DataStore reads with the safe version:
// Before
dataStore.data.map { it[myKey] ?: defaultValue }
// After
dataStoreManager.safeRead(
getData = { it[myKey] },
defaultValue = defaultValue
)
And wrap writes with the safe version:
// Before
dataStore.edit { it[myKey] = value }
// After
dataStoreManager.safeEdit { it[myKey] = value }
The issue is likely occurring because of data corruption in the preferences file, which can happen due to process termination during write operations, device storage issues, system upgrades, or low memory conditions
Third-party libraries using DataStore could also contribute to this issue. The crash happening in the background suggests it might be related to background operations or migrations.
It's recommended to add corruption handling to all DataStore instances in your app. You can create a single DataStoreManager instance per DataStore and reuse the safe reading/writing methods.
Hope this helps!
Upvotes: 2