I don’t see a way to edit my first response above, so I’ll just put this here because I think it’s pretty useful information for people struggling with persisting serialized objects.
I kept getting confounding errors even though it was obvious that my serialVersionUIDs were not being obfuscated. So I did a deep dive and got ahold of one of the serialized objects right off of the phone disk and tried to read them as best I could in UTF-8 to see if I could get any clues, and sure enough the mapping l.lc, which corresponded to the User class, was referenced explicitly by name inside of the file, consistent with grammar laid out in oracle’s docs on serialization. And that’s because I don’t actually serialize my state objects themselves per se, I serialize containers for those objects.
And that difference can create really confusing errors. If you have a serializable container class Container() and it has a member list of serializable objects, those objects will be referenced by class name inside of the serialized container. The punchline being that regardless of how much you protect the serialVersionUID, if the mapping name of the classes held inside of Container() has changed between serializing and deserializing, you’ll be stuck trying to cast an object of type old.mapping to an instance of type new.mapping.
I guess this doesn’t come up a lot because an ordinary person is probably going to persist their object and not a container for that object.
I don’t have time in the immediate term to refactor the persistence scheme. So the only way I thought right to fix it was to just put a blanket keep on all the serialized classes. Kind of a bummer because there’s a lot in there.
Anyway, I’m just putting this here in case it could help someone else because I had a really difficult time with it. If you have serializable objects inside of a serializable container, and you persist the container, the objects inside will be labeled as their mapping name. And that mapping name is likely to change.
EDIT:
Ok so I really hate that I’m hijacking this as my personal blog/therapy session but I’m just trying to put myself in the shoes of someone who googled “kotlin serialized classes proguard” and ended up here.
I was really bummed out about putting a huge -keep over all my persistent classes because even though there’s nothing sensitive in there, it still seems kind of unprofessional looking to just leave so much business logic in plaintext. And I built so much on top of Serializable that adopting another persistence strategy wasn’t the solution I wanted even if I may decide to eventually.
TLDR: I just decided to use -applymapping so that even though the persisted classes got new names, at least the mappings wouldn’t change.
Longer explanation in case you’re also thinking of trying something different:
For testing I found a nice big chunk of kind of irrelevant stuff that I could comment/uncomment in order to consistently change mappings between builds.
In order to avoid versioning issues with Java’s ObjectInputStream, I’ve always organized my persistent classes into couples of the form UpdatableClassName(), which holds the serialVersionUID and all the real substance of the class, and ClassName() which extends UpdatableClassName and only holds the serialVersionUID and the readObject() function. That way I can persist across updates by putting any new changes inside of ClassName and patch up the difference with readObject().
I thought that maybe I could only apply the keep rule to ClassName() and then keep all of the business logic hidden inside of UpdatableClassName and just let the mapping change since only ClassName is explicitly referenced. But that doesn’t work because as readObject() reads up the graph, it finds the mapping for UpdatableClassName (which has changed between builds) and throws an exception.
Then I considered intercepting the stream from readObject() and then changing the class name at runtime. But by that point, you’re already so deep in the weeds that it’s kind of a silly idea even if ObjectInputStream made it easy to do, which it doesn’t. I only found one example of someone achieving that and they had the benefit of knowing was the old name was, which I won’t.
So then I went through the docs and found out that -applymapping already exists and this use case seemed like a good candidate for it.
That’s it. Final update. Feel free to opine with why this isn’t a good use case for -applymapping or why ObjectInputStream is a bad way to persist state on android.