【问题标题】:How do I ensure lateinit variable initialization before it is needed?如何确保在需要之前初始化 lateinit 变量?
【发布时间】:2021-10-09 04:27:07
【问题描述】:

我有一个应用程序大部分时间都会启动,但每启动 7 次左右就会崩溃并出现错误:

kotlin.UninitializedPropertyAccessException:lateinit 属性 weekdayList 尚未初始化

这是一个明显的错误,我只是不确定如何确保变量在我的应用程序上下文中足够早地初始化。

我尝试过的事情

  • 我尝试移动变量,使“内部”和“外部” 变量,一个在onCreate 内,一个下划线变量为 类变量。

  • 更改视图模型,使其等到对数据库的调用完成(我 无法完成这项工作,但主要是因为我不知道该怎么做)。

我认为问题出在onCreate 函数中,并且工作日观察设置变量值的速度没有比调用任务观察(需要weekdayList 变量)更快?


编辑 1

我引用了this,但我最终遇到了类似的错误

java.lang.IndexOutOfBoundsException:空列表不包含索引 1 处的元素。


编辑 2

我现在了解lateinit 变量和可空对象的工作原理,我想尝试更好地澄清这一点。

变量weekdayList 需要在我点击observetaskList 之前将其初始化为正确的列表,否则应用程序将崩溃。

我尝试将变量设置为可为空,结果是:

  • 当程序为空时跳过部分程序(不是一个选项)
  • 因空指针异常而崩溃(如果设置为不可为空)
  • 没有任务被分配到任何一天,这意味着没有更新回收站视图,从而使应用看起来不包含任何任务。
  • 工作日按钮不起作用,因为没有 weekdayList 可供他们比较以启动下一个活动

我的问题不在于确定它是否是null,而是试图保证它不会是null

抱歉给您带来了困扰


