1

I am working on a practice project where I store subscribers in a room database. I use flows in the DAO and get them as stateflows later in the viewmodel. This works fine but the UI somewhy don't react when I update any subscribers (change their name or email). It only updates if I insert or delete. What could be the problem? And is it optimal to use StateFlows that way?

(It works with LiveData but I would like to try it with StateFlows)

I appreciate any kind of help

ViewModel:

class SubscriberViewModel(private val repository: SubscriberRepository) : ViewModel() {

private var isUpdateOrDelete = false
private lateinit var subscriberToUpdateOrDelete: Subscriber

val inputName = MutableStateFlow("")
val inputEmail = MutableStateFlow("")

private val _isDataAvailable = MutableStateFlow(false)
val isDataAvailable : StateFlow<Boolean>
    get() = _isDataAvailable

val saveOrUpdateButtonText = MutableStateFlow("Save")
val deleteOrDeleteAllButtonText = MutableStateFlow("Delete all")

private val _subscribers : MutableStateFlow<List<Subscriber>> = MutableStateFlow(emptyList())
    get() {
        viewModelScope.launch(Dispatchers.IO) {
            repository.subscribers.collect {
                field.value = it
            }
        }
        return field
    }

val subscribers : StateFlow<List<Subscriber>>
    get() = _subscribers



private fun clearInput() {
    inputName.value = ""
    inputEmail.value = ""
    isUpdateOrDelete = false
    saveOrUpdateButtonText.value = "Save"
    deleteOrDeleteAllButtonText.value = "Delete all"
}

fun initUpdateAndDelete(subscriber: Subscriber) {
    inputName.value = subscriber.name
    inputEmail.value = subscriber.email
    isUpdateOrDelete = true
    subscriberToUpdateOrDelete = subscriber
    saveOrUpdateButtonText.value = "Update"
    deleteOrDeleteAllButtonText.value = "Delete"
}

fun saveOrUpdate() {
    if (isUpdateOrDelete) {
        subscriberToUpdateOrDelete.name = inputName.value
        subscriberToUpdateOrDelete.email = inputEmail.value
        update(subscriberToUpdateOrDelete)
    } else {
        val name = inputName.value
        val email = inputEmail.value
        if (name.isNotBlank() && email.isNotBlank()) {
            insert(Subscriber(0, name, email))
        }
        inputName.value = ""
        inputEmail.value = ""
    }
}

fun deleteOrDeleteAll() {
    if (isUpdateOrDelete) {
        delete(subscriberToUpdateOrDelete)
    } else {
        deleteAll()
    }
}

private fun insert(subscriber: Subscriber) = viewModelScope.launch(Dispatchers.IO) {
    repository.insert(subscriber)
    //StateFlow changed, UI received the change
    _isDataAvailable.value = true
}

private fun update(subscriber: Subscriber) = viewModelScope.launch(Dispatchers.IO) {
    repository.update(subscriber)
    //StateFlow changed, UI can't receive the change
    clearInput()
}

private fun delete(subscriber: Subscriber) = viewModelScope.launch(Dispatchers.IO) {
    repository.delete(subscriber)
    //StateFlow changed, UI received the change
    clearInput()
}

private fun deleteAll() = viewModelScope.launch(Dispatchers.IO) {
    repository.deleteAll()
    //StateFlow changed, UI received the change
    _isDataAvailable.value = false
}
}

MainActivity:

class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var viewModel: SubscriberViewModel
private lateinit var viewModelFactory: SubscriberViewModelFactory

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
    val dao = SubscriberDatabase.getInstance(application).subscriberDAO
    viewModelFactory = SubscriberViewModelFactory(SubscriberRepository(dao))
    viewModel = ViewModelProvider(this, viewModelFactory)[SubscriberViewModel::class.java]
    binding.viewModel = viewModel
    binding.lifecycleOwner = this
    initRecycleView()
}

private fun initRecycleView() {
    binding.recyclerViewSubscribers.layoutManager = LinearLayoutManager(
        this@MainActivity,
        LinearLayoutManager.VERTICAL, false
    )
    displaySubscribersList()
}

private fun displaySubscribersList() {
    lifecycleScope.launch {
        repeatOnLifecycle(Lifecycle.State.STARTED) {
            viewModel.subscribers.collect { list -> //This code doesn't run when I update a subscriber
                binding.recyclerViewSubscribers.adapter = SubscriberRecyclerViewAdapter(list) {
                        subscriber: Subscriber -> listItemClicked(subscriber)
                }
            }
        }
    }
}

private fun listItemClicked(subscriber: Subscriber) {
    Toast.makeText(this, "${subscriber.name} is selected", Toast.LENGTH_SHORT).show()
    viewModel.initUpdateAndDelete(subscriber)
}

}
bmartin042503
  • 83
  • 1
  • 7
  • 1
    I’m on mobile and can’t look at your code closely right now, but just one comment. The way you create the subscribers flow is wrong. Every time the property is accessed, a new coroutine is launched to do the same thing, so you will have more and more coroutines collecting copies of the cold DAO flow and redundantly publishing the same thing to the MutableStateFlow. And by using the MutableStateFlow and manually publishing to it, you end up having to create another property with a read-only reference. Just use the `stateIn` function to create your flow in a single line simply and correctly. – Tenfour04 Jan 15 '22 at 05:24
  • 1
    `val subscribers = repository.subscribers.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())` – Tenfour04 Jan 15 '22 at 05:31
  • Is `Subscriber` a `data` class, defined like `data class Subscriber (...)`? – Sergio Jan 15 '22 at 13:04
  • yes it is a data class – bmartin042503 Jan 15 '22 at 13:48
  • `@Entity(tableName = "subscriber_data_table") data class Subscriber( @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "subscriber_id") val id : Int, @ColumnInfo(name = "subscriber_name") var name : String, @ColumnInfo(name = "subscriber_email") var email : String )` – bmartin042503 Jan 15 '22 at 14:02

0 Answers0