Crash in fragment when screen rotates
alexChurkin opened this issue · 2 comments
Hello! I found an issue with using your library in fragments. Try to run the code below and rotate your screen, and you'll see an exception like this:
2018-11-24 04:05:54.640 20891-20891/com.test.myapplication E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.test.myapplication, PID: 20891
java.lang.RuntimeException: Failure delivering result ResultInfo{who=@android:requestPermissions:, request=65559, result=-1, data=Intent { act=android.content.pm.action.REQUEST_PERMISSIONS (has extras) }} to activity {com.test.myapplication/com.test.myapplication.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference
at android.app.ActivityThread.deliverResults(ActivityThread.java:4089)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:4132)
at android.app.ActivityThread.-wrap20(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1533)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference
at android.widget.Toast.<init>(Toast.java:103)
at android.widget.Toast.makeText(Toast.java:256)
at com.test.myapplication.TestFragment.showToast(TestFragment.kt:36)
at com.tes.myapplication.TestFragment$doIt$2.invoke(TestFragment.kt:29)
at com.test.myapplication.TestFragment$doIt$2.invoke(TestFragment.kt:14)
at com.github.florent37.runtimepermission.kotlin.KotlinRuntimePermission$onDeclined$1.onResponse(kotlin-runtimepermissions.kt:29)
at com.github.florent37.runtimepermission.RuntimePermission.onReceivedPermissionResult(RuntimePermission.java:123)
at com.github.florent37.runtimepermission.RuntimePermission.access$000(RuntimePermission.java:27)
at com.github.florent37.runtimepermission.RuntimePermission$1.onRequestPermissionsResult(RuntimePermission.java:47)
at com.github.florent37.runtimepermission.PermissionFragment.onRequestPermissionsResult(PermissionFragment.java:89)
at androidx.fragment.app.FragmentActivity.onRequestPermissionsResult(FragmentActivity.java:860)
at android.app.Activity.dispatchRequestPermissionsResult(Activity.java:7084)
at android.app.Activity.dispatchActivityResult(Activity.java:6936)
at android.app.ActivityThread.deliverResults(ActivityThread.java:4085)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:4132)
at android.app.ActivityThread.-wrap20(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1533)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
My activity:
package com.test.myapplication
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.container)
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.replace(R.id.container, TestFragment())
.commit()
}
}
}
My fragment:
package com.test.myapplication
import android.Manifest
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import com.github.florent37.runtimepermission.kotlin.askPermission
import kotlinx.android.synthetic.main.testfragment.*
import kotlinx.android.synthetic.main.testfragment.view.*
class TestFragment : Fragment() {
private var toast: Toast? = null
lateinit var button: Button
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val view = inflater.inflate(R.layout.testfragment, container, false)
button = view.button
button.setOnClickListener { doIt() }
return view
}
fun doIt() {
askPermission(Manifest.permission.READ_CONTACTS, Manifest.permission.ACCESS_FINE_LOCATION) {
showToast("ACCEPTED")
button.text = "ACCEPTED"
}.onDeclined {
showToast("DECLINED")
button.text = "DECLINED"
}
}
fun showToast(text: String) {
toast?.cancel()
toast = Toast.makeText(context, text, Toast.LENGTH_SHORT).apply { show() }
}
}
I tried to create my own library to make requesting permissions easier and came across the same crash :/
@alexChurkin this is the expected behavior in your case.
When you rotate the device, your MainActivity and TestFragment are recreated. You pass lambdas (accepted and declined blocks) as the askPermission method call parameters that are referenced to the Fragment they are created in. Then you rotate the device, and you callback body will be invoked in the already detached TestFragment. That's the reason why context is null. By the way, this is a memory leak.
This issue also occurs in the example Activities. The only difference is the absence of crashes)
appendText(resultView, "Denied :")
code does nothing actually (it is invoked into the leaked Activity)
You should handle this case in your own: use ViewModel, Loaders etc.