4

I am using Kotlin Android Extension to access view directly by their id. I have a progress bar which I access directly in fragment using id i.e progress_bar

<ProgressBar
    android:id="@+id/progress_bar"
    style="@style/Widget.AppCompat.ProgressBar.Horizontal"
    android:layout_width="match_parent"
    android:layout_height="15dp"
    android:indeterminate="true"/>

In fragment, I am showing and hiding it with this code

progress_bar.visibility = if (visible) View.VISIBLE else View.GONE

It is working perfectly until I rotate the screen. After that, it throws the exception

java.lang.IllegalStateException: progress_bar must not be null.

The variable gets null on screen rotation. How to solve this problem?

Fragment code

class SingleAppFragment : Fragment() {

private lateinit var appName: String

companion object {
    fun newInstance(appName: String = ""): SingleAppFragment {
        val fragment = SingleAppFragment()
        val args = Bundle()
        args.putString(Constants.EXTRA_APP_NAME, appName)
        fragment.arguments = args
        return fragment
    }
}

private var mListener: OnFragmentInteractionListener? = null

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    appName = if (arguments != null && !arguments.getString(Constants.EXTRA_APP_NAME).isEmpty()) {
        arguments.getString(Constants.EXTRA_APP_NAME)
    } else {
        Constants.APP_NAME_FACEBOOK
    }
}

override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
                          savedInstanceState: Bundle?): View? {
    return inflater!!.inflate(R.layout.fragment_single_app, container, false)
}

override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    initView()
    setEventListeners()
}

private fun initView() {
    var canShowSnackBar = true

    web_single_app.webViewClient = object : WebViewClient() {

        override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
            super.onPageStarted(view, url, favicon)
            showHideProgressBar(true)
            canShowSnackBar = true
        }

        override fun onPageFinished(view: WebView?, url: String?) {
            super.onPageFinished(view, url)
            showHideProgressBar(false)
        }

        override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {
            web_single_app.stopLoading()
            if (canShowSnackBar) {
                mListener?.onErrorWebView()
                canShowSnackBar = false
            }
        }
    }
    web_single_app.settings.javaScriptEnabled = true
    web_single_app.loadUrl(Constants.APP_NAME_URL_MAP[appName])
}

private fun setEventListeners() {
    back_web_control.setOnClickListener({
        web_single_app.goBack()
    })
}

fun showHideProgressBar(visible: Boolean) {
    progress_bar_web_control.visibility = if (visible) View.VISIBLE else View.GONE
}

fun loadUrl(appName: String) {
    web_single_app.loadUrl(Constants.APP_NAME_URL_MAP[appName])
}

override fun onAttach(context: Context?) {
    super.onAttach(context)
    if (context is OnFragmentInteractionListener) {
        mListener = context
    }
}

override fun onDetach() {
    super.onDetach()
    mListener = null
}

interface OnFragmentInteractionListener {
    fun onErrorWebView()
}
}

Steps to reproduce:

  1. Start Activity
  2. Fragment get loaded
  3. At Fragment load, I load an URL and show a progress bar
  4. At loading the URL I rotate the phone and the progress bar variable gets null
Palkesh Jain
  • 412
  • 4
  • 12
  • 2
    In which method do you get the progress_bar by Id? Please consider the fragment state lifecycle. Maybe you try to load it when the view is not ready yet. Refer to here https://developer.android.com/guide/components/fragments.html – Bruno Bieri Mar 01 '18 at 13:43
  • @BrunoBieri I am calling in the onCreateView() but before the "return view" statement. Hence, the variable didn't get assigned. Now, I am accessing the "progress_bar" like "view.progress_bar" and the problem is solved. Thank you. – Palkesh Jain Mar 01 '18 at 13:50
  • 2
    You should be doing this in `onViewCreated` – Michał Pawlik Mar 01 '18 at 13:53
  • Thanks, @MichałBaran, I will follow this – Palkesh Jain Mar 01 '18 at 14:00
  • @PalkeshJain I converted my comment to an answer. May you can mark it as an answer for your question. – Bruno Bieri Mar 01 '18 at 14:07
  • @PalkeshJain from your comment it's not clear if the problem is solved. Do you still face the issue? – Bruno Bieri Mar 01 '18 at 14:27
  • @BrunoBieri yes, still facing the issue. Now, I am calling the view methods in onViewCreated(). The crashing scenario: I load an URL and then immediately rotate the screen. – Palkesh Jain Mar 01 '18 at 14:45
  • @PalkeshJain as written please provide the code when you **assign** the `progress_bar` variable in your code. – Bruno Bieri Mar 01 '18 at 14:52
  • @BrunoBieri I have added the whole fragment code, please check. Thanks – Palkesh Jain Mar 01 '18 at 16:19
  • @PalkeshJain I can't help you with it. You haven't posted the code where you **assign** the `progress_bar` variable. From the code you posted it seems the variable is not assgined at all. – Bruno Bieri Mar 01 '18 at 19:24
  • 1
    @BrunoBieri the variable is assigned by kotlin android extension, in which we don't need to initialize the view variables. My problem is solved by setRetainInstance(true) – Palkesh Jain Mar 01 '18 at 20:32
  • @PalkeshJain, if you have enough (mana) rate, you can accept or increase an answer that helped you. In my case I also faced this problem, but in a large project with coroutines, and `retainInstance = true` didn't help. In a new project with a fragment (without `retainInstance = true`) it doesn't reproduce. – CoolMind Nov 02 '18 at 12:46
  • Thank you for accepting the answer. Hope it helped. – CoolMind Nov 06 '18 at 13:54

3 Answers3

1

In which method do you get the progress_bar by Id?

Please consider the fragment state lifecycle. Maybe you try to load it when the view is not ready yet.

Ensure your progress_bar variable is assigned only after the view is ready. For example in the onViewCreated method.

See here the official Android lifecycle:

Official Android lifecycle

Update

As @CoolMind pointed out the diagram doesn't show the method onViewCreated.

The complete Android Activity/Fragment lifecycle can be found here:

The complete Android Activity/Fragment lifecycle

Bruno Bieri
  • 9,724
  • 11
  • 63
  • 92
1

In my case this bug happens from time to time. Of course, onViewCreated() is a good method to place your code in. But sometimes it's strangely not enough. And setRetainInstance(true) may help, may not. So sometimes this helps: access your Views with a view variable. You can even access them inside onCreateView(). You can use ?. for a guarantee that an application won't crash (of course, some views won't update in this case). If you wish to get context, use view.context.

In my case this bug reproduced only in Kotlin coroutines.

private fun showProgress(view: View) {
    view.progressBar?.visibility = View.VISIBLE
}

private fun hideProgress(view: View) {
    view.progressBar?.visibility = View.GONE
}

Then in code:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    showData(view)
}

private fun showData(view: View) {
    showProgress(view)
    adapter = SomeAdapter()
    adapter.setItems(items)
    val divider = SomeItemDecoration(view.context)
    view.recycler_view?.run {
        addItemDecoration(divider)
        adapter = this@SomeFragment.adapter
        layoutManager = LinearLayoutManager(view.context)
        setHasFixedSize(true)
    }
    hideProgress(view)
}
CoolMind
  • 26,736
  • 15
  • 188
  • 224
0

Add retain intance true to the fragment so that it will not be destroyed when an orientation changes occurs

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    retainInstance=true
}

Also do a null check using safe call operator before accessing views

fun showHideProgressBar(visible: Boolean) {
    progress_bar_web_control?.visibility = if (visible) View.VISIBLE else View.GONE
}
Sharath kumar
  • 4,064
  • 1
  • 14
  • 20