主要活动

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private val plannerViewModel: PlannerViewModel by viewModels {
        PlannerViewModelFactory((application as PlannerApplication).repository)
    }

    private var weekdayList: List<Weekday> = listOf()
    private var taskList: List<Task> = listOf()
    private var taskDayList = mutableListOf<Task>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val clearButtonText = binding.clearCardText
        val sundayButtonText = binding.sundayCardText
        val mondayButtonText = binding.mondayCardText
        val tuesdayButtonText = binding.tuesdayCardText
        val wednesdayButtonText = binding.wednesdayCardText
        val thursdayButtonText = binding.thursdayCardText
        val fridayButtonText = binding.fridayCardText
        val saturdayButtonText = binding.saturdayCardText
        val sundayRv: RecyclerView = binding.sundayRv
        val sundayAdapter = TaskRvAdapter(null)
        sundayRv.adapter = sundayAdapter
        sundayRv.layoutManager = LinearLayoutManager(this)
        val mondayRv: RecyclerView = binding.mondayRv
        val mondayAdapter = TaskRvAdapter(null)
        mondayRv.adapter = mondayAdapter
        mondayRv.layoutManager = LinearLayoutManager(this)
        val tuesdayRv: RecyclerView = binding.tuesdayRv
        val tuesdayAdapter = TaskRvAdapter(null)
        tuesdayRv.adapter = tuesdayAdapter
        tuesdayRv.layoutManager = LinearLayoutManager(this)
        val wednesdayRv: RecyclerView = binding.wednesdayRv
        val wednesdayAdapter = TaskRvAdapter(null)
        wednesdayRv.adapter = wednesdayAdapter
        wednesdayRv.layoutManager = LinearLayoutManager(this)
        val thursdayRv: RecyclerView = binding.thursdayRv
        val thursdayAdapter = TaskRvAdapter(null)
        thursdayRv.adapter = thursdayAdapter
        thursdayRv.layoutManager = LinearLayoutManager(this)
        val fridayRv: RecyclerView = binding.fridayRv
        val fridayAdapter = TaskRvAdapter(null)
        fridayRv.adapter = fridayAdapter
        fridayRv.layoutManager = LinearLayoutManager(this)
        val saturdayRv: RecyclerView = binding.saturdayRv
        val saturdayAdapter = TaskRvAdapter(null)
        saturdayRv.adapter = saturdayAdapter
        saturdayRv.layoutManager = LinearLayoutManager(this)


        // Setting day card names
        clearButtonText.text = "Clear"
        sundayButtonText.text = "Sun"
        mondayButtonText.text = "Mon"
        tuesdayButtonText.text = "Tue"
        wednesdayButtonText.text = "Wed"
        thursdayButtonText.text = "Thu"
        fridayButtonText.text = "Fri"
        saturdayButtonText.text = "Sat"
        sundayButtonText.text = "Sun"

        plannerViewModel.allWeekdays.observe(this, {
            weekdayList = it
        })

        plannerViewModel.allTasks.observe(this, { tasks ->
            taskList = tasks
            taskDayList = mutableListOf()

            for (i in 1..7) {

                taskDayList = sortTasks(weekdayList[i], taskList)

                when (i) {
                    1 -> {
                        sundayAdapter.submitList(taskDayList)
                        toggleVisibility(taskDayList, binding.sundayInner,
                                binding.sundayCardText, sundayRv, binding.sundayNoTasks)
                    }
                    2 -> {
                        mondayAdapter.submitList(taskDayList)
                        toggleVisibility(taskDayList, binding.mondayInner,
                                binding.mondayCardText, mondayRv, binding.mondayNoTasks)
                    }
                    3 -> {
                        tuesdayAdapter.submitList(taskDayList)
                        toggleVisibility(taskDayList, binding.tuesdayInner,
                                binding.tuesdayCardText, tuesdayRv, binding.tuesdayNoTasks)
                    }
                    4 -> {
                        wednesdayAdapter.submitList(taskDayList)
                        toggleVisibility(taskDayList, binding.wednesdayInner,
                                binding.wednesdayCardText, wednesdayRv, binding.wednesdayNoTasks)
                    }
                    5 -> {
                        thursdayAdapter.submitList(taskDayList)
                        toggleVisibility(taskDayList, binding.thursdayInner,
                                binding.thursdayCardText, thursdayRv, binding.thursdayNoTasks)
                    }
                    6 -> {
                        fridayAdapter.submitList(taskDayList)
                        toggleVisibility(taskDayList, binding.fridayInner,
                                binding.fridayCardText, fridayRv, binding.fridayNoTasks)
                    }
                    7 -> {
                        saturdayAdapter.submitList(taskDayList)
                        toggleVisibility(taskDayList, binding.saturdayInner,
                                binding.saturdayCardText, saturdayRv, binding.saturdayNoTasks)
                    }
                }
            }
        })
    }

    private fun toggleVisibility(taskDayList: List<Task>, inner: ConstraintLayout,
                                 cardText: View, rv: RecyclerView, noTask: View) {
        if (taskDayList.count() == 0 ) {
            val newConstraintSet = ConstraintSet()
            newConstraintSet.clone(inner)
            newConstraintSet.connect(noTask.id, ConstraintSet.TOP,
                    cardText.id, ConstraintSet.BOTTOM)
            newConstraintSet.applyTo(inner)

            newConstraintSet.connect(cardText.id, ConstraintSet.BOTTOM,
                    noTask.id, ConstraintSet.TOP)
            newConstraintSet.applyTo(inner)

            rv.visibility = View.GONE
            noTask.visibility = View.VISIBLE

            Log.i("this", "ran zero")
        } else {
            val newConstraintSet = ConstraintSet()
            newConstraintSet.clone(inner)
            newConstraintSet.connect(rv.id, ConstraintSet.TOP,
                    cardText.id, ConstraintSet.BOTTOM)
            newConstraintSet.applyTo(inner)

            newConstraintSet.connect(cardText.id, ConstraintSet.BOTTOM,
                    rv.id, ConstraintSet.TOP)
            newConstraintSet.applyTo(inner)

            rv.visibility = View.VISIBLE
            noTask.visibility = View.GONE

            Log.i("this", "ran else")
        }
    }

    private fun sortTasks(day: Weekday, tasks: List<Task>): MutableList<Task> {
        val newAdapterList = mutableListOf<Task>()

        tasks.forEach {
            if (it.weekdayId == day.id) {
                newAdapterList.add(it)
            }
        }

        return newAdapterList
    }

    private fun startWeekdayActivity(day: Weekday) {
        val intent = Intent(this, WeekdayActivity::class.java)
        intent.putExtra("dayId", day.id)
        this.startActivity(intent)
    }

    private fun clearDb(taskList: List<Task>) {
        val alertDialog: AlertDialog = this.let { outerIt ->
            val builder = AlertDialog.Builder(outerIt)
            builder.apply {
                setPositiveButton("Clear",
                        DialogInterface.OnClickListener { dialog, id ->
                            if (taskList.count() == 0) {
                                Toast.makeText(context, "No tasks to clear", Toast.LENGTH_SHORT).show()
                            } else {
                                plannerViewModel.deleteAllTasks()
                                Toast.makeText(context, "Tasks cleared", Toast.LENGTH_SHORT).show()
                            }
                        })
                setNegativeButton("Cancel",
                        DialogInterface.OnClickListener { dialog, id ->
                            // User cancelled the dialog
                        })
            }
                    .setTitle("Clear tasks?")
                    .setMessage("Are you sure you want to clear the weeks tasks?")

            builder.create()
        }

        alertDialog.show()
    }

    private fun checkDay(dayIn: String, weekdayList: List<Weekday>) {
        weekdayList.forEach {
            if (dayIn == "clear_card" && it.day == "Clear") {
                clearDb(taskList)
            } else {
                val dayInAbr = dayIn.substring(0, 3).toLowerCase(Locale.ROOT)
                val dayOutAbr = it.day.substring(0, 3).toLowerCase(Locale.ROOT)

                if (dayInAbr == dayOutAbr) {
                    startWeekdayActivity(it)
                }
            }
        }
    }

    fun buttonClick(view: View) {
        when (view.id) {
            R.id.clear_card -> checkDay(view.context.resources.getResourceEntryName(R.id.clear_card).toString(), weekdayList)
            R.id.sunday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.sunday_card).toString(), weekdayList)
            R.id.monday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.monday_card).toString(), weekdayList)
            R.id.tuesday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.tuesday_card).toString(), weekdayList)
            R.id.wednesday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.wednesday_card).toString(), weekdayList)
            R.id.thursday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.thursday_card).toString(), weekdayList)
            R.id.friday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.friday_card).toString(), weekdayList)
            R.id.saturday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.saturday_card).toString(), weekdayList)
        }
    }
}

