Search code examples
javaandroidandroid-studiokotlinkotlin-extension

Android kotlin extension error


java.lang.VerifyError: .../utils/KotlinViewExtKt$animateFadeOut$1

Getting that error while running app on emulator PRE Lolipop (<21 api)

Function causing the trouble:

fun View.animateFadeOut(animDuration: Long = 250) {
    this.animate()
            .alpha(0F)
            .setDuration(animDuration)
            .setListener(object : Animator.AnimatorListener {

                override fun onAnimationRepeat(p0: Animator?) {}

                override fun onAnimationEnd(animation: Animator?, isReverse: Boolean) {
                    super.onAnimationEnd(animation, isReverse)
                    show(false)
                }

                override fun onAnimationEnd(p0: Animator?) {
                    show(false)
                }

                override fun onAnimationCancel(p0: Animator?) {
                }

                override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
                }

                override fun onAnimationStart(p0: Animator?) {
                }
            })
            .start()
}

fun View.show(show: Boolean) {
    val vis = if (show) View.VISIBLE else View.GONE
    if (visibility != vis) {
        visibility = vis
    }
}

Pointing on .setListener line.

Works perfectly on 21+ api.

AS versio: 3.0.1. Kotlin version: 1.2.21 (tried 1.1.51).

What could be the reason? My bad or kotlin? Multidex is enabled.


Solution

  • Solution

    Based on this issue and this fix:

    ViewExtension.kt

    fun View.animateFadeOut(animDuration: Long = 250L) {
        this.animate()
                .alpha(0F)
                .setDuration(animDuration)
                .setListener(object : AbstractAnimatorListener() {
    
                    override fun onAnimationEnd(animation: Animator?, isReverse: Boolean) {
                        super.onAnimationEnd(animation, isReverse)
                        show(false)
                    }
    
                    override fun onAnimationEnd(p0: Animator?) {
                        show(false)
                    }
                })
                .start()
    }
    
    fun View.show(show: Boolean) {
        val vis = if (show) View.VISIBLE else View.GONE
        if (visibility != vis) {
            visibility = vis
        }
    }
    

    AbstractAnimatorListener.kt

    abstract class AbstractAnimatorListener : Animator.AnimatorListener {
    
        override fun onAnimationRepeat(p0: Animator?) {}
    
        override fun onAnimationEnd(p0: Animator?) {}
    
        override fun onAnimationCancel(p0: Animator?) {}
    
        override fun onAnimationStart(p0: Animator?) {}
    
        override fun onAnimationEnd(animation: Animator?, isReverse: Boolean) {
            onAnimationEnd(animation)
        }
    
        override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
            onAnimationStart(animation)
        }
    }
    

    Explanation

    Try to remove these methods added in API 26 using Java 8 Defaults and use alternative animation:

    Animator.AnimatorListener:

    onAnimationEnd added in API level 26

    void onAnimationEnd (Animator animation, boolean isReverse)
    

    onAnimationStart added in API level 26

    void onAnimationStart (Animator animation, boolean isReverse)
    

    These methods can be overridden, though not required, to get the additional play direction info when an animation starts.

    AnimatorSet:

    Starting in Android 8.0 (API 26) the AnimatorSet API supports seeking and playing in reverse.


    Note:

    /**
     * Skipping calling super when overriding this method results in
     * {@link #onAnimationStart(Animator)} not getting called.
     */
    default void onAnimationStart(Animator animation, boolean isReverse) {
        onAnimationStart(animation);
    }
    

    I need to add this to my build.gradle file to test it

    tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
      kotlinOptions {
        jvmTarget = "1.8"
      }
    }
    

    or replace

    super.onAnimationEnd(animation, isReverse)
    

    by

    onAnimationEnd(animation)
    

    to avoid the error:

    Super calls to Java default methods are prohibited in JVM target 1.6. Recompile with '-jvm-target 1.8'

    And add the next line to my gradle.properties file thanks to this answer

    android.enableD8=true
    

    to avoid the exception:

    com.android.dx.cf.code.SimException: default or static interface method used without --min-sdk-version >= 24

    It compiles now, launching a kitKat emulator just now...

    It works, and MultiDex is also enabled in my project.

    Sorry, I cannot reproduce it.

    Alternatively, remove the two methods using Java 8 defaults, it also works.


    Note2:

    Arpan suggestion about AnimatorListenerAdapter would work but it's not necessary.

    You can remove those methods, change the animation and create:

    object EmptyAnimatorListener : Animator.AnimatorListener {
        override fun onAnimationRepeat(p0: Animator?) {}
        override fun onAnimationEnd(p0: Animator?) {}
        override fun onAnimationCancel(p0: Animator?) {}
        override fun onAnimationStart(p0: Animator?) {}
    }
    

    And use delegation like this:

        .setListener(object : Animator.AnimatorListener by EmptyAnimatorListener {
            override fun onAnimationStart(animation: Animator) {
                // Do something
            }
    
            override fun onAnimationEnd(animation: Animator) {
                // Do something
            }
        })
    

    Note3:

    Reproduced adding it to an extension function:

    E/AndroidRuntime: FATAL EXCEPTION: main Process: ...android, PID: 3409 java.lang.VerifyError: com/.../ui/common/extension/android/view/ViewExtensionKt$animateFadeOut$1

    • Removing the Api 26 methods using Java Defaults solves the issue.

    • Replacing the super call calling your onAnimationEnd also solves it:

          override fun onAnimationEnd(animation: Animator?, isReverse: Boolean) {
              onAnimationEnd(animation)
          }
      
    • Calling super.onAnimationStart(animation: Animator?, isReverse: Boolean) requires API 26:

    enter image description here

    • Decompiling Kotlin Bytecode shows these methods cannot be resolved:

    enter image description here