繁体   English   中英

当我使用 Coroutines 向数据库添加新项目时,Room 数据库如何在 UI 中更新我的 RecyclerView?

[英]How can Room database update my RecyclerView in the UI when I have added a new item to the database using Coroutines?

我正在使用 Android 架构组件来创建一个 todo android 应用程序,该应用程序将包含一个注释列表。 每条笔记都会有一个待办事项列表,类似于 Google Keep Notes 的工作方式。 因此,您可以编辑便笺、更新待办事项的内容、添加更多待办事项,当您完成一个待办事项时,您单击复选框,它就会从待办事项列表中消失并出现在已完成的列表中。 我正在使用 Room 数据库,并使用带有 Hilt 的依赖项注入将我的存储库注入到 viewModel。 我的问题是,当我尝试在便笺中创建新的待办事项时,它会在房间中创建,因为 DAO 在协程中执行该操作,但是即使我的 recyclerview 从房间数据库获取实时数据,我的 recyclerView 也永远不会更新。 有什么我可能会丢失的吗? (我的问题出在 ToDoListViewModel 和 ToDoListFragment 中)

这是我的存储库的链接。 https://bitbucket.org/fco2/chuka-notes/src/main_feature/

我试图调试并找出我做错了什么,或者我正在做的事情是否可能是不可能的,但我相信这是可能的,但我可能只是在我的步骤中遗漏了一些东西。

提前感谢任何愿意查看并提供反馈的人!

以下是一些代码片段: ToDoListFragment.kt

@AndroidEntryPoint
class ToDoListFragment : Fragment() {
    private lateinit var binding: FragmentToDoListBinding
    private val viewModel: ToDoListViewModel by viewModels()
    private val args: ToDoListFragmentArgs by navArgs()
    private lateinit var toDoListAdapter: ToDoListAdapter
    //private lateinit var completedListAdapter: ToDoCompletedListAdapter
    override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View {
        binding = FragmentToDoListBinding.inflate(layoutInflater)
        binding.addToDoItemBtn.supportImageTintList = ContextCompat.getColorStateList(requireContext(), R.color.colorWhite)
        binding.toDoTextTitle.setText(args.noteTitle)
        initializeToDoListRecyclerView()
        binding.addToDoItemBtn.setOnClickListener { addToDoNote() }
        performBackPressedAction()
        binding.lifecycleOwner = viewLifecycleOwner
        return binding.root
    }

    private fun initializeToDoListRecyclerView(){
        val manager = LinearLayoutManager(requireContext())
        toDoListAdapter = ToDoListAdapter(ToDoListClickListener { toDoItem, view ->
            performViewClickAction(toDoItem, view)
        })
        binding.toDoListRecyclerView.apply{
            layoutManager = manager
            adapter = toDoListAdapter
        }

        if(args.noteId == -1L)
            viewModel.addToToDoList()
        viewModel.toDoList().observe(viewLifecycleOwner, { updatedList ->
            toDoListAdapter.differ.submitList(updatedList)
            setProgressBarAndDividerVisibility(updatedList)
            Timber.d("CHUKA - todo _> actual list observer size ${updatedList.size}")
        })
        //viewModel.resetDb()
    }

    private fun setProgressBarAndDividerVisibility(updatedList: List<ToDoItem>) {
        binding.progressBar.visibility = View.GONE
        if (updatedList.isNotEmpty())
            binding.divider.visibility = View.VISIBLE
        else
            binding.divider.visibility = View.GONE
    }

     private fun addToDoNote(){
        if(args.noteId == -1L){
            viewModel.addToToDoList()
        }else{
            saveNoteAndToDoItem()
        }
    }
    
    private fun saveNoteAndToDoItem(){
        val note = Note().apply{
            title = binding.toDoTextTitle.text.toString()
            label = "Android"
            id = args.noteId
        }

        val toDoItem = ToDoItem().apply {
            isCompleted = viewModel.currentIsCompleted.value!!
            message = viewModel.currentTextEdit.value!!
        }
        if( viewModel.currentToDoItemId.value != null && viewModel.currentToDoItemId.value != -1L)
            toDoItem.id = viewModel.currentToDoItemId.value!!
        //not setting noteId here because if you are creating a new note, you need to get the notId after it is created
        viewModel.saveNoteAndToDoItem(note, toDoItem)
    }
}

Note.kt 和 ToDoItem.kt

@Entity(tableName = "note")
data class Note(
    var title: String = "<No Title>",
    var label: String = "",
    var color: String = "",
    @ColumnInfo(name = "last_updated")
    var lastUpdated: Date? = null
) {
    @PrimaryKey(autoGenerate = true)
    var id: Long? = null
}

@Entity(tableName = "to_do_item")
data class ToDoItem(
    @ColumnInfo(name = "note_id")
    var noteId: Long = 0L,
    var message: String = "",
    @ColumnInfo(name = "is_completed")
    var isCompleted: Boolean = false,
    @ColumnInfo(name = "date_completed")
    var dateCompleted: Date? = null
){
    @PrimaryKey(autoGenerate = true)
    var id: Long? = null
}

ToDoListViewModel.kt