视图模型

class PlannerViewModel(private val repository: DbRepository) : ViewModel() {
    val allWeekdays: LiveData<List<Weekday>> = repository.allWeekdays.asLiveData()
    val allTasks: LiveData<List<Task>> = repository.allTasks.asLiveData()

    fun insertWeekday(weekday: Weekday) = viewModelScope.launch {
        repository.insertWeekday(weekday)
    }

    fun insertTask(task: Task) = viewModelScope.launch {
        repository.insertTask(task)
    }

    fun deleteTask(task: Task) = viewModelScope.launch {
        repository.deleteTask(task)
    }

    fun deleteAllTasks() = viewModelScope.launch {
        repository.deleteAllTasks()
    }
}

class PlannerViewModelFactory(private val repository: DbRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(PlannerViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return PlannerViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

【问题讨论】:

标签: android android-studio kotlin viewmodel kotlin-lateinit


【解决方案1】:

声明为 lateinit 的变量只是意味着您确定当对象被取消引用时它不会为空。在您的情况下,您在为其分配值之前从 weekdayList 对象调用方法。清楚地理解这个概念以及为什么你的代码有效是很重要的。

编码愉快!

【讨论】:

  • 感谢您的回答!我想我理解lateinit 的概念。这对我来说似乎是一个同步问题,对吧?没有保证在调用第二个观察之前它会被赋予一个值。我该如何解决这个问题?
  • 正如另一个答案所暗示的那样,您可以使变量为空,然后在使用它之前检查它是否不为空,如果不是,然后才使用它。
  • 但在这种情况下,taskList 无法组织到适当的日期,因此会显示不正确的信息。我该如何解决这个问题?
【解决方案2】:

您可以使用“isInitialized”方法来检查“lateinit”变量是否已初始化。

请参考以下文章-

https://blog.mindorks.com/how-to-check-if-a-lateinit-variable-has-been-initialized

【讨论】:

    【解决方案3】:

    lateinit 是一种让您在声明 var 时没有初始值的方法。这是一种避免获取永远不会为空的内容并将其设为可空(并且必须永远对其进行空检查)的好方法,这样您就可以暂时将其设置为空作为占位符,什么都不会看到。

    你所做的是向编译器承诺“好的,我不会在构造类时提供值,但是 我保证我会在尝试之前将其设置为某个值阅读它”。你是在告诉编译器相信你,你知道你的代码是如何工作的,并且你可以保证一切都会好起来的。

    您的问题是,您似乎无法保证在您写入该属性之前不会尝试读取该属性。您的状态可以是“有值”或“没有值”,其余代码可能会遇到任何一种状态。

    “无值”状态基本上是null,因此您可能应该将变量设为可空,并将其初始化为空。 Kotlin 有所有很好的 null 安全性来帮助你的代码处理它,直到你得到一个值。 lateinit 似乎是适合这项工作的错误工具,即使您检查了 ::isInitialized,当空检查的东西就在那里时,它只会让您的生活变得更加艰难!

    【讨论】:

    • 感谢您对lateinit 的描述和nullables/null 检查,非常清楚。我目前遇到的问题不是我不再不清楚这个概念,而是太多的代码依赖于一个被初始化的变量,所以它不是一个选项。如果weekdayListnull,则不会设置任何recyclerview,从而使应用程序几乎毫无用处。我不需要跳过带有空值检查的部分,我需要保证在需要之前设置变量
    • 但你不能保证,对吧?你试图在它被设置之前阅读它,这发生在那个观察者 lambda 中。这将在稍后发生,并且您无法控制何时发生,或者您的观察者将触发的顺序(或者至少,这是一个复杂的推理,您不应该依赖它)。相反,您应该做的是让您的代码流更具反应性。当您在该观察者中获得weekdayList 值时,使 that 调用一些填充回收器的更新函数。你可以从你的allTasks观察者那里打电话
    • 一般的想法是你的观察者用一些新数据触发,然后你说“哦,好吧,是时候做一个更新了”。但是,如果您还没有准备好更新,因为您依赖于一些您还没有的其他数据(例如 weekdayListallTasks),您可以存储您拥有的数据,以便下次更新时使用试图。当观察者获得新值并且您拥有所需的所有部分时,然后您可以进行更新并填充 recyclerview。您需要以异步方式思考,其中事情发生在任意时间,而不是通常的方式,即您的代码以可预测的顺序触发
    • 谢谢!我想这就是我需要的答案。稍后用更正的代码更新原始问题,让我知道它是否看起来更像你在说的(更重要的是,它似乎可以在不运行不必要的东西的情况下工作)
    • @schnondle 别担心,伙计,很高兴你明白了!
    【解决方案4】:

    使用惰性属性,更多信息请参考doc

    假设 weekDayList 是你要成功初始化的属性 ->

    private var weekDayList: List<WeekDay> by lazy {
        //return your first value
        listOf<WeekDay>()
    }
    

    这里有一个关于 LifeCycleAware Lazy 属性的有用链接:blog 虽然,它不是必需的。

    【讨论】:

      【解决方案5】:

      cmets 中仙人掌帮助的解决方案。

      我将很多列表依赖项移到了一个名为setAdapterList 的新函数中。这允许两个observes 运行该函数,并且只有两个列表都初始化的那个将运行包含的代码。我保留了变量lateinit,到目前为止它似乎还在工作!

      主要活动的主要变化

      ...
      
      private fun setAdapterLists(adapterList: List<TaskRvAdapter>, rvList: List<RecyclerView>) {
              if (this::weekdayList.isInitialized  && this::taskList.isInitialized) {
                  adapterList.forEach {
                      taskDayList = mutableListOf()
                      val i = adapterList.indexOf(it)
                      taskDayList = sortTasks(weekdayList[i + 1], taskList)
      
                      Log.i("rvli", rvList[i].toString())
      
                      when (i) {
                          0 -> {
                              adapterList[i].submitList(taskDayList)
                              toggleVisibility(taskDayList, binding.sundayInner,
                                      binding.sundayCardText, rvList[i], binding.sundayNoTasks)
                          }
                          1 -> {
                              adapterList[i].submitList(taskDayList)
                              toggleVisibility(taskDayList, binding.mondayInner,
                                      binding.mondayCardText, rvList[i], binding.mondayNoTasks)
                          }
                          2 -> {
                              adapterList[i].submitList(taskDayList)
                              toggleVisibility(taskDayList, binding.tuesdayInner,
                                      binding.tuesdayCardText, rvList[i], binding.tuesdayNoTasks)
                          }
                          3 -> {
                              adapterList[i].submitList(taskDayList)
                              toggleVisibility(taskDayList, binding.wednesdayInner,
                                      binding.wednesdayCardText, rvList[i], binding.wednesdayNoTasks)
                          }
                          4 -> {
                              adapterList[i].submitList(taskDayList)
                              toggleVisibility(taskDayList, binding.thursdayInner,
                                      binding.thursdayCardText, rvList[i], binding.thursdayNoTasks)
                          }
                          5 -> {
                              adapterList[i].submitList(taskDayList)
                              toggleVisibility(taskDayList, binding.fridayInner,
                                      binding.fridayCardText, rvList[i], binding.fridayNoTasks)
                          }
                          6 -> {
                              adapterList[i].submitList(taskDayList)
                              toggleVisibility(taskDayList, binding.saturdayInner,
                                      binding.saturdayCardText, rvList[i], binding.saturdayNoTasks)
                          }
                      }
                  }
              }
          }
          
      
      ...
      
      

      【讨论】:

      • 我在得到更多帮助后更改了答案
      猜你喜欢
      • 1970-01-01
      • 2022-12-15
      • 2020-04-26
      • 1970-01-01
      • 1970-01-01
      • 2016-10-03
      • 2021-12-11
      • 1970-01-01
      • 2023-02-14
      相关资源
      最近更新 更多