Android
Android

Reputation: 1609

Kotlin Fragment NullPointerException

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

Answers (1)

CommonsWare
CommonsWare

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

Related Questions