I had to implement this migration for an object that was stored in DataStore using Protobuf and Kotlinx Serialisable on Android. The original object looked like this:
@Serializable
data class Assignment(val state: State)
@Serializable
sealed interface State {
@Serializable
data object Pending : State
// More implementations
}
The new object was supposed to look like this:
@Serializable
sealed interface State {
@Serializable
data class Pending(val id: String) : State
// More implementations
}
As you can see, the sealed interface implementation of Pending changed from a data object to a data class. But the problem is that the original object was stored in DataStore, so executing this migration directly would result in the following crash:
Caused by: java.lang.NoSuchFieldError: No field INSTANCE of type Lcom/georgimirchev/****/State$Pending; or its superclasses (declaration of /data/data/****/State$Pending) appears in /data/data/***/classes16.dex
The issue is that DataStore continued to try to serialize the object by using the INSTANCE field although the object was no longer such; it became a data class.
I tried many things, like:
- Clearing the DataStore file and purely renaming it
- Uninstall and reinstall the app
- Making the state field nullable
And none of the above worked. Only 2 things worked:
A) Renaming the Pending field
B) Creating the INSTANCE field in the companion object of the class itself
I chose approach B) as it seems the least invasive one. The thing I did was simply this:
data class Pending(val id: String?) : State {
companion object {
@JvmField
val INSTANCE = Pending(null)
}
}
By providing the INSTANCE field by using JvmField and the companion object it seems that it tricked DataStore that it could create the data object it was using before. Hopefully this works for you too!