Search code examples
androidkotlinnullpointerexceptionandroid-livedata

Easy Problem: Android Kotlin LiveData observe function causes null pointer exception after I delete the item its trying to observe


I'm using room to manage my local database for a dictionary. I select a word then delete it and it should navigate back to the homepage. Sometimes, however, after deleting the word it causes a null pointer exception trying to access the word I've deleted. I can simply try to catch the exception when it does happen, but I think I'm missing a fundamental understanding of how these lifecycles work and would like a better way to implement this if possible. I appreciate any help here.

this is the code for the delete function in my fragment:

    private fun deleteItem() {
    viewModel.deleteWord(word)
    findNavController().navigateUp()
}

not sure if needed but here is my dao, no repository was used.

@Dao
interface DictionaryDao {
    @Query("SELECT * FROM dictionary") // can alter this to order by word for example
    fun getAll(): Flow<List<DictionaryData>>
    @Query("SELECT * from dictionary WHERE id=:id")
    fun getContents(id: Int): Flow<DictionaryData>
    @Query("SELECT word FROM dictionary")
    fun getWordsList(): Flow<List<String>>

@Insert (onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(word: DictionaryData)
@Update
suspend fun update(word: DictionaryData)
@Delete
suspend fun delete(word: DictionaryData)}

most importantly here is the fragment with the observer that keeps trying to access my word object after i've deleted it.

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    val id = navigationArgs.itemId
    viewModel.retrieveData(id).observe(viewLifecycleOwner) { selectedItem ->
        word = selectedItem
        bind(word)
    }
}

and that selectedItem object is what causes the crash:

E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.app1.personalpocketdictionary, PID: 12648
java.lang.NullPointerException: selectedItem must not be null
    at com.app1.personalpocketdictionary.fragments.ItemDetailFragment.onViewCreated$lambda-6(ItemDetailFragment.kt:114)

Lastly from the viewModel here are the two relevant functions used:

    fun deleteWord(word: DictionaryData){
    viewModelScope.launch {
        dictionaryDao.delete(word)
        Log.d("devNotes", "doa delete successful")
    }
}

and

    fun retrieveData(id: Int) : LiveData<DictionaryData>{
    return dictionaryDao.getContents(id).asLiveData()
}

I thought after deleting the item and even navigating to another fragment and completing its onViewCreated function of the new fragment we navigated to that this observe function wouldn't be used anymore but no, its still alive tryna get that data. but only sometimes! When i delete, half the time it crashes and half the time it works. idk man pls send help.

If its confusing at all please lmk I'd be happy to further clarify


Solution

  • You are observing the result of the query to the database where you get the value of the item with the ID with which you start the fragment. I guess you use that "word" to display the data. But when deleting the object from the DB, the observer remains and then the query to the DB with that ID returns null because that object no longer exists. So "word" is getting that null. And "word" is not declared nullable. When you use navigateUp the fragment is not deleted instantly but goes to the backstack and the crash occurs there. I can think of two ways to fix this:

    1- You declare word as nullable

    var word: Object?
    

    2- You compare the value of the result of the observer

    ...
    selectedItem?.let{
       word = it
       bind(word)
    }
    ...