Status Update
Comments
mi...@perplexity.ai <mi...@perplexity.ai> #2
ti...@google.com <ti...@google.com> #3
mi...@perplexity.ai <mi...@perplexity.ai> #4
ti...@google.com <ti...@google.com> #5
In our case, we have b2b app running on dedicated devices.
After 1.1.0 DataStore update we had 6 terminals reported 30k androidx.datastore.core.CorruptionException
fatal crashes on app start.
We use DataStore in WorkManager jobs and on regular Activity starts. Seems that after the application gets this state it will never will succeed to start successfully again. I am afraid that data is being corrupted.
Can't reproduce. But same as OP, we have very vanilla usage of DataStore Preferences. No multiple processes, just simple read/write key-values in a single process single Activity.
mi...@perplexity.ai <mi...@perplexity.ai> #6
Hi, thanks for the feedback, noticed the number of +1s, I'll prioritize an investigation.
To facilitate the process, could you please share how you create the DataStore instance?
ti...@google.com <ti...@google.com> #7
Here is how we are creating our DataStore<Preferences>
instances:
PreferenceDataStoreFactory.create(
produceFile = { context.preferencesDataStoreFile(filename) }, // filename is [a-zA-Z]
scope = scope, // CoroutineScope(Dispatchers.IO + SupervisorJob())
migrations = listOf(
SharedPreferencesMigration(
context = context,
sharedPreferencesName = sharedPrefFile, // we are using our package name here
keysToMigrate = keysToMigrate, // a Set<String>, sometimes empty if nothing to be migrated
),
),
corruptionHandler = ReplaceFileCorruptionHandler {
// logging step omitted
emptyPreferences()
},
)
And these methods are called in Hilt @Provides
methods in a @InstallIn(SingletonComponent::class) @Module
for each preferences we use in our app.
mi...@perplexity.ai <mi...@perplexity.ai> #8
@Module
@InstallIn(SingletonComponent::class)
class PreferencesDataStoreModule {
@Provides
@Singleton
fun provideDataStore(@ApplicationContext context: Context): DataStore<Preferences> =
PreferenceDataStoreFactory.create(
produceFile = {
context.preferencesDataStoreFile(PREFERENCES_FILE_NAME)
},
)
}
ti...@google.com <ti...@google.com>
mi...@perplexity.ai <mi...@perplexity.ai> #9
mi...@perplexity.ai <mi...@perplexity.ai> #10
ti...@google.com <ti...@google.com> #11
Re corruptionHandler
to handle the corrupted data file, like in
mi...@perplexity.ai <mi...@perplexity.ai> #12
Re
Question1:
Why these corruptions started appearing only after 1.1.0 version? We were using DataStore forever with such config and everything worked fine.
Question2:
the library doesn't proactively fix the data corruption.
Why is that? Our expectation was that datastore-preferences would work as drop-in replacement for shared-preferences. And users of shared-preferences never had to care about such issues...
ti...@google.com <ti...@google.com> #13
mi...@perplexity.ai <mi...@perplexity.ai> #14
ap...@google.com <ap...@google.com> #15
Hi I'm looking into it again, I think
Re
Why is that? Our expectation was that datastore-preferences would work as drop-in replacement for shared-preferences. And users of shared-preferences never had to care about such issues...
You didn't have to care such issues of shared preferences because SP just silently fails in such cases, so you won't know the failures. And that's one of the reason why we encouraged people to use Jetpack DataStore than SharedPreferences.
I understand that you would prefer to have the default corruption handler here to replace the file automatically, and it's reasonable to some extent. Unfortunately, we designed the API following a slightly different principle, where people need to "opt-in" this behavior, because their data could be sensitive and they would want to have some ad-hoc logic before the file just disappears and gets replaced automatically. Also you'll need to provide the value for the replacement anyways, right? As far as I can tell, that's not too much different from providing the corruption handler specifications, because you need to pass a param anyways.
pr...@google.com <pr...@google.com> #16
Small update. We downgraded datastore-preferences
1.1.0 -> 1.0.0 and have the apps in production for some time. All CorruptionException
issues stopped.
Re
people need to "opt-in" this behavior, because their data could be sensitive and they would want to have some ad-hoc logic before the file just disappears and gets replaced automatically.
Yes, exactly. I do not understand this. You consider that it's important that developers add logic to handle the corruption case, but at the same time it's exposed via API as an optional opt-in.
There are three possibilities, how datastore-preferences
initialization API behaves:
- Have default behavior, to silently reset prefs on corrupted files (match SharedPreferences). On file corruption event: user silently loses data, the application continues to run with default values.
- Have a mandatory
corruptHandler
parameter on preferences initialization.public fun preferencesDataStore(corruptionHandler:ReplaceFileCorruptionHandler<Preferences>,<..>)
. On file corruption event: Developers are forced to decide what should happen. - Have an optional/opt-in
corruptHandler
parameter [current default variant]. On file corruption event: Application is rendered as completely unusable. RuntimeExection is thrown. The only way end-users can use the app again, is to go and do ClearAppData in the App Settings.
The default behavior is the one that will be used by most developers (not opt-in version).
// At the top level of your kotlin file:
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
Meaning: application will be crashing until the end-user goes to Android app settings and clear data.
So for me ether one of 1.
or 2.
options sounds better then the current 3.
one
Description
Jetpack Compose component used: Modifier.sharedElement
Hey! We added a shared element transition to the app, using Compose 1.7.3. We are seeing this crash in firebase, however I could not reproduce the issue locally.
Our layout hierarchy looks like this:
NavHost -> ... -> AnimatedVisibility -> ... -> BoxWithConstraints -> First layout with sharedElement
\> ... -> AnimatedVisibility -> ... -> BoxWithConstraints -> Second layout with sharedElement
With "..." I replaced series of "normal" layouts.
The crash is due to this precondition failing:
It seems somehow one of the elements gets drawn without having had layout before.
Any idea if I am doing something wrong, or any workarounds? Thank you!!
Stack trace:
SharedElementInternalState.drawInOverlay
java.lang.IllegalArgumentException - Error: current bounds not set yet.
androidx.compose.animation.SharedElementInternalState.drawInOverlay (SharedElementInternalState.java:196)
androidx.compose.animation.SharedTransitionScopeImpl.drawInOverlay$animation_release (SharedTransitionScopeImpl.java:1086)
androidx.compose.animation.SharedTransitionScopeKt$SharedTransitionScope$1$2$1.invoke (SharedTransitionScope.kt:161)
androidx.compose.animation.SharedTransitionScopeKt$SharedTransitionScope$1$2$1.invoke (SharedTransitionScope.kt:159)
androidx.compose.ui.draw.DrawWithContentModifier.draw (DrawModifier.kt:422)
androidx.compose.ui.node.LayoutNodeDrawScope.drawDirect-eZhPAX0$ui_release (LayoutNodeDrawScope.kt:110)
androidx.compose.ui.node.LayoutNodeDrawScope.draw-eZhPAX0$ui_release (LayoutNodeDrawScope.kt:89)
androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers (NodeCoordinator.kt:450)
androidx.compose.ui.node.NodeCoordinator.draw (NodeCoordinator.kt:439)
androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw (LayoutModifierNodeCoordinator.kt:280)
androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers (NodeCoordinator.kt:447)
androidx.compose.ui.node.NodeCoordinator.draw (NodeCoordinator.kt:439)
androidx.compose.ui.node.LayoutNode.draw$ui_release (LayoutNode.kt:1000)
androidx.compose.ui.node.InnerNodeCoordinator.performDraw (InnerNodeCoordinator.kt:196)
androidx.compose.ui.node.LayoutNodeDrawScope.drawContent (LayoutNodeDrawScope.kt:68)
androidx.compose.foundation.BackgroundNode.draw (Background.kt:163)
androidx.compose.ui.node.LayoutNodeDrawScope.drawDirect-eZhPAX0$ui_release (LayoutNodeDrawScope.kt:110)
androidx.compose.ui.node.LayoutNodeDrawScope.draw-eZhPAX0$ui_release (LayoutNodeDrawScope.kt:89)
androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers (NodeCoordinator.kt:450)
androidx.compose.ui.node.NodeCoordinator.access$drawContainedDrawModifiers (NodeCoordinator.kt:58)
androidx.compose.ui.node.NodeCoordinator$drawBlock$1$1.invoke (NodeCoordinator.java:469)
androidx.compose.ui.node.NodeCoordinator$drawBlock$1$1.invoke (NodeCoordinator.java:468)
androidx.compose.runtime.snapshots.Snapshot$Companion.observe (Snapshot.java:2441)
androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe (SnapshotStateObserver.kt:502)
androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads (SnapshotStateObserver.kt:258)
androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release (OwnerSnapshotObserver.kt:133)
androidx.compose.ui.node.NodeCoordinator$drawBlock$1.invoke (NodeCoordinator.java:468)
androidx.compose.ui.node.NodeCoordinator$drawBlock$1.invoke (NodeCoordinator.java:466)
androidx.compose.ui.platform.GraphicsLayerOwnerLayer$recordLambda$1.invoke (GraphicsLayerOwnerLayer.java:291)
androidx.compose.ui.platform.GraphicsLayerOwnerLayer$recordLambda$1.invoke (GraphicsLayerOwnerLayer.java:289)
androidx.compose.ui.graphics.layer.GraphicsLayerV29.record (GraphicsLayerV29.android.kt:245)
androidx.compose.ui.graphics.layer.GraphicsLayer.recordInternal (AndroidGraphicsLayer.android.kt:430)
androidx.compose.ui.graphics.layer.GraphicsLayer.record-mL-hObY (AndroidGraphicsLayer.android.kt:423)
androidx.compose.ui.platform.GraphicsLayerOwnerLayer.updateDisplayList (GraphicsLayerOwnerLayer.android.kt:284)
androidx.compose.ui.platform.GraphicsLayerOwnerLayer.drawLayer (GraphicsLayerOwnerLayer.android.kt:229)
androidx.compose.ui.node.NodeCoordinator.draw (NodeCoordinator.kt:434)
androidx.compose.ui.node.LayoutNode.draw$ui_release (LayoutNode.kt:1000)
androidx.compose.ui.node.InnerNodeCoordinator.performDraw (InnerNodeCoordinator.kt:196)
androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers (NodeCoordinator.kt:447)
androidx.compose.ui.node.NodeCoordinator.draw (NodeCoordinator.kt:439)
androidx.compose.ui.node.LayoutNode.draw$ui_release (LayoutNode.kt:1000)
androidx.compose.ui.platform.AndroidComposeView.dispatchDraw (AndroidComposeView.android.kt:1564)
android.view.View.draw (View.java:25180)
android.view.View.updateDisplayListIfDirty (View.java:24036)
android.view.ViewGroup.recreateChildDisplayList (ViewGroup.java:4764)
...