android - 夜间模式下 RecyclerView 的问题 android kotlin NullPointerException
问题描述
我的 Fragment 中有一个 RecyclerView,应用中有两个主题:Day、Night 和 System Default。
有一个奇怪的问题会导致 NullPointerException。如果我将主题切换到夜间并退出应用程序,然后再次进入,则 NullPointerException 崩溃并且应用程序将无法再次打开,直到我将其从手机或模拟器中删除。但是,如果我一直停留在浅色主题并关闭并再次打开应用程序,那么一切都会好起来的。
片段代码:
private var _binding: FragmentListBinding? = null
private val binding get() = _binding!!
private lateinit var rvAdapter: RvStatesAdapter
private var statesList = ArrayList<State>()
private var databaseReferenceStates: DatabaseReference? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentListBinding.inflate(inflater, container, false)
checkTheme()
initDatabase()
getStates()
binding.rvStates.layoutManager = LinearLayoutManager(requireContext())
binding.ibMenu.setOnClickListener {
openMenu()
}
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private fun getStates() {
databaseReferenceStates?.addValueEventListener(object: ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()) {
for (stateSnapshot in snapshot.children) {
val state = stateSnapshot.getValue(State::class.java)
statesList.add(state!!)
}
rvAdapter = RvStatesAdapter(statesList)
binding.rvStates.adapter = rvAdapter
}
}
override fun onCancelled(error: DatabaseError) {
}
})
}
private fun initDatabase() {
FirebaseApp.initializeApp(requireContext());
databaseReferenceStates = FirebaseDatabase.getInstance().getReference("States")
}
private fun openMenu() {
binding.drawerLayout.openDrawer(GravityCompat.START)
binding.navigationView.setNavigationItemSelectedListener {
when (it.itemId) {
R.id.about_app -> Toast.makeText(context, "item clicked", Toast.LENGTH_SHORT).show()
R.id.change_theme -> {
chooseThemeDialog()
}
}
binding.drawerLayout.closeDrawer(GravityCompat.START)
true
}
}
private fun checkTheme() {
when (ThemePreferences(requireContext()).darkMode) {
0 -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
(activity as AppCompatActivity).delegate.applyDayNight()
}
1 -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
(activity as AppCompatActivity).delegate.applyDayNight()
}
2 -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
(activity as AppCompatActivity).delegate.applyDayNight()
}
}
}
private fun chooseThemeDialog() {
val builder = AlertDialog.Builder(requireContext())
builder.setTitle("Choose Theme")
val themes = arrayOf("Light", "Dark", "System default")
val checkedItem = ThemePreferences(requireContext()).darkMode
builder.setSingleChoiceItems(themes, checkedItem) {dialog, which ->
when (which) {
0 -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
(activity as AppCompatActivity).delegate.applyDayNight()
ThemePreferences(requireContext()).darkMode = 0
dialog.dismiss()
}
1 -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
(activity as AppCompatActivity).delegate.applyDayNight()
ThemePreferences(requireContext()).darkMode = 1
dialog.dismiss()
}
2 -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
(activity as AppCompatActivity).delegate.applyDayNight()
ThemePreferences(requireContext()).darkMode = 2
dialog.dismiss()
}
}
}
val dialog = builder.create()
dialog.show()
}
主题偏好类:
companion object {
private const val DARK_STATUS = ""
}
private val preferences = PreferenceManager.getDefaultSharedPreferences(context)
var darkMode = preferences.getInt(DARK_STATUS, 0)
set(value) = preferences.edit().putInt(DARK_STATUS, value).apply()
.xml 代码中的 RecyclerView:
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvStates"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="20dp"
android:background="@color/background"
app:layoutManager="LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvLabelDescription"
tools:listitem="@layout/rv_state_list" />
以及来自 RecyclerView Adapter 的代码:
inner class MyViewHolder(val binding: RvStateListBinding): RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(RvStateListBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem = stateList[position]
with(holder) {
with(stateList[position]) {
binding.tvState.text = this.name
Picasso.with(itemView.context)
.load(this.image)
.into(binding.ibState, object: Callback {
override fun onSuccess() {
binding.progressBar.visibility = View.GONE
}
override fun onError() {
}
})
itemView.ibState.setOnClickListener {
val action = StatesFragmentDirections.actionListFragmentToAttractionsFragment(currentItem)
itemView.findNavController().navigate(action)
}
}
}
}
override fun getItemCount(): Int {
return stateList.size
}
解决方案
您的崩溃来自该onDataChange
回调 - 您正在调用getStates
(在binding
设置之后)但是当结果返回并onDataChange
尝试访问binding
时,它再次为 null。
如果我不得不猜测,当您调用checkTheme
并且它调用applyDayNight
时,如果活动已经在使用您正在应用的主题,那可能没有任何作用。因此,如果您正在设置浅色主题,并且它已经在使用浅色主题,那没问题。(如果您将系统设置为深色主题,您可以通过查看它是否停止崩溃来测试这一点,假设您的应用主题是 DayNight 主题)
但如果它需要更改为深色主题,则意味着重新创建Activity
and Fragment
。我不知道现在重新创建的细节,但至少您可能会使用新主题重新创建视图布局。这意味着布局被破坏,这意味着onDestroyView
被调用 - 在那里,你设置binding
为 null
因此,我假设您的onDataChange
回调要么在布局(和绑定)破坏和重新创建之间到达,要么整个 Fragment 被破坏并且回调只是调用一个binding
永远不会恢复的变量。
最简单的解决方法就是不设置binding
为空。让它lateinit
像 Emmanuel 所说的那样,每次onCreateView
调用它都会被初始化/覆盖。onCreateView
如果回调更新旧的绑定布局,这很酷,新的无论如何都会要求更新
只要确保您正在清理您正在设置的事件侦听器(databaseReferenceStates
如果需要) - 如果这是您清除绑定的原因onDestroyView
,则侦听器仍然具有对包含该变量的片段的引用,您可以最终将死者保留在内存中(否则你可以只是 null-check binding
)
推荐阅读
- java - JNI 在应用程序中检测到错误 java.lang.UnsatisfiedLinkError 在 android.tools.build:gradle 3.6.0 中找不到“xxxxx.so”
- reactjs - 加载函数是 wp.element.createElement
- python - 使用按钮在信息小部件之间切换
- sql-server - 在雪花查询中使用 Like 运算符匹配整个世界
- python - 如何使用python在excel中对日期列进行验证?
- c++ - 如何在 C++ 中获取字符串的特定部分?
- python - 如何使用 Matplotlib 的动画功能?
- c++ - 使用 C++ 的 if 语句中的 return 关键字出错
- r - 当用户单击树状图块时,悬停值并启用链接
- node.js - body-parser 不能用于 express