Fixing Android DataStore migration from a data object to a data class

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!

Leave a comment