Не обновляется recyclerview с cursor
Я написал приложение учебное CRUD приложение при помощи room и mvvm архитектуре, но поставили задачу добавить управление бд ещё при помощи cursor. И проблема возникла что при любых операциях первоначальный список не обновляется, как при room. Как я понимаю в room под капотом реализован потерн наблюдатель. Может кто подскажет или скинет похожий пример.
const val FIRST_COLUMN = "id"
const val SECOND_COLUMN = "name"
const val THIRD_COLUMN = "breed"
const val FOURTH_COLUMN = "age"
const val FIFTH_COLUMN = "created"
const val CATS_DATABASE = "CATS_DATABASE"
const val TABLE_NAME = "cats_table"
private const val DATABASE_VERSION = 1
private const val CREATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS $TABLE_NAME" +
" ($FIRST_COLUMN INTEGER PRIMARY KEY AUTOINCREMENT, $SECOND_COLUMN VARCHAR(50), $THIRD_COLUMN VARCHAR(50), " +
"$FOURTH_COLUMN INTEGER, $FIFTH_COLUMN INTEGER);"
typealias CatListener = (users: List<Cat>) -> Unit
class CursorDataBase(context: Context): SQLiteOpenHelper(
context,
CATS_DATABASE,
null,
DATABASE_VERSION
) {
private val listOfTopics = mutableListOf<Cat>()
private val listeners = mutableSetOf<CatListener>()
override fun onCreate(db: SQLiteDatabase) {
try {
db.execSQL(CREATE_TABLE_SQL)
} catch (exception: SQLException) {
Log.e("onCreate", "Exception while trying to create database", exception)
}
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
Log.d("onUpgrade", "onUpgrade called")
}
private fun getCursorWithTopicsRead(): Cursor {
return readableDatabase.rawQuery("SELECT * FROM $TABLE_NAME", null)
}
fun getAll() = flow<List<Cat>> {
getCursorWithTopicsRead().use { cursor ->
if (cursor.moveToFirst()) {
do {
val id = cursor.getInt(cursor.getColumnIndex(FIRST_COLUMN))
val name = cursor.getString(cursor.getColumnIndex(SECOND_COLUMN))
val breed = cursor.getString(cursor.getColumnIndex(THIRD_COLUMN))
val age = cursor.getInt(cursor.getColumnIndex(FOURTH_COLUMN))
val created = cursor.getLong(cursor.getColumnIndex(FIFTH_COLUMN))
val person = Cat(id, name, breed, age, created)
listOfTopics.add(person)
emit(listOfTopics)
Log.d("getAll", listOfTopics.size.toString())
} while (cursor.moveToNext())
}
cursor.close()
}
}
suspend fun save(cat: Cat) {
val values = ContentValues()
values.put(SECOND_COLUMN, cat.name)
values.put(THIRD_COLUMN, cat.breed)
values.put(FOURTH_COLUMN, cat.age)
values.put(FIFTH_COLUMN, cat.created)
writableDatabase.insert(TABLE_NAME, null, values)
}
suspend fun delete(cat: Cat): Int {
writableDatabase.delete(TABLE_NAME, "id = ${cat.id}", null)
notifyChanges()
return 1
}
suspend fun update(cat: Cat){
val updatedValues = ContentValues()
updatedValues.put(SECOND_COLUMN, cat.name)
updatedValues.put(THIRD_COLUMN, cat.breed)
updatedValues.put(FOURTH_COLUMN, cat.age)
val where = "id = ${cat.id}"
writableDatabase.update(TABLE_NAME, updatedValues, where, null)
notifyChanges()
}
fun addListener(listener: CatListener) {
listener.invoke(listOfTopics)
listeners.add(listener)
}
fun removeListener(listener: CatListener) {
listeners.remove(listener)
}
private fun notifyChanges() {
listeners.forEach { it.invoke(listOfTopics) }
}
}
viewModel
class CatListViewModel(
private val preferencesManager: PreferencesManager,
private val repository: CursorDataBase): ViewModel() {
// private val repository = get()
// val preferencesFlow: Flow<FilterPreferences> = preferencesManager.orderFlow
@ExperimentalCoroutinesApi
private val preferencesFlow: Flow<String> = preferencesManager.orderFlow2
// https://habr.com/ru/post/529944/
private val catEventChannel = Channel<CatEvent>()
val catEvent: Flow<CatEvent> = catEventChannel.receiveAsFlow()
// @ExperimentalCoroutinesApi
// private val catFlow = preferencesFlow.flatMapLatest { filterPreferences ->
// repository.getTasks(filterPreferences)
// }
private val catFlow = repository.getAll()
fun onAddNewCatClick() = viewModelScope.launch {
catEventChannel.send(CatEvent.NavigateToAddCatFragment)
}
fun onTaskSwiped(cat: Cat) = viewModelScope.launch {
repository.delete(cat)
catEventChannel.send(CatEvent.ShowUndoDeleteTaskMessage(cat))
}
fun onUndoDeletedClick(cat: Cat) = viewModelScope.launch {
repository.save(cat)
}
fun onSortOrderSelected(sortOrder: SortOrder) = viewModelScope.launch {
preferencesManager.updateSortOrder(sortOrder)
}
fun onCatSelected(cat: Cat) = viewModelScope.launch {
catEventChannel.send(CatEvent.NavigateToEditTaskScreen(cat))
}
fun onAddEditResult(result: Int) {
when (result) {
ADD_TASK_RESULT_OK -> showTaskSavedConfirmationMessage("кот добавлен")
EDIT_TASK_RESULT_OK -> showTaskSavedConfirmationMessage("кот обнавлён")
}
}
private fun showTaskSavedConfirmationMessage(s: String) = viewModelScope.launch {
catEventChannel.send(CatEvent.ShowTaskSavedConfirmationMessage(s))
}
sealed class CatEvent {
object NavigateToAddCatFragment : CatEvent()
data class NavigateToEditTaskScreen(val cat: Cat) : CatEvent()
data class ShowUndoDeleteTaskMessage(val cat: Cat) : CatEvent()
data class ShowTaskSavedConfirmationMessage(val msg: String) : CatEvent()
}
@ExperimentalCoroutinesApi
val cats: LiveData<List<Cat>> = catFlow.asLiveData()
}
фрагмент
class CatListFragment : Fragment(), CatAdapter.OnItemClickListener {
private val viewModel: CatListViewModel by viewModels { factory() }
private var _binding: CatListFragmentBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = CatListFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val catAdapter = CatAdapter(this)
binding.apply {
recyclerViewCats.apply {
adapter = catAdapter
layoutManager = LinearLayoutManager(requireContext())
setHasFixedSize(true)
SwipeHelper(viewModel::onTaskSwiped).attachToRecyclerView(recyclerViewCats)
}
viewModel.cats.observe(viewLifecycleOwner) {
catAdapter.submitList(it)
}
binding.fabAddCat.setOnClickListener {
viewModel.onAddNewCatClick()
}
}
// https://habr.com/ru/company/otus/blog/564050/
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.catEvent.collect { event ->
when (event) {
is CatListViewModel.CatEvent.NavigateToAddCatFragment -> {
val action = CatListFragmentDirections.actionCatListFragmentToCatFragment(null, "Новый кот")
findNavController().navigate(action)
}
is CatListViewModel.CatEvent.ShowUndoDeleteTaskMessage -> {
Snackbar.make(requireView(), "Кот удалён", Snackbar.LENGTH_LONG)
.setAction("ОТМЕНИТЬ") {
viewModel.onUndoDeletedClick(event.cat)
}.show()
}
is CatListViewModel.CatEvent.NavigateToEditTaskScreen -> {
val action =
CatListFragmentDirections.actionCatListFragmentToCatFragment(event.cat, "Редактирование")
findNavController().navigate(action)
}
is CatListViewModel.CatEvent.ShowTaskSavedConfirmationMessage -> {
Snackbar.make(requireView(), event.msg, Snackbar.LENGTH_SHORT).show()
}
}
}
}
setFragmentResultListener("add_edit_request") { _, bundle ->
val result = bundle.getInt("add_edit_result")
viewModel.onAddEditResult(result)
}
setHasOptionsMenu(true)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_fragment_list_cat, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_sort_by_name -> {
viewModel.onSortOrderSelected(SortOrder.BY_NAME)
true
}
R.id.action_sort_by_age -> {
viewModel.onSortOrderSelected(SortOrder.BY_AGE)
true
}
R.id.action_sort_by_date -> {
viewModel.onSortOrderSelected(SortOrder.BY_DATE)
true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onItemClick(cat: Cat) {
viewModel.onCatSelected(cat)
}
}