1

I'm parsing data with a Volley Request on the onResume() method of my fragment, to populate the layout with the parsed data I need to get the view of my fragment, that's why I'm calling requireView() onResume() after a successfull parsing. For the first time now my App crashed on one line which I'm going to mark with a * saying:

java.lang.IllegalStateException: Fragment DataMain{1a53f20} (4f68cb4d-c05f-4416-932e-26e455fbf106)} did not return a View from onCreateView() or this was called before onCreateView().

This is the code:

class DataMain : Fragment(), CoroutineScope by MainScope() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.fragment_Data, container, false)
        DataBalance = view.findViewById(R.id.Data_balance)
        // Firebase User Auth
        getUID.requestUidToken()?.addOnSuccessListener { 

                    }?.addOnFailureListener {

                    val unregisteredView = inflater.inflate(R.layout.fragment_unregistred, container, false)
                    val registerNow = unregisteredView.findViewById<TextView>(R.id.textview_registernow)
                    registerNow.setOnClickListener {
                        val mIntent = Intent(activity, LoginMain::class.java)
                        requireActivity().startActivity(mIntent)
                    }
                }
            view
        } else {
            val unregisteredView = inflater.inflate(R.layout.fragment_unregistred, container, false)
            val registerNow = unregisteredView.findViewById<TextView>(R.id.textview_registernow)
            registerNow.setOnClickListener {
                val mIntent = Intent(activity, LoginMain::class.java)
                requireActivity().startActivity(mIntent)
            }
            unregisteredView
        }
    }

        private fun populateDataLayout(res: JSONObject, view: View) {
        val response = Gson().fromJson(res.toString(), ServerResponse::class.java)
        //here I need the view
        val lVDataMenu: ListView = view.findViewById(R.id.lV_DataMenu)
        lVDataMenu.adapter = lVadapter

        else if (response.code == 403) {

        }
}


    override fun onResume() {
        getUID.requestUidToken()?.addOnSuccessListener { getTokenResult ->
            Volley(config.DATA_LOADDATA, { res ->
                **CRASH**populateDataLayout(res,requireView())
            }, {
                tv_unverified.visibility = View.VISIBLE
            })
        }
        super.onResume()
    }

I could get rid of the use of "view.findViewById(R.id.lV_DataMenu)" in the populate Routine but then I wouldn't understand the problem here. I wasn't able to reacreate the error, so that's why I'm asking. So why is it possible that the requireView() doesn't return a View? Is it because the user switches to the next fragment while Volley isn't finished with the parsing? And how can I fix that?

KayD
  • 372
  • 5
  • 17

1 Answers1

2
override fun onResume() {
    getUID.requestUidToken()?.addOnSuccessListener { getTokenResult ->
        Volley(config.DATA_LOADDATA, { res ->
            **CRASH**populateDataLayout(res,requireView())
        }, {
            tv_unverified.visibility = View.VISIBLE
        })
    }

You're not calling requireView() in onResume() here.

You have two asynchronous calls and you're passing them functions (those lambda expressions in {}) as parameters. These functions are invoked later e.g. when the async operation completes. At that time your fragment could have finished its lifecycle.

Two common strategies for handling it:

  1. Cancel your pending async calls when the fragment is finishing up. You seem to fire the calls in onCreateView() and onResume(). The lifecycle counterparts for these would be onDestroyView() and onPause(). How to do this specifically depends on the async APIs you're using.

  2. Fire-and-forget with staleness check: When the async completes, check if the fragment is still alive, otherwise do nothing. For example, change

    populateDataLayout(res,requireView())
    

    to something like

    view?.let { populateataLayout(res, it) }
    

    where view is the nullable getView() counterpart to nonnull requireView().

laalto
  • 150,114
  • 66
  • 286
  • 303
  • This was very helpfull to understand the problem, thank you sir. May I ask: Is there anything I can read to get deeper into this specific topic and handle the problem with the, as you call it "Fire-and-forget" Method? – KayD Mar 15 '20 at 10:27
  • view?.let = the "let" basically says: if view !=null then {do this here}? – KayD Mar 15 '20 at 10:32
  • 1
    Yep. The magic is the `?.` safecall operator that invokes the `let` scope function only if the operand is nonnull. – laalto Mar 15 '20 at 10:44