简体   繁体   中英

Saving and restoring Kotlin lambda with savedInstanceState

How to save Kotlin lambda in fragment before the screen rotation? It works in Activity but doesn't correct work in the fragment. An exception occurs when executing a lambda expression if it contains calls to methods of the child class of PermissionsFragment, Why?

Class which stores lambda in itself:

class ActionKeeper(var action: ((isGranted: Boolean) -> Unit)? = null) : Serializable

Fragment which saves lambda to ActionKeeper on screen rotation:

abstract class PermissionsFragment : Fragment() {

    private var action: ((isGranted: Boolean) -> Unit)? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (savedInstanceState != null) {
            restoreState(savedInstanceState)
        }
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putSerializable("actionKeeper", ActionKeeper(action))
    }

    private fun restoreState(state: Bundle) {
        val keeper = state.getSerializable("actionKeeper") as ActionKeeper
        action = keeper.action
    }

    fun usePermission(permission: String, action: (isGranted: Boolean) -> Unit) {
        if (!isPermissionGranted(permission)) {
            this.action = action
            requestPermissions(arrayOf(permission), 1)
        } else {
            action(true)
        }
    }

   /* ........ */
}

Class which extends from PermissionsFragment:

class SamplePermissionsFragment : PermissionsFragment() {
    private var toast: Toast? = null

    private fun doWithPermission() {
        usePermission(Manifest.permission.SEND_SMS) { isGranted ->
            if (isGranted) {
                showToast("Fragment permission granted")
            } else {
                showToast("Fragment permission refused")
            }
        }
    }

    private fun showToast(text: String) {
        toast?.cancel()
        toast = Toast.makeText(context!!, text, Toast.LENGTH_SHORT).apply { show() }
    }
}

Logcat exteption:

2018-09-27 15:57:17.068 5569-5569/com.alexchurkin.permissionsample E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.alexchurkin.permissionsample, PID: 5569
    java.lang.RuntimeException: Failure delivering result ResultInfo{who=@android:requestPermissions:, request=65537, result=-1, data=Intent { act=android.content.pm.action.REQUEST_PERMISSIONS (has extras) }} to activity {com.alexchurkin.permissionsample/com.alexchurkin.permissionsample.fragment.FragmentHostActivity}: kotlin.KotlinNullPointerException
        at android.app.ActivityThread.deliverResults(ActivityThread.java:4196)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:4239)
        at android.app.ActivityThread.-wrap20(ActivityThread.java)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1599)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:165)
        at android.app.ActivityThread.main(ActivityThread.java:6365)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:883)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:773)
     Caused by: kotlin.KotlinNullPointerException
        at com.alexchurkin.permissionsample.fragment.SamplePermissionsFragment.showToast(SamplePermissionsFragment.kt:56)
        at com.alexchurkin.permissionsample.fragment.SamplePermissionsFragment.access$showToast(SamplePermissionsFragment.kt:13)
        at com.alexchurkin.permissionsample.fragment.SamplePermissionsFragment$doWithPermission$1.invoke(SamplePermissionsFragment.kt:35)
        at com.alexchurkin.permissionsample.fragment.SamplePermissionsFragment$doWithPermission$1.invoke(SamplePermissionsFragment.kt:13)
        at com.alexchurkin.fastpermissions.fragments.PermissionsFragment.onRequestPermissionsResult(PermissionsFragment.kt:38)
        at android.support.v4.app.FragmentActivity.onRequestPermissionsResult(FragmentActivity.java:860)
        at android.app.Activity.dispatchRequestPermissionsResult(Activity.java:7268)
        at android.app.Activity.dispatchActivityResult(Activity.java:7120)
        at android.app.ActivityThread.deliverResults(ActivityThread.java:4192)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:4239) 
        at android.app.ActivityThread.-wrap20(ActivityThread.java) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1599) 
        at android.os.Handler.dispatchMessage(Handler.java:102) 
        at android.os.Looper.loop(Looper.java:165) 
        at android.app.ActivityThread.main(ActivityThread.java:6365) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:883) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:773) 

You might have issue with context

Try below code and let me know :

private fun showToast(text: String) {
    toast?.cancel()
    activity?.let {
        toast = Toast.makeText(it, text, Toast.LENGTH_SHORT).apply { show() }
    }
}

Edit:

Save object of your ActionKeeper as:

outState.putSerializable("actionKeeper", object: ActionKeeper(action))

The problem in your code is that there are references inside your lambda that will be deallocated (thus, inaccessible) in the future. When saving the state, Android will save the pointers (references) inside that lambda, so it can't point towards invalid memory.

This is better explained here: How to save and restore lambdas in Android?

To fix it, in your case, you could have a reference of the callee in one of the lambda parameters, ie

private var action: ((Fragment, Boolean) -> Unit)? = null

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM