Kotlin Fragment not attached to a context

Viewed 871

I have my app connected to Crashlytic and I get this error:

Fatal Exception: java.lang.IllegalStateException: Fragment ProductFragment not attached to a context.
   at androidx.fragment.app.Fragment.requireContext(Fragment.java:900)
   at androidx.fragment.app.Fragment.getResources(Fragment.java:964)
   at com.example.fragments.ProductFragment$loaditems$1.onSuccess(ProductFragment.kt:60)
   at com.example.fragments.ProductFragment$loaditems$1.onSuccess(ProductFragment.kt:21)
   at com.google.android.gms.tasks.zzn.run(:4)
   at android.os.Handler.handleCallback(Handler.java:873)
   at android.os.Handler.dispatchMessage(Handler.java:99)
   at android.os.Looper.loop(Looper.java:214)
   at android.app.ActivityThread.main(ActivityThread.java:6981)
   at java.lang.reflect.Method.invoke(Method.java)
   at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1445)

I never have seen my app crash, thats the weird thing. Because of this I don't know what kind of thing triggers the error.

This is my code:

package com.example.fragments

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.adapters.MyProductAdapter
import com.example.R
import com.example.models.productmodel
import com.google.firebase.firestore.ktx.firestore
import com.google.firebase.ktx.Firebase
import org.json.JSONArray
import java.io.IOException
import java.io.InputStream

class ProductFragment : Fragment() {

   private var recyclerView: RecyclerView? = null
   private var errorscreen: LinearLayout? = null
   private var loadingscreen: LinearLayout? = null
   private var gridLayoutManager: GridLayoutManager? = null
   private var arrayList: ArrayList<productmodel> ? = null
   private var adapter: MyProductAdapter? = null
   private val tabel = "producten_nl"

   override fun onCreateView(
      inflater: LayoutInflater, container: ViewGroup?,
      savedInstanceState: Bundle?
   ): View? {
      val v = inflater.inflate(R.layout.fragment_producten, container, false)

      loadingscreen = v.findViewById(R.id.productloadingscreen)
      recyclerView = v.findViewById(R.id.json_listp)
      errorscreen = v.findViewById(R.id.errorscreen)

      gridLayoutManager = GridLayoutManager(getActivity(), 2, LinearLayoutManager.VERTICAL, false)
      recyclerView?.layoutManager = gridLayoutManager
      recyclerView?.setHasFixedSize(true)
      arrayList = ArrayList()
      arrayList = loaditems()

      return v
   }

   private fun loaditems(): ArrayList<productmodel> {
      val items: ArrayList<productmodel> = ArrayList()
      val db = Firebase.firestore

      db.collection(tabel)
         .orderBy("volgorde")
         .get()
         .addOnSuccessListener { result ->
            if (result.count() != 0) {
               for (document in result) {
                  if(document.getBoolean("show") == true) {
                     val photo = resources.getIdentifier(document.getString("image"), "drawable", requireContext().packageName)
                     val name = document.getString("name") as String
                     val image = document.getString("image") as String
                     val url = document.getString("url") as String

                     items.add(productmodel(photo, name, image, url))
                  }
               }
               adapter = MyProductAdapter(requireActivity().baseContext, arrayList!!)
               recyclerView?.adapter = adapter
               recyclerView?.visibility = View.VISIBLE
               loadingscreen?.visibility = View.GONE
            }
            else
            {
               errorscreen?.visibility = View.VISIBLE
               recyclerView?.visibility = View.GONE
               loadingscreen?.visibility = View.GONE
            }
         }
         .addOnFailureListener { exception ->
            errorscreen?.visibility = View.VISIBLE
            recyclerView?.visibility = View.GONE
            loadingscreen?.visibility = View.GONE
         }
      return items
   }
}

Line 60 is this line: if(document.getBoolean("show") == true) { and line 21 is this line: class ProductFragment : Fragment() {

I don't know what this error is and what causes this error. Does anyone know what I do wrong?

Edit: This is the way I put a fragmant inside an activity

    private fun  makeCurrentFragment(fragment: Fragment) =
    supportFragmentManager.beginTransaction().apply {
        replace(R.id.fl_wrapper, fragment)
        commit()
    }
1 Answers

The culprit is the requireContext() call in the successListener of your Firestore request.

This is what's probably happening when the crash occurs:

  1. User navigates to the ProductFragment
  2. loadItems is called and the Firestore request is sent off
  3. The User navigates backwards removing the ProductFragment before the Firestore successListener is called.
  4. The Firestore request finishes and the successListener is called, but now the Fragment is no longer attached to any context so requireContext() throws an IllegalStateException

A quick fix would be to check the context isn't null in the successListener before continuing and return if it is:

.addOnSuccessListener { result ->
   val context = context ?: return
   if (result.count() != 0) {
      for (document in result) {
         if(document.getBoolean("show") == true) {
            val photo = resources.getIdentifier(document.getString("image"), "drawable", context.packageName)
            val name = document.getString("name") as String
            val image = document.getString("image") as String
            val url = document.getString("url") as String

            items.add(productmodel(photo, name, image, url))
         }
      }
      ...

You also have a call to requireActivity() in the successListener that will similarly fail in the same situation. Rather than initialising the Adapter in the successListener you could initialise it before the request is made and just update its data in the successListener.

Related