class ToDoListViewModel @ViewModelInject constructor(
        @Assisted val savedStateHandle: SavedStateHandle,
        private val repository: NoteAndToDoRepository
) : ViewModel(){

    var noteId : Long? = savedStateHandle.get<Long?>("noteId")!!
    fun toDoList() = repository.getAllToDoItemsFor(noteId!!).asLiveData()
    //val completedList : LiveData<MutableList<ToDoItem>> = repository.getAllCompletedItemsFor(noteId.value!!)

    private val _currentTextEdit = MutableLiveData<String>()
    val currentTextEdit: LiveData<String> = _currentTextEdit

    private val _currentIsCompleted = MutableLiveData<Boolean>()
    val currentIsCompleted : LiveData<Boolean> = _currentIsCompleted

    private val _currentToDoItemId = MutableLiveData<Long>()
    val currentToDoItemId : LiveData<Long> = _currentToDoItemId

    fun setCurrentTextEdit(s: String){
        _currentTextEdit.value = s
    }

    fun setCurrentIsCompleted(t: Boolean){
        _currentIsCompleted.value = t
    }

    fun setCurrentToDoItemId(l: Long){
        _currentToDoItemId.value = l
    }

    private val _savedNote = MutableLiveData<Boolean>()
    val savedNote : LiveData<Boolean> = _savedNote

    init{
        _currentTextEdit.value = ""
        _currentIsCompleted.value = false
    }

    fun setSavedNote(state: Boolean){
        _savedNote.postValue(state)
    }

    fun addToToDoList() {
        viewModelScope.launch {
            //if
            if (noteId == -1L || noteId == null) {
                noteId = repository.upsertNote(Note())
                Timber.d("CHUKA - todo _>  Note is NULL")
            }
            val itemToAdd = ToDoItem(noteId!!)
            Timber.d("CHUKA - todo _>  itemToAdd: $itemToAdd")
            _currentToDoItemId.value = repository.upsertToDoItem(itemToAdd) // this is what is supposed to trigger recyclerview update.
        }
    }

    fun saveNoteAndToDoItem(note: Note, toDoItem: ToDoItem) = viewModelScope.launch {
        //Timber.d("CHUKA - todo _>  called saveNoteAndToDoItem , noteId: ${note.id} todoId: ${toDoItem.id}")

        //save note
        noteId = repository.upsertNote(note)
        //save toDoItem  || note that to-do id is still probably null here
        //update noteId for toDoItem
        toDoItem.noteId = noteId!!
        repository.upsertToDoItem(toDoItem)
        Timber.d("CHUKA - todo _>  noteId: $noteId, -- ${note}, -- $toDoItem")
        setCurrentTextEdit("")
        setCurrentToDoItemId(currentToDoItemId.value!!)
    }
}

NoteAndToDoItemDao.kt

@Dao
interface NoteAndToDoItemDao {

    //get all notes -
    @Transaction
    @Query("SELECT * FROM note")
    fun getAllNotes(): LiveData<List<NoteWithToDoItem>>

    //insert note
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun upsertNote(note: Note): Long

    //get all to-do items 
    @Query("SELECT * FROM to_do_item WHERE note_id = :noteId AND is_completed = 0")
    fun getAllToDoItemsFor(noteId: Long): Flow<List<ToDoItem>>

    //upsert to-do item
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun upsertToDoItem(toDoItem: ToDoItem): Long
}

我的应用模块

@Module
@InstallIn(ApplicationComponent::class)
object ChukaNotesAppModule {

    @Provides
    @Singleton
    fun getDatabase(@ApplicationContext context: Context) = Room.databaseBuilder(
            context, ToDoItemsDatabase::class.java, DATABASE_NAME
    ).build()

    //.addMigrations(MIGRATION_1_2)

    @Provides
    @Singleton
    fun getNoteAndToDoItemDao(database: ToDoItemsDatabase) = database.getToDoItemsDao()

    //Room Migrations
    private val MIGRATION_1_2: Migration = object : Migration(1, 2) {
        override fun migrate(database: SupportSQLiteDatabase) {
            // Since we didn't alter the table, there's nothing else to do here.
        }
    }
}

ToDoListAdapter.kt

class ToDoListAdapter(private val clickListener: ToDoListClickListener)
    : RecyclerView.Adapter<ToDoListAdapter.ToDoListViewHolder>() {

    class ToDoListViewHolder(private var binding: ComponentToDoItemBinding) : RecyclerView.ViewHolder(binding.root){
        fun bind(toDoItem: ToDoItem, clickListener: ToDoListClickListener) {
            binding.toDoItem = toDoItem
            binding.clickListener = clickListener
            binding.executePendingBindings()
        }

        companion object{
            fun from(parent: ViewGroup): ToDoListViewHolder {
                val inflater = LayoutInflater.from(parent.context)
                val binding = ComponentToDoItemBinding.inflate(inflater, parent, false)
                return ToDoListViewHolder(binding)
            }
        }
    }

    private val differCallback = object: DiffUtil.ItemCallback<ToDoItem>(){
        override fun areItemsTheSame(oldItem: ToDoItem, newItem: ToDoItem): Boolean {
            Timber.d("CHUKA - todo _>  areItemsTheSame")
            return oldItem.message == newItem.message  && oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: ToDoItem, newItem: ToDoItem): Boolean {
            Timber.d("CHUKA - todo _>  areContentsTheSame")
            return oldItem.hashCode() == newItem.hashCode()
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ToDoListViewHolder {
        return ToDoListViewHolder.from(parent)
    }

    override fun onBindViewHolder(holder: ToDoListViewHolder, position: Int) {
        val toDoItem = differ.currentList[position]!!
        holder.bind(toDoItem, clickListener)
    }

    val differ = AsyncListDiffer(this, differCallback)

    override fun getItemCount(): Int = differ.currentList.size
}


class ToDoListClickListener(val clickListener: (ToDoItem, View) -> Unit){
    fun onClick(toDoItem: ToDoItem, view: View) = clickListener(toDoItem, view)
}



我能够使用 Kotlin Flow 解决上述问题,然后使用 Flow collect function 在 viewModel 中监听结果。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM