Reputation: 1609
Writing a program in Kotlin and using a Fragment to show a list of users where populating RecyclerView from Firebase but sometimes getting NullPointerException, posting Log and Code
Log:
java.lang.NullPointerException
at com.fb.hc.fragments.UsersFragment$retrieveAllUser$1.onDataChange(UsersFragment.kt:108)
at com.google.firebase.database.core.ValueEventRegistration.fireEvent(com.google.firebase:firebase-database@@19.3.0:75)
at com.google.firebase.database.core.view.DataEvent.fire(com.google.firebase:firebase-database@@19.3.0:63)
at com.google.firebase.database.core.view.EventRaiser$1.run(com.google.firebase:firebase-database@@19.3.0:55)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:237)
at android.app.ActivityThread.main(ActivityThread.java:7807)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1047)
Code:
class UsersFragment : Fragment() {
private var userAdapter: UsersAdapter? = null
private var mUsers: List<Users>? = null
....
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view: View = inflater.inflate(R.layout.fragment_users, container, false)
mUsers = ArrayList()
retrieveAllUser()
return view
}
private fun retrieveAllUser() {
val firebaseUserID = FirebaseAuth.getInstance().currentUser!!.uid
val refUser = FirebaseDatabase.getInstance().reference.child("Users")
refUser.addValueEventListener(object : ValueEventListener{
override fun onDataChange(p0: DataSnapshot)
{
(mUsers as ArrayList).clear()
if (topicCompleteView.text.toString() == "") {
for (snapshot in p0.children)
{
val user: Users? = snapshot.getValue(Users::class.java)
if (!(user?.getUID()).equals(firebaseUserID) && user != null) {
(mUsers as ArrayList<Users>).add(user)
}
}
if (mUsers.isNullOrEmpty()) {
} else {
// error line
userAdapter = UsersAdapter(context!!, mUsers as ArrayList<Users>, false)
recyclerView.adapter = userAdapter
}
}
}
Upvotes: 4
Views: 1062
Reputation: 1007658
You are doing asynchronous I/O directly in a fragment. Then you are trying to update the UI of the fragment when that work completes.
The problem is that sometimes your fragment will be destroyed before that work completes (e.g., the user pressed the BACK button). In that case, context
will be null
and context!!
will be... bad.
The tactical fix is to only update the UI if you have a context:
activity?.let {
userAdapter = UsersAdapter(it, mUsers as ArrayList<Users>, false)
recyclerView.adapter = userAdapter
}
The better fix, by a fair margin, is to get this I/O out of the fragment entirely. If the user triggers a configuration change (rotates the screen, toggles dark mode, etc.), your new fragment will not be getting the I/O results from the original fragment. It would be better to move this I/O into a ViewModel
that your fragment uses, where you make those results available via a LiveData
or similar mechanism. Your fragment would observe the LiveData
and apply the results. This not only fixes the crash, but it also handles configuration changes better.
Upvotes: 5