How to update Room database in Android - android

In my application I want use MVI for application architecture and I should use Room database.
I have one activity and one fragment!
In fragment I receive data from user and save into database and in activity show this data into recyclerview.
I write below codes and my data successfully save into database!
But for show it into activity, I should exit from application and enter to show data list!
I want without exit from application, update automatically this list.
Dao codes :
#Query("SELECT * FROM my_table")
fun getAllData(): MutableList<Entity>
Repository codes:
class MyRepository #Inject constructor(private val dao: DataDao) {
fun allData() = dao.getAllData()
}
ViewModel codes:
#HiltViewModel
class MyViewModel #Inject constructor(private val repository: MyRepository) : ViewModel() {
val mainIntent = Channel<MainIntent>()
private val _state = MutableStateFlow<MainState>(MainState.Idle)
val state : StateFlow<MainState> get() = _state
init {
handleIntent()
}
private fun handleIntent() {
viewModelScope.launch {
mainIntent.consumeAsFlow().collect{
when(it){
is MainIntent.LoadAllData-> fetchingAllDataList()
}
}
}
}
private fun fetchingAllDataList() {
viewModelScope.launch {
_state.value = MainState.LoadData(repository.allData())
}
}
}
Activity codes :
lifecycleScope.launch {
//Send
viewModel.mainIntent.send(MainIntent.LoadAllData)
//Get
viewModel.state.collect { state ->
when (state) {
is MainState.Idle -> {}
is MainState.LoadData -> {
dataAdapter.setData(state.list)
fataList.apply {
layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
adapter = noteAdapter
}
}
}
}
}
How can I fix this problem?

Several methods below can solve this problem.
Use EventBus. Send an EventBus message after saving data to database in fragment and handle this message in MyViewModel or your Activity to reload data
In your 'dao' interface, change function return type to LiveData: fun getAllData(): LiveData<MutableList<Entity>>. When related data changed in database, Room database automaticly notify changes to Observers. Check this
Use Broadcast like using EventBus
If fragment is contained in the Activity which requires the notification when data changed, use SharedViewModel to notify activity.
class MyFragment: BottomDialogSheetFragment {
var entityChangeListener: IEntityChangeListener? = null
...
// after saving data to database
entityChangeListener?.onChanged()
}
class MyActivity {
fun showDialog() {
val fragment = MyFragment()
fragment.entityChangeListener = object : IEntityChangeListener {
override fun onChanged() {
// change [fetchAllDataList] function to public
myViewModel.fetchAllDataList()
}
}
}
}
interface IEntityChangeListener {
fun onChanged()
}
// using SharedViewModel
class MyFragment: BottomDialogSheetFragment {
var entityViewModel by sharedViewModel<EntityViewModel>
...
// saving entity data
entityViewModel.saveData(entities)
}
class MyActivity {
// shared view model for entity database business
val entityViewModel by viewModels<EntityViewModel>
// viewmodel for other business logic
val viewModel by viewModels<MyViewModel>
}
class EntityViewModel: ViewModel(){
...
private val _state = MutableStateFlow<MainState>(MainState.Idle)
val state : StateFlow<MainState> get() = _state
fun fetchingAllDataList() {
viewModelScope.launch(Dispatchers.IO) {
_state.value = MainState.LoadData(repository.allData())
}
}
fun saveData(entities: List<Entity>) {
dao.save(entities)
fetchingAllDataList()
}
}

Related

ViewModel not initializing or problem design with my viewModel

I've been reading some questions, answers and blogs about MVVM pattern in Android, and I've implemented it in my application.
My application consists of a MainActivity with 3 Tabs. Content of each tab is a fragment.
One of these fragments, is a List of Users stored on Room DB, which is where I've implemented the MVVM (implementing User object, ViewModel, Repository and Adapter with RecycleView).
In this same fragment, I have an "add User" button at the end that leads to a new activity where a formulary is presented to add a new user. In this activity I want to be sure that the full name of user not exists in my DB before saving it.
I was trying to use the same ViewModel to get full UserNames full name, but it seems that ViewModel is never initialized and I dont' know why.
I've read some questions about that viewmodel can't be used in different activities (I use it in MainActivity also in AddUser activity
This is my ViewModel:
class UserViewModel : ViewModel() {
val allUsersLiveData: LiveData<List<User>>
private val repository: UserRepository
init {
Timber.i("Initializing UserViewModel")
repository = UserRepository(UserTrackerApplication.database!!.databaseDao())
allUsersLiveData = repository.getAllUsers()
}
fun getAllUsersFullName(): List<String> {
return allUsersLiveData.value!!.map { it.fullname}
}
And my AddUser activity:
class AddUser : AppCompatActivity() {
private lateinit var userList:List<String>
private lateinit var binding: ActivityAddUserBinding
private val userViewModel: UserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_user)
Timber.i("Add User OnCreate")
binding = ActivityAddUserBinding.inflate(layoutInflater)
setContentView(binding.root)
}
fun addUserClick(v : View){
//someCode
val userName = binding.constraintLayoutAddUser.etUserName!!.text.toString()
if(checkUserExistance(userName)) {
val text: String = String.format(
resources.getString(R.string.repeated_user_name),
userName
Snackbar.make(v, text, Snackbar.LENGTH_LONG).show()
{
else
{
lifecycleScope.launch {
UserTrackerApplication.database!!.databaseDao()
.insertUser(user)
Timber.i("User added!")
}
finish()
}
}
Debugging, I see the log "Initializing UserViewModel" when the fragment of MainActivity is started, but I can't see it when AddUser activity is called. So it seems it's not initializing correctly.
So the questions:
Is this a good approach? I'm making some design mistake?
Why the VM isn't initializing?
EDIT
I forgot to add this function. Calling userViewModel here is where I get the error:
private fun checkUserExistance(userName: String): Boolean {
var result = false
userList = userViewModel.getAllUsersNames()
for (usr in userList)
{
if(usr.uppercase() == userName.uppercase())
{
result = true
break
}
}
return result
}
EDIT 2
I added this on my "onCreate" function and started to work:
userViewModel.allUsersLiveData.observe(this, Observer<List<User>>{
it?.let {
// updates the list.
Timber.i("Updating User Names")
userList =userViewModel.getAllUsersNames()
}
})
if you take a look at by viewModels delegate you will see it's lazy it means it will initialize when it is first time accessed
#MainThread
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: {
defaultViewModelProviderFactory
}
return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}

How to make an emit from another function in a flow after flow.stateIn()?

I get page data from a database, I have a repository that returns a flow.
class RepositoryImpl (private val db: AppDatabase) : Repository {
override fun fetchData (page: Int) = flow {
emit(db.getData(page))
}
}
In the ViewModel, I call the stateIn(), the first page arrives, but then how to request the second page? By calling fetchData(page = 2) I get a new flow, and I need the data to arrive on the old flow.
class ViewModel(private val repository: Repository) : ViewModel() {
val dataFlow = repository.fetchData(page = 1).stateIn(viewModelScope, WhileSubscribed())
}
How to get the second page in dataFlow?
I don't see the reason to use a flow in the repository if you are emitting only one value. I would change it to a suspend function, and in the ViewModel I would update a variable of type MutableStateFlow with the new value. The sample code could look like the following:
class RepositoryImpl (private val db: AppDatabase) : Repository {
override suspend fun fetchData (page: Int): List<Data> {
return db.getData(page)
}
}
class ViewModel(private val repository: Repository) : ViewModel() {
val _dataFlow = MutableStateFlow<List<Data>>(emptyList())
val dataFlow = _dataFlow.asStateFlow()
fun fetchData (page: Int): List<Data> {
viewModelScope.launch {
_dataFlow.value = repository.fetchData(page)
}
}
}

LiveData Value only observes the last added value but using delay makes it working didn't understand why?

In my viewmodel class
class ViewModel(application: Application) : AndroidViewModel(application) {
private val repository: Repository by lazy {
Repository.getInstance(getApplication<BaseApplication>().retrofitFactory)
}
private var _liveData = MutableLiveData<ItemState>()
val liveData: LiveData<ItemState> = _liveData
init {
fetchData()
}
private fun fetchData() {
repository.getLiveData().observeForever(liveDataObserver)
}
override fun onCleared() {
super.onCleared()
repository.getLiveData().removeObserver(liveDataObserver)
}
private val liveDataObserver = Observer<User> {
if (it != null) {
setData(it)
}
}
private fun setData(it: User) =viewModelScope.launch {
val list1 = mutableListOf<something1>()
val list2 = mutableListOf<something2>()
list1.add(it.data)
list2.add(it.data)
}
_liveData.value = ItemState.State1(list1)
delay(1)
_liveData.value = ItemState.State2(list2)
}
The ItemState is a sealed class with two data members
sealed class ItemState {
data class State1(val list: List<something1>) : ItemState()
data class State2(val list: List<something2>) : ItemState()
}
Activity Observer Code
viewModel.liveData.observe(this, Observer {
loadDataIntoUi(it)
})
private fun loadDataIntoUi(data: ItemState) {
when (data) {
is ItemState.State1 -> adaptr1.addItems(data.list)
is ItemState.State2 -> adaptr2.addItems(data.list)
}
Now if i don't use delay in my viewModel here like above the livedata first value that is Office doesn't get observed but it works fine with delay
I have done a lot of research didn't understand why this happening also I have many alternate solutions to this but my question is why delay make's it working

MutableLiveData ArrayList is empty even after postValue() Kotlin

I am now stuck and currently wondering why my mutable arraylist returns null even if it is being updated with postvalue(). I tried to display it using Toast and it displayed [] which I think is null. It had no space in between so it looked like a box. I did toString() it as well in order to show the text. How would I be able to solve this problem?
Here is my Main Activity:
val list = ArrayList<String>()
list.add("text1")
list.add("text2")
val viewmodel = ViewModelProviders.of(this).get(viewmodel::class.java)
viewmodel.Testlist.postValue(list)
ViewModel:
class viewmodel: ViewModel() {
val Testlist: MutableLiveData<ArrayList<String>> = MutableLiveData()
init {
Testlist.value = arrayListOf()
}
}
Fragment:
Top area:
activity?.let {
val viewmodel = ViewModelProviders.of(this).get(viewmodel::class.java)
observeInput(viewmodel)
}
Bottom area:
private fun observeInput(viewmodel: viewmodel) {
viewmodel.Testlist.observe(viewLifecycleOwner, Observer {
it?.let {
Toast.makeText(context, it.toString(), Toast.LENGTH_LONG).show()
}
})
}
You post the value to the LiveData object in the activity's viewmodel, which isn't the same instance as the fragment's viewmodel. Let's take look at the way you instantiate the viewmodel in your fragment:
activity?.let {
// activity can be refered by the implicit parameter `it`
// `this` refers to the current fragment hence it's the owner of the view model
val viewmodel = ViewModelProviders.of(this).get(viewmodel::class.java)
observeInput(viewmodel)
}
To get a viewmodel that is shared between your activity and fragment you have to pass the activity as its owner:
activity?.let { val viewmodel = ViewModelProviders.of(it).get(viewmodel::class.java) }
Probably you can see developer guide example to resolve your problem
https://developer.android.com/topic/libraries/architecture/viewmodel.html#kotlin
// shared viewmodel
class SharedViewModel : ViewModel() {
private val usersList: MutableLiveData<List<String>>()
fun getUsers(): LiveData<List<String>> {
return usersList
}
fun setUsers(users: List<String>) {
usersList.value = users
}
}
// Attach ViewModel In Activity onCreate()
val model = ViewModelProviders.of(this)[SharedViewModel::class.java]
val list = arrayListOf<String>()
list.add("user1")
list.add("user2")
model.setUsers(list)
// Get same ViewModel instance In fragment onCreateView()
model = activity?.run {
ViewModelProviders.of(this)[SharedViewModel::class.java]
} ?: throw Exception("Invalid Activity")
model.getUsers().observe(this, Observer<List<User>>{ users ->
// update UI
})
You can use this :
fun <T : Any?> MutableLiveData<ArrayList<T>>.default(initialValue: ArrayList<T>) = apply { setValue(initialValue) }
and then use this function as below:
viewmodel.Testlist.default(ArrayList())
For me, I have a BaseActivity that other activities extend from it :
class UAppCompatActivity : AppCompatActivity() {
protected fun <T : Any?> MutableLiveData<ArrayList<T>>.default(initialValue: ArrayList<T>) = apply { setValue(initialValue) }
protected fun <T> MutableLiveData<ArrayList<T>>.addItem(item: T) {
val updatedItems = this.value as ArrayList
updatedItems.add(item)
this.value = updatedItems
}
protected fun <T> MutableLiveData<ArrayList<T>>.deleteItem(item: T) {
val updatedItems = this.value as ArrayList
updatedItems.remove(item)
this.value = updatedItems
}
...
have you used the same instance of your view model? or have you defined another view model in the fragment class? The issue could be that you're accessing a different instance of the view model and not the one were the MutableLiveData was updated

How livedata send the data to activity if any changes

Hi I am reading this example of LiveData and Observer https://code.tutsplus.com/tutorials/introduction-to-android-architecture--cms-28749
MainActivityViewModel.kt
class MainActivityViewModel : ViewModel() {
private var notes: MutableLiveData<List<String>>? = null
fun getNotes(): LiveData<List<String>> {
if (notes == null) {
notes = MutableLiveData<List<String>>()
loadNotes()
}
return notes!!
}
private fun loadNotes() {
// do async operation to fetch notes
}
}
MainActivity.kt
class MainActivity : LifecycleActivity(), AnkoLogger {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val viewModel = ViewModelProviders.of(this)
.get(MainActivityViewModel::class.java)
viewModel.getNotes().observe(
this, Observer {
notes -> info("notes: $notes")
}
)
}
}
How LiveData is sending data to MainActivity if there is any changes in notes (new or delete). I see activity is calling viewModel.getNotes() which may not get called once onCreate method finish.
LiveData isn't sending anything to MainActivity, it's "sending" to the Observer passed to the observe method. This Observer has a reference to MainActivity where it was created and can call its methods.

Categories

Resources