Search code examples
androidkotlinandroid-roomkotlinx.coroutines

Receiving data in incorrect order on Querying database


Activity receiving intent

class AddNoteActivity : AppCompatActivity() {

     private lateinit var addViewModel: NoteViewModel
     private lateinit var titleEditText: TextInputEditText
     private lateinit var contentEditText: TextInputEditText

 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_add_note_activty)
        setSupportActionBar(toolbar)

        addViewModel = ViewModelProviders.of(this).get(NoteViewModel::class.java)
        titleEditText = findViewById(R.id.itemTitle)
        contentEditText = findViewById(R.id.itemNote)


           val extra = intent.extras
            if (extra != null) {
                val uuid = extra.getLong("UUID")
                val note: Notes? = addViewModel.getNote(uuid)
                titleEditText.setText(note!!.title)
                contentEditText.setText(note.note)
             }
      }
}

NoteViewModel class

class NoteViewModel(application: Application) : AndroidViewModel(application) {
    companion object {
             private var note: Notes = Notes(0, "", "test title", "test ontent")
            }


    fun getNote(uuid: Long?): Notes {
            val job = async(CommonPool) {
                getNoteAsyncTask(notesDatabase).execute(uuid)
            }
            runBlocking { job.await() }
            return note
    }

    class getNoteAsyncTask(database: NotesDatabase) : AsyncTask<Long, Unit, Unit>() {

        private val db: NotesDatabase = database

        override fun doInBackground(vararg params: Long?) {
            note = db.notesDataDao().getNote(params[0])
        }
    }

}

If I pass an intent to get a Note object from the database with a uuid and set that received data in titleEditText and contentEditText, the data set in the Note was from previous intent invoked when we clicked on the Note item in RecyclerView. On clicking the Note item for the first time, I get the default value which I have set "test title" and "test content".

Aforementioned is the behavior most of the time. Sometimes the data set in titleEditText and contentEditText is of the correct Note object.

Can someone please tell me what I have done wrong? How can I correct my apps behavior?


Solution

  • Unfortunately, there is a big mistake in how you use a view model to provide a data to your view(AddNoteActivity).

    Basically, your view never has a chance to wait for the data to be fetched as it always receives a default value. This happens because the AsyncTask runs on its own thread pool so the coroutine completes immediately and returns a default value.

    You should consider using LiveData to post a new object to your view and refactor your view model.

    So, you need to make a query to the database synchronous and observe changes to a note rather than have a getter for it. Of course, in a real life scenario it might be a good idea to have different kind of states to be able to show a spinner while a user is waiting. But this is another big question. So to keep things simple consider changing your view model to something like that:

    class NoteViewModel(private val database: NotesDatabase) : ViewModel { // you do not need an application class here
        private val _notes = MutableLiveData<Notes>()
        val notes: LiveData<Notes> = _notes
    
        fun loadNotes(uuid: Long) {
            launch(CommonPool) {
                val notes = database.notesDataDao().getNote(uuid)
                _notes.setValue(notes)
            }
        }
    }
    

    Then, you can observe changes to the note field in your activity.

    class AddNoteActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            // ...
    
            val noteViewModel = ViewModelProviders.of(this).get(NoteViewModel::class.java)
            noteViewModel.notes.observe(this, Observer {
                title.text = it.title
                content.text = it.note
            })
        }
    }
    

    Also you need to use a ViewModelProvider.Factory to create your view model and properly inject dependencies into it. Try to avoid having a context there as it makes it much harder to test.