I have an app that launches the majority of the time, but every 7 or so launches it crashes with the error:
kotlin.UninitializedPropertyAccessException: lateinit property weekdayList has not been initialized
This is a clear error, I'm just not sure how to ensure the variable is initialized early enough in the context of my app.
Things I have tried
I tried moving variables around, making "inner" and "outer" variables, one within
onCreateand an underscore led variable as the class variable.Changing the viewmodel so that it will wait until the call to the db has finished (I couldn't make this work, but mostly because I wasn't sure how to do it).
I think the problem is in the onCreate function, and that the weekday observe isn't setting the value of the variable faster than the task observe (where the weekdayList variable is needed) is called?
Edit 1
I referenced this but I end up with a similar error
java.lang.IndexOutOfBoundsException: Empty list doesn't contain element at index 1.
Edit 2
I understand how lateinit variables and nullables work at this point, I want to try and clarify this a little better.
The variable weekdayList needs to be initialized to the correct list before I hit the observe for the taskList, otherwise the app will crash.
I have tried setting the variable to be nullable, and Iend up with:
- skipping parts of the program when it's null (not an option)
- crashing with a null pointer exception (if set to non-nullable)
- no tasks get assigned to any day, which means no recyclerviews get updated, thus making the app appear to contain no tasks when it does.
- weekday buttons that don't work because there is no
weekdayListfor them to compare against to launch the next activity
My problem doesn't stand in figuring out if it's null or not, it's trying to guarantee that it won't be null.
Sorry for the confusion
Main Activity
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) } } } Viewmodel
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") } }
plannerViewModel.allWeekdays.observe(this, { weekdayList = it })will run sync and you have your property initialized when you use just after this code, you must move your code that uses the observale var to inside the observer