Reputation: 119
I'm fairly new to MVVM architecture and I'm a little confused as to why I can't seem to get a Livedata object to update into a recyclerview in a fragment. Using Log in various places throughout the app, I've found that the sqlite table is updating, it's just not displaying the data.
It's a simple note taking app, and I'm only trying to display the titles from the sqlite table at the moment. I can worry about the rest later.
Any help is appreciated, I'm new to room (and sqlite in general) so a lot of the code is based on Room With a View tutorial.
I can provide more code if necessary, I'm just trying to keep it as simple as possible for someone to look at!
TitlesFragment
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class TitlesFragment : Fragment(){
private lateinit var noteViewModel: NoteViewModel
private lateinit var note: Note
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val rootView = inflater.inflate(R.layout.fragment_titles, container, false)
val adapter = this.context?.let { NoteListAdapter(it) }
noteViewModel = ViewModelProvider(this).get(NoteViewModel::class.java)
noteViewModel.allNotes.observe(viewLifecycleOwner, Observer { notes ->
notes?.let { adapter?.setNotes(it) }
})
val recyclerView = rootView.findViewById< RecyclerView>(R.id.titlesrecyclerview) as RecyclerView
recyclerView.layoutManager = LinearLayoutManager(activity)
recyclerView.adapter = this.context?.let { NoteListAdapter(it) }
return rootView
}
fun receiveNote(note: Note) {
this.note = note
noteViewModel.insert(this.note)
}
}
NoteListAdapter
import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.RecyclerView
class NoteListAdapter internal constructor(
context: Context
) : RecyclerView.Adapter<NoteListAdapter.NoteViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var notes = emptyList<Note>()
inner class NoteViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val noteItemView: TextView = itemView.findViewById(R.id.title_box)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteViewHolder {
val itemView = inflater.inflate(R.layout.fragment_titles, parent, false)
return NoteViewHolder(itemView)
}
override fun onBindViewHolder(holder: NoteViewHolder, position: Int) {
val current = notes[position]
holder.noteItemView.text = current.note
}
internal fun setNotes(notes: List<Note>) {
this.notes = notes
notifyDataSetChanged()
}
override fun getItemCount() = notes.size
}
NoteViewModel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class NoteViewModel (application: Application) : AndroidViewModel(application) {
private val repository: NoteRepository
val allNotes: LiveData<List<Note>>
init {
val notesDao = NoteDatabase.getDatabase(application, viewModelScope).noteDao()
repository = NoteRepository(notesDao)
allNotes = repository.allNotes
}
fun insert(note: Note) = viewModelScope.launch(Dispatchers.IO) {
repository.insert(note)
}
}
NoteDatabase
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@Database(entities = arrayOf(Note::class), version = 1, exportSchema = false)
abstract class NoteDatabase : RoomDatabase() {
abstract fun noteDao(): NoteDao
companion object {
@Volatile
private var INSTANCE: NoteDatabase? = null
fun getDatabase(context: Context, scope: CoroutineScope): NoteDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
NoteDatabase::class.java,
"note_database"
).addCallback(NoteDatabaseCallback(scope)).build()
INSTANCE = instance
return instance
}
}
}
private class NoteDatabaseCallback(
private val scope: CoroutineScope) : RoomDatabase.Callback() {
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
INSTANCE?.let { database ->
scope.launch {
populateDatabase(database.noteDao())
}
}
}
suspend fun populateDatabase(noteDao: NoteDao) {
noteDao.deleteAll()
var note = Note("test1", "hello")
noteDao.insert(note)
note = Note("test2", "world")
noteDao.insert(note)
}
}
}
NoteRepository
import androidx.lifecycle.LiveData
class NoteRepository(private val noteDao: NoteDao) {
val allNotes: LiveData<List<Note>> = noteDao.getAllNotes()
suspend fun insert(note: Note) {
noteDao.insert(note)
}
}
NoteDao
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
@Dao
interface NoteDao {
@Query("SELECT * from note_table")
fun getAllNotes(): LiveData<List<Note>>
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(note: Note)
@Query("DELETE FROM note_table")
suspend fun deleteAll()
}
Upvotes: 0
Views: 1288
Reputation: 2844
After looking at onCreateView()
you have:
val adapter = this.context?.let { NoteListAdapter(it) }
and the observer is trying to update accordingly:
noteViewModel.allNotes.observe(viewLifecycleOwner, Observer { notes ->
notes?.let { adapter?.setNotes(it) }
})
however, you are setting a new adapter at:
recyclerView.adapter = this.context?.let { NoteListAdapter(it) }
thus it won't populate the correct adapter so consider switching to the actual adapter
and removing the new assignment.
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val rootView inflater.inflate(R.layout.fragment_titles, container, false)
noteViewModel = ViewModelProvider(this).get(NoteViewModel::class.java)
val adapter = NoteListAdapter(requireContext())
noteViewModel.allNotes.observe(viewLifecycleOwner, Observer { notes ->
notes?.let { adapter.setNotes(it) }
})
val recyclerView = rootView.findViewById< RecyclerView>(R.id.titlesrecyclerview) as RecyclerView
recyclerView.layoutManager = LinearLayoutManager(requireContext())
recyclerView.adapter = adapter
return rootView
}
Upvotes: 1