Android: экземпляр View получает значение null при повороте экрана

Я использую Kotlin Android Extension для доступа к просмотру напрямую по их идентификатору. У меня есть индикатор выполнения, к которому я обращаюсь непосредственно во фрагменте, используя идентификатор, т.е. 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"/>

Во фрагменте я показываю и скрываю его с помощью этого кода

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

Он работает отлично, пока я не поверну экран. После этого выбрасывает исключение

java.lang.IllegalStateException: progress_bar не должен быть нулевым.

Переменная становится нулевой при повороте экрана. Как решить эту проблему?

Код фрагмента

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()
}
}

Действия для воспроизведения:

  1. Начать деятельность
  2. Фрагмент загружается
  3. При загрузке фрагмента я загружаю URL-адрес и показываю индикатор выполнения
  4. При загрузке URL-адреса я поворачиваю телефон, и переменная индикатора выполнения становится нулевой

person Palkesh Jain    schedule 01.03.2018    source источник
comment
В каком методе вы получаете progress_bar по идентификатору? Обратите внимание на жизненный цикл состояния фрагмента. Возможно, вы пытаетесь загрузить его, когда представление еще не готово. См. здесь developer.android.com/guide/components/fragments.html   -  person Bruno Bieri    schedule 01.03.2018
comment
@BrunoBieri Я звоню в onCreateView(), но перед оператором return view. Следовательно, переменная не была присвоена. Теперь я получаю доступ к progress_bar, например view.progress_bar, и проблема решена. Спасибо.   -  person Palkesh Jain    schedule 01.03.2018
comment
Вы должны сделать это в onViewCreated   -  person Michał Baran    schedule 01.03.2018
comment
Спасибо, @MichałBaran, я буду следить за этим   -  person Palkesh Jain    schedule 01.03.2018
comment
@PalkeshJain Я преобразовал свой комментарий в ответ. Можете ли вы отметить это как ответ на ваш вопрос.   -  person Bruno Bieri    schedule 01.03.2018
comment
@PalkeshJain из вашего комментария неясно, решена ли проблема. Вы все еще сталкиваетесь с проблемой?   -  person Bruno Bieri    schedule 01.03.2018
comment
@BrunoBieri да, все еще сталкиваюсь с проблемой. Теперь я вызываю методы представления в onViewCreated(). Сценарий сбоя: я загружаю URL-адрес и сразу же поворачиваю экран.   -  person Palkesh Jain    schedule 01.03.2018
comment
@PalkeshJain, как написано, укажите код, когда вы назначаете переменную progress_bar в своем коде.   -  person Bruno Bieri    schedule 01.03.2018
comment
@BrunoBieri Я добавил весь код фрагмента, проверьте. Спасибо   -  person Palkesh Jain    schedule 01.03.2018
comment
@PalkeshJain Я не могу тебе с этим помочь. Вы не опубликовали код, в котором вы назначаете переменную progress_bar. Из кода, который вы разместили, кажется, что переменная вообще не назначена.   -  person Bruno Bieri    schedule 01.03.2018
comment
@BrunoBieri переменная назначается расширением kotlin для Android, в котором нам не нужно инициализировать переменные представления. Моя проблема решается setRetainInstance(true)   -  person Palkesh Jain    schedule 01.03.2018
comment
@PalkeshJain, если у вас достаточно маны, вы можете принять или увеличить ответ, который вам помог. В моем случае я тоже столкнулся с этой проблемой, но в большом проекте с сопрограммами и retainInstance = true не помогло. В новом проекте с фрагментом (без retainInstance = true) не воспроизводится.   -  person CoolMind    schedule 02.11.2018
comment
Спасибо, что приняли ответ. Надеюсь, это помогло.   -  person CoolMind    schedule 06.11.2018


Ответы (3)


В моем случае эта ошибка возникает время от времени. Конечно, onViewCreated() — хороший способ разместить код. Но иногда этого, как ни странно, недостаточно. И setRetainInstance(true) может помочь, а может и нет. Так что иногда это помогает: получить доступ к своим View с помощью переменной view. Вы даже можете получить к ним доступ внутри onCreateView(). Вы можете использовать ?. для гарантии того, что приложение не выйдет из строя (конечно, в этом случае некоторые представления не будут обновляться). Если вы хотите получить context, используйте view.context.

В моем случае эта ошибка воспроизводилась только в сопрограммах Kotlin.

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

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

Затем в коде:

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 = [email protected]
        layoutManager = LinearLayoutManager(view.context)
        setHasFixedSize(true)
    }
    hideProgress(view)
}
person CoolMind    schedule 02.11.2018

В каком методе вы получаете progress_bar по идентификатору?

Учитывайте состояние фрагмента жизненного цикла. Возможно, вы пытаетесь загрузить его, когда представление еще не готово.

Убедитесь, что ваша переменная progress_bar назначена только после того, как представление будет готово. Например, в методе onViewCreated.

См. здесь официальный жизненный цикл Android:

Официальный жизненный цикл Android

Обновить

Как отметил @CoolMind, на диаграмме не показан метод onViewCreated.

Полный жизненный цикл Android Activity/Fragment можно найти здесь:

Полный жизненный цикл действия/фрагмента Android

person Bruno Bieri    schedule 01.03.2018
comment
Я обновил свой вопрос, теперь вы можете лучше понять сценарий. - person Palkesh Jain; 01.03.2018
comment
Вы правы, onViewCreated() — это правильное место для доступа к представлениям, но ваша схема его не содержит. :) - person CoolMind; 02.11.2018
comment
@CoolMind, ты прав. Я проверил источники, и кажется, что это действительно не так. Проверьте здесь (developer.android.com/reference/android/app/Fragment ) и здесь (developer.android.com/guide/components/fragments) - person Bruno Bieri; 02.11.2018
comment
@BrunoBieri, я согласен с тобой. К вашему сведению, см. stackoverflow.com/questions/37105513/edittext-view-returns -нуль. - person CoolMind; 02.11.2018
comment
@CoolMind спасибо!! Я не знал об этом. Обновил ответ. - person Bruno Bieri; 02.11.2018
comment
@BrunoBieri, удачного кодирования! :) - person CoolMind; 02.11.2018

Добавить сохранение экземпляра true для фрагмента, чтобы он не был уничтожен при изменении ориентации.

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

Также выполните нулевую проверку с помощью оператора безопасного вызова перед доступом к представлениям.

fun showHideProgressBar(visible: Boolean) {
    progress_bar_web_control?.visibility = if (visible) View.VISIBLE else View.GONE
}
person Sharath kumar    schedule 02.03.2018