Ненулевой параметр при смене темы из тени уведомления

#android #kotlin #android-fragments #android-viewpager #android-viewpager2

#Android #kotlin #android-фрагменты #android-viewpager #android-viewpager2

Вопрос:

Всякий раз, когда я использую тень уведомления для переключения между темными и светлыми темами устройства (мое приложение при запуске), по какой-то причине всегда происходит сбой. Минимальный API моего приложения — 29 (Android 10). Logcat указывает на строку кода, в которой причина ошибки не очевидна. ( import android.view.* ). Как я могу предотвратить повторение этого?

  java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter view
 

Класс активности

 class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if (savedInstanceState == null) supportFragmentManager.beginTransaction()
            .replace(R.id.detail_container, MainFragment())
            .commitNow()
    }
}
 

Основной класс фрагмента

 package com.example.vp2

import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.text.TextUtils
import android.view.*
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Spinner
import android.widget.TextView
import androidx.appcompat.app.ActionBar
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.core.app.NavUtils
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import java.util.*

class MainFragment : androidx.fragment.app.Fragment() {
    private lateinit var mySpinnerItems: Array<String>

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

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

        return v
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        val mSpinner = requireView().findViewById<Spinner>(R.id.mSpinner)
        val mViewPager2 = requireView().findViewById<ViewPager2>(R.id.mViewPager2)

        // Spinner items array
        mySpinnerItems = arrayOf(
            "Item 1",
            "Item 2",
            "Item 3",
        )

        val arrayAdapter = ArrayAdapter(requireView().context, android.R.layout.simple_spinner_item, mySpinnerItems)
        arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_item)
   
        mSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
            override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
                when {
                    mySpinnerItems[position] == "Item 1" -> {
                        mViewPager2.setCurrentItem(0, false)
                    }

                    mySpinnerItems[position] == "Item 2" -> {
                        mViewPager2.setCurrentItem(1, false)
                    }

                    else -> {
                        mViewPager2.setCurrentItem(2, false)
                    }
                }
            }

            override fun onNothingSelected(parent: AdapterView<*>) {
                mViewPager2.setCurrentItem(0, false)
            }
        }
        mSpinner.adapter = arrayAdapter

        mViewPager2.adapter = MySpinnerFragmentAdapter(this)
        mViewPager2.orientation = ViewPager2.ORIENTATION_HORIZONTAL

        mViewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
            override fun onPageSelected(position: Int) {
                when (position) {
                    0 -> {
                        mSpinner.setSelection(0)
                    }
                    1 -> {
                        mSpinner.setSelection(1)
                    }
                    else -> {
                        mSpinner.setSelection(2)
                    }
                }
                super.onPageSelected(position)
            }
        })

        super.onActivityCreated(savedInstanceState)
    }

    private class MySpinnerFragmentAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
        private val intItems = 3

        override fun createFragment(position: Int): Fragment {
            return when (position) {
                0 -> Fragment1()
                1 -> Fragment2()
                else -> Fragment3()
            }
        }

        override fun getItemCount(): Int {
            return intItems
        }
    }

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        menu.clear()
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return if (item.itemId == android.R.id.home) {
            val intent = activity?.let { NavUtils.getParentActivityIntent(it) }
            when {
                intent != null -> {
                    intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
                    NavUtils.navigateUpTo(requireActivity(), intent)
                }
            }
            true
        } else super.onOptionsItemSelected(item)
    }
}
 

Основной макет активности

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@ id/detail_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
</LinearLayout>
 

Макет основного фрагмента

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@ id/detail_container">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@ id/mViewPager2"
        android:layout_height="0dp"
        android:layout_width="match_parent"
        android:layout_weight="1" />

    <!-- divider (start)-->
    <View
        android:id="@ id/divider"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_marginBottom="5dp"
        android:background="?android:attr/textColorSecondary" />
    <!-- divider (end)-->

    <Spinner
        android:id="@ id/mSpinner"
        style="@style/Widget.AppCompat.Spinner.Underlined"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:spinnerMode="dropdown"/>
</LinearLayout>
 

Обновить

введите описание изображения здесь

предложение cactustictacs

     mSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
        override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
            when {
                mySpinnerItems[position] == "Item 1" -> {
                    mViewPager2.setCurrentItem(0, false)
                }

                mySpinnerItems[position] == "Item 2" -> {
                    mViewPager2.setCurrentItem(1, false)
                }

                else -> {
                    mViewPager2.setCurrentItem(2, false)
                }
            }
        }

        override fun onNothingSelected(parent: AdapterView<*>) {
            // Code to perform some action when nothing is selected
            mViewPager2.setCurrentItem(0, false)
        }
    }
 

Ответ №1:

Трассировка стека (пожалуйста, в будущем публикуйте фактический текст, а не скриншот!) сообщает, что какой-то вызываемый параметр view имеет значение null, если его тип указан как ненулевой ( View вместо View? ). И это вызвано AdapterView.onItemSelected запуском (так что это view параметр в этом методе), который объявлен в onActivityCreated

По сути, этот onItemSelected метод должен иметь типы с нулевым значением для первых двух параметров, AdapterView<*>? и View? .

Это то, что вы получаете, если позволяете IDE автоматически реализовывать методы (с помощью ctrl I) — в документах есть этот View! тип, что означает, что, поскольку он исходит из Java, он может быть нулевым, может и нет, не знаю — так что безопасное значение по умолчанию View? . Когда вы указываете ненулевой тип (например View ), Kotlin выполняет нулевую проверку, чтобы убедиться, что это именно тот Intrinsics.checkNotNullParameter вызов. Вы получили значение null, поэтому оно выдало исключение!

Так что да, сделайте их обнуляемыми, затем проверьте их на нуль, прежде чем обращаться к ним. Также убедитесь appcompat , что он обновлен (по крайней мере 1.2.0 ), потому что у них была проблема, из-за которой Activity они не воссоздавались при использовании setDefaultNightMode

Комментарии:

1. Для части проверки нуля этот код будет предшествовать when предложению? Должен ли я проверять, являются ли они нулевыми одновременно?

2. Есть несколько способов проверить наличие нулей и безопасно обработать их ( kotlinlang.org/docs/reference/null-safety.html ) но в данном случае, поскольку это параметр, он является a val и не изменится — так что вы могли бы просто сделать if (view == null) return строку вверху. Поскольку компилятор знает, что вы продолжите работу с методом, только если значение не равно нулю, и значение не может измениться (и стать нулевым), он «умно преобразует» его в ненулевой тип для остальной части функции в любом случае. Но вы на самом деле не используете view его в своем коде, поэтому вам вообще не нужно его проверять! Просто сделайте тип nullable

3. Проблема решена. Я не включил все это, основываясь на вашем предложении. Нет смысла иметь бесполезный код. Большое спасибо.