Search code examples
gradleandroid-jetpack-composeproguardandroid-r8

Jetpack Compose - rememberSaveable stops working with ProGuard/R8 on


I've been struggling with trying to get ProGuard/R8 minification to not break my builds for several days now, and I can't seem to find any info online about this latest problem: in my debug builds, rememberSaveable is able to remember some crucial state between creations/destructions/recompositions of a composable. The release build with -dontshrink in the proguard settings also works fine. But when I build a release build with minification completely enabled, suddenly the composable forgets its state completely between recreations. Is there a rule that I can use to tell ProGuard to stop doing whatever minification or tree shaking is causing this? Here's what my code looks like:

fun ServerChannelNav(
    ...
    startingServerId: String = "",
    ...
    lastServerChannels: Map<String, String>,
    ...
) {
    var currentServerId by rememberSaveable { mutableStateOf(startingServerId) }
    var currentChannelId by rememberSaveable {
        mutableStateOf(
            lastServerChannels[startingServerId] ?: ""
        )
    }
    ...
}

And here are my proguard rules so far:

# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}

-dontobfuscate

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile


-dontwarn kotlin.reflect.jvm.internal.**
-keep class kotlin.reflect.jvm.internal.** { *; }
-keep interface javax.annotation.Nullable

-keepattributes SourceFile,LineNumberTable,*Annotation*,EnclosingMethod,Signature,Exceptions,InnerClasses
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn javax.annotation.**
-keepclasseswithmembers class * {
    @retrofit2.http.* <methods>;
}
-keep,allowobfuscation,allowshrinking class retrofit2.Response

-keep class kotlin.Metadata { *; }
-keep class kotlin.reflect.** { *; }

-keep,allowshrinking class io.github.alexispurslane.bloc.data.SinglePolyUnwrappedDeserializer { *; }
-keep,allowshrinking class io.github.alexispurslane.bloc.data.SinglePolyUnwrappedDeserializer$** { *; }
-keepclassmembers class io.github.alexispurslane.bloc.data.** {
  @com.fasterxml.jackson.annotation.JsonCreator *;
  @com.fasterxml.jackson.annotation.JsonProperty *;
  ** serialize*(...);
  ** deserialize*(...);
}

-keep class io.github.alexispurslane.bloc.data.network.models.** {
    @com.fasterxml.jackson.annotation.** *;
    *;
}
-keep class io.github.alexispurslane.bloc.data.network.models.RevoltWebSocketRequest$** {
    @com.fasterxml.jackson.annotation.** *;
    *;
}
-keep class io.github.alexispurslane.bloc.data.network.models.RevoltWebSocketResponse$** {
    @com.fasterxml.jackson.annotation.** *;
    *;
}
-keep class com.fasterxml.jackson.** {
  *;
}

-dontwarn com.fasterxml.jackson.databind.**

-keep class * implements java.io.Serializable
-keep interface com.fasterxml.jackson.** { *; }
-keep class com.fasterxml.** { *; }
-dontwarn com.fasterxml.jackson.databind.**
-keep @com.fasterxml.jackson.annotation.JsonIgnoreProperties class * { *; }
-keep class com.fasterxml.jackson.annotation.** {
  *;
}
-keep class androidx.datastore.*.** {*;}

I could just keep tree shaking and minification off, but it makes my app double the size (80MB) and that seems a bit hefty for how small my app actually is.


Solution

  • As it turns out, what was happening wasn't that rememberSaveable wasn't working, but that the thing backing it between creations/destructions — a pref datastore — wasn't working, because rememberSaveable isn't supposed to last between creations/destructions. And the reason the preferences datastore wasn't working?

    Once again, ProGuard was breaking Jackson serialization/deserialization! In this case, I was using a Kotlin anonymous subclass object of TypeReference (e.g. object : TypeReference<T> {}) to pass some type information into a Jackson serialization operation, and since it's hard to write rules for stuff like that (unlike toplevel declarations) all the type info was getting stripped and the serialization was crashing behind the scenes. So I just had to add an @Keep annotation before the literal and it worked.