The task is to open an activity with notes attached to this diary when you select a single diary. (one-to-many) This is how entities in the database look like:
@Entity(tableName = "word_table")
data class Word(@ColumnInfo(name = "word") val word: String,
@ColumnInfo(name = "description") val description : String
)
{
@ColumnInfo(name = "id")
@PrimaryKey(autoGenerate = true)
var id : Long = 0
}
@Entity(tableName = "note_table")
data class Note(@ColumnInfo(name = "note_name") val note : String,
@ColumnInfo(name = "text") val text : String,
@ColumnInfo(name = "diaryId") val diaryId : Long
){
@PrimaryKey(autoGenerate = true)
var idNote : Long = 0
}
Using a data class in NoteRepository.kt
data class NotesAndWords (@Embedded val word : Word,
@Relation(parentColumn = "id", entityColumn = "diaryId")
val notes : List<Note>)
And a Query in WordDao.kt
@Transaction
@Query("SELECT * from word_table ")
fun getSomeNotes() : LiveData<List<NotesAndWords>>
I get the data and save it in the NoteRepository class:
class NoteRepository (private val wordDao : WordDao) {
var allNotes : LiveData<List<NotesAndWords>> = wordDao.getSomeNotes()
suspend fun insertNote(note : Note)
{
wordDao.insertNote(note)
}
}
Then via NoteViewModel.kt passing data to NoteActivity.kt:
class NoteViewModel(application: Application) : AndroidViewModel(application) {
private val repository: NoteRepository
val allNotes: LiveData<List<NotesAndWords>>
init {
val wordsDao = WordRoomDatabase.getDatabase(application, viewModelScope).wordDao()
repository = NoteRepository(wordsDao)
allNotes = repository.allNotes
}
fun insertNote(note: Note) = viewModelScope.launch {
repository.insertNote(note)
}
}
(NoteActivity.kt)
class NoteActivity : AppCompatActivity() {
private val newWordActivityRequestCode = 1
private lateinit var noteViewModel: NoteViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_note)
val adapter = NoteListAdapter(this, intent.getLongExtra("tag", -1) ){
val intent = Intent(this, ClickedActivity::class.java)
intent.putExtra("tag", it.note)
startActivity(intent)
}
recyclerview1.adapter = adapter
recyclerview1.layoutManager = LinearLayoutManager(this)
noteViewModel = ViewModelProvider(this).get(NoteViewModel::class.java)
noteViewModel.allNotes.observe(this, Observer {
adapter.setNotes(it)
})
val fab = findViewById<FloatingActionButton>(R.id.fab)
fab.setOnClickListener {
val intent = Intent(this, NewWordActivity::class.java)
startActivityForResult(intent, newWordActivityRequestCode)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == newWordActivityRequestCode && resultCode == Activity.RESULT_OK)
{
data?.getStringArrayListExtra(NewWordActivity.EXTRA_REPLY)?.let {
val note = Note(it[0], it[1], intent.getLongExtra("tag", -1))
noteViewModel.insertNote(note)
}
}
else
{
Toast.makeText(applicationContext, R.string.empty_not_saved,
Toast.LENGTH_LONG).show()
}
}
Then, in the adapter, I use MutableMap to transform the list so that the key is the name id and the value is the notes selected on request (attached to a specific diary)
NoteListAdapter.kt:
class NoteListAdapter internal constructor(
context: Context,
val wordId: Long,
private val listener : (Note) -> Unit
) : RecyclerView.Adapter<NoteListAdapter.NoteViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
//private val mContext = context
private var notes = emptyList<NotesAndWords>() // Cached copy of words
private var notesMapped = mutableMapOf<Long, List<Note>>()
inner class NoteViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val noteItemView: TextView = itemView.findViewById(R.id.textView1)
private val noteDescriptionView: TextView = itemView.findViewById(R.id.textView)
fun bindView(note: Note, listener : (Note) -> Unit) {
noteItemView.text = note.diaryId.toString()
noteDescriptionView.text = note.text
itemView.setOnClickListener {
listener(note)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteViewHolder {
val itemView = inflater.inflate(R.layout.recyclerview_layout, parent,
false)
return NoteViewHolder(itemView)
}
override fun onBindViewHolder(holder: NoteViewHolder, position: Int) {
holder.bindView(notesMapped[wordId]!![position], listener)
}
internal fun setNotes(notes: List<NotesAndWords>) {
this.notes = notes
for (i in this.notes) {
notesMapped[i.word.id] = i.notes
}
notifyDataSetChanged()
}
override fun getItemCount() = notesMapped[wordId]!!.size
}
Database:
@Database(entities = [Word::class, Note::class], version = 2, exportSchema = false)
abstract class WordRoomDatabase : RoomDatabase() {
abstract fun wordDao(): WordDao
private class WordDatabaseCallback(private val scope: CoroutineScope) : RoomDatabase.Callback()
{
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
INSTANCE?.let { database ->
scope.launch {
populateDatabase(database.wordDao())
}
}
}
suspend fun populateDatabase(wordDao: WordDao) {
//wordDao.deleteAll()
//wordDao.deleteAllNotes()
}
}
companion object {
@Volatile
private var INSTANCE: WordRoomDatabase? = null
fun getDatabase(context: Context, scope:CoroutineScope): WordRoomDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
val instance = Room.databaseBuilder(context.applicationContext,
WordRoomDatabase::class.java, "word_database")
.addCallback(WordDatabaseCallback(scope))
//.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
return instance
}
}
}
I've created several diaries and one note in each of them, using the buttons to create new diaries and notes. Now, if you select several diaries in turn, then on some attempt to select a diary, a NullPointerException is issued in the adapter, in this line:
override fun getItemCount() = notesMapped[wordId]!!.size
Why is this exception thrown if notesMapped always has the wordId key?
NoteActivity is called from another activity and the diary id is passed to it
This repository on GitHub: https://github.com/Lomank123/RoomDatabase
Edit:
noteViewModel.allNotes.observe(this, Observer {
var getList = emptyList<Note>()
for(i in it)
{
if(i.word.id == wordId)
{
getList = i.notes
break
}
}
adapter.setNotes(getList)
})
I've changed the Observer in NoteActivity and changed setNotes() method in adapter, but now it returns nothing. With for() I get the right notes and give them to adapter.setNotes(). If it doesn't work, how can I get the correct list of notes?
Hi initially the map is empty and the map is returning a null value and you are checking size on a null object. Also as a good practice do not use a map instead use a list of notes only and pass the list directly.