material-components / material-components-android-examples

Companion example apps and code for MDC-Android.
Apache License 2.0
2.51k stars 524 forks source link

[Owl] Screen rotation crash #63

Open ashtanko opened 3 years ago

ashtanko commented 3 years ago

Hi, When the smartphone is rotated, the app crash came with backtrace:

2020-12-19 20:59:43.866 13774-13774/com.materialstudies.owl E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.materialstudies.owl, PID: 13774
    java.lang.IllegalArgumentException: width and height must be > 0
        at android.graphics.Bitmap.createBitmap(Bitmap.java:1113)
        at android.graphics.Bitmap.createBitmap(Bitmap.java:1080)
        at android.graphics.Bitmap.createBitmap(Bitmap.java:1030)
        at android.graphics.Bitmap.createBitmap(Bitmap.java:991)
        at androidx.core.view.ViewKt.drawToBitmap(View.kt:230)
        at androidx.core.view.ViewKt.drawToBitmap$default(View.kt:226)
        at com.materialstudies.owl.util.ViewExtensionsKt.show(ViewExtensions.kt:194)
        at com.materialstudies.owl.ui.MainActivity$onCreate$1$1$1.onDestinationChanged(MainActivity.kt:46)
        at androidx.navigation.NavController.addOnDestinationChangedListener(NavController.java:233)
        at com.materialstudies.owl.ui.MainActivity$onCreate$1$1.invokeSuspend(MainActivity.kt:44)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241)
        at androidx.lifecycle.DispatchQueue.drainQueue(DispatchQueue.kt:76)
        at androidx.lifecycle.DispatchQueue.resume(DispatchQueue.kt:55)
        at androidx.lifecycle.LifecycleController$observer$1.onStateChanged(LifecycleController.kt:40)
        at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:361)
        at androidx.lifecycle.LifecycleRegistry.forwardPass(LifecycleRegistry.java:300)
        at androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:339)
        at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:145)
        at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:131)
        at androidx.lifecycle.ReportFragment.dispatch(ReportFragment.java:68)
        at androidx.lifecycle.ReportFragment$LifecycleCallbacks.onActivityPostResumed(ReportFragment.java:188)
        at android.app.Activity.dispatchActivityPostResumed(Activity.java:1278)
        at android.app.Activity.performResume(Activity.java:7970)
        at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4195)
        at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4237)
        at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:52)
        at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
2020-12-19 20:59:43.878 13774-13774/com.materialstudies.owl I/Process: Sending signal. PID: 13774 SIG: 9

Run on: Google Pixel and Android 10. App crashes only when the BottomNavigationView is visible.

eliasbellido commented 3 years ago

I just came across with this also. That's because during rotation somehow the width and height are 0. I haven't tried any solution yet, but just in case did you already?

DaireJN commented 3 years ago

Hi I Have modified the extension functions to use view tree observer to ensure the layout is ready before operating on it try, it out.

fun BottomNavigationView.show() {
    if (visibility == VISIBLE) return
    viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            viewTreeObserver.removeOnGlobalLayoutListener(this)
            val parent = parent as ViewGroup
            // View needs to be laid out to create a snapshot & know position to animate. If view isn't
            // laid out yet, need to do this manually.
            if (!isLaidOut) {
                measure(
                    MeasureSpec.makeMeasureSpec(parent.width, MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(parent.height, MeasureSpec.AT_MOST)
                )
                layout(parent.left, parent.height - measuredHeight, parent.right, parent.height)
            }

            val drawable = BitmapDrawable(context.resources, drawToBitmap())
            drawable.setBounds(left, parent.height, right, parent.height + height)
            parent.overlay.add(drawable)
            ValueAnimator.ofInt(parent.height, top).apply {
                startDelay = 100L
                duration = 300L
                interpolator = AnimationUtils.loadInterpolator(
                    context,
                    android.R.interpolator.linear_out_slow_in
                )
                addUpdateListener {
                    val newTop = it.animatedValue as Int
                    drawable.setBounds(left, newTop, right, newTop + height)
                }
                doOnEnd {
                    parent.overlay.remove(drawable)
                    visibility = VISIBLE
                }
                start()
            }
        }
    })
}
fun BottomNavigationView.hide() {
    if (visibility == GONE) return
    viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            viewTreeObserver.removeOnGlobalLayoutListener(this)
            val drawable = BitmapDrawable(context.resources, drawToBitmap())
            val parent = parent as ViewGroup
            drawable.setBounds(left, top, right, bottom)
            parent.overlay.add(drawable)
            visibility = GONE
            ValueAnimator.ofInt(top, parent.height).apply {
                startDelay = 100L
                duration = 200L
                interpolator = AnimationUtils.loadInterpolator(
                    context,
                    android.R.interpolator.fast_out_linear_in
                )
                addUpdateListener {
                    val newTop = it.animatedValue as Int
                    drawable.setBounds(left, newTop, right, newTop + height)
                }
                doOnEnd {
                    parent.overlay.remove(drawable)
                }
                start()
            }
        }
    })
}