How do I initialize one LiveData object to another? - android

The following class has a method than can load a list of users and store it in a LiveData wrapper:
class UserLoader {
...
val loadedUsersLiveData: MutableLiveData<List<User>> = MutableLiveData()
fun loadUsers() {
...
val userRequest: Call<UserResponse> = userApi.loadUsers()
userRequest.enqueue(object : Callback<UserResponse> {
...
override fun onResponse(call: Call<UserResponse>, response: Response<UserResponse>) {
...
loadedUsersLiveData.value = ... // assigns the list of users returned
}
...
}
}
How would I initialize the LiveData variable in my class below to the value of the one that's fetched in the class above?
class UserTableViewModel : ViewModel() {
// TODO: initialize usersLiveData to UserLoader's loadedUsersLiveData
val usersLiveData: LiveData<List<User>> // ??
fun loadUsers() {
UserLoader().loadUsers()
}
}

class UserTableViewModel : ViewModel() {
private val userLoader = UserLoader()
// TODO: initialize usersLiveData to UserLoader's loadedUsersLiveData
val usersLiveData: LiveData<List<User>> = userLoader.loadedUsersLiveData
fun loadUsers() {
userLoader.loadUsers()
}
}

Don't directly expose mutable properties always expose immutable properties for reading the values.
on side note: you can make class UserLoader() to object if you want to safe to call it from everywhere or don't have constructor values.
LiveData is an interface but what you can do is initialize it through MutableLiveData or another LiveData of same type which in your case from UserLoader.
You can do:
class UserTableViewModel : ViewModel() {
private val _userLiveData = MutableLiveData<List<User>>()
val usersLiveData: LiveData<List<User>> get() = _userLiveData // observe it in the view..
// don't forget to call this function...
fun updateUserList() {
_userLiveData.value = UserLoader().loadedUsersLiveData
}
fun loadUsers() {
UserLoader().loadUsers()
}
}
or
class UserTableViewModel : ViewModel() {
//observe it in view..
val usersLiveData: LiveData<List<User>> = UserLoader().loadedUsersLiveData
}
fun loadUsers() {
UserLoader().loadUsers()
}

Related

how to pass a parameter to my Word Dao for query using View Model and Live Data?

Update: how to use ViewModelFactory and is it necessary to use this for passing our parameter? What's the benefit? is that going to break live data concept?
I want to send a parameter to my word Dao of my room database for query but in my case, I don't know how to pass that parameter. so let's begin with codes...
WordDao.kt
#Dao
interface WordDao {
#Insert
fun insert(word: Word)
#Update
fun update(word: Word)
#Delete
fun delete(word: Word)
#Query("delete from En_Fa")
fun deleteAllNotes()
#Query("SELECT * FROM En_Fa ORDER BY id ASC")
fun getAllNotes(): LiveData<List<Word>>
#Query("Select * From En_Fa WHERE date == :today ")
fun getTodayWords(today: String): LiveData<List<Word>>
}
WordRepository.kt
class WordRepository(private val wordDao: WordDao, today: String) {
val readAllData: LiveData<List<Word>> = wordDao.getAllNotes()
val readToday: LiveData<List<Word>> = wordDao.getTodayWords(today)
fun addWord(word: Word) {
wordDao.insert(word)
}
}
WordViewModel.kt
class WordViewModel(application: Application): AndroidViewModel(application) {
val readAllData2: LiveData<List<Word>>
private val repository: WordRepository
init {
val wordDao = WordDatabase.getInstance(application).wordDao()
repository = WordRepository(wordDao, today)
readAllData2 = repository.readToday
}
fun addWord(word: Word){
viewModelScope.launch(Dispatchers.IO){
repository.addWord(word)
}
}
}
and this is the line of my code that make an object of this wordview model class in my fragment
private val mWordViewModel: WordViewModel by viewModels()
so how to pass my (today) variable from my fragment to my WordViewModel class
I think you are looking for this:
#Dao
interface WordDao {
// ...
#Query("Select * From En_Fa WHERE date == :today ")
fun getTodayWords2(today: String): <List<Word>
}
Then in the repository:
class WordRepository{
// ... ...
var mutableWords: MutableLiveData<List<Word>> = MutableLiveData()
fun getWords(today: String): List<Word> { // WARNING! run this in background thread else it will crash
return wordDao.getTodayWords2(today)
}
fun getWordsAsync(today: String) {
Executors.newSingleThreadExecutor().execute {
val words = getWords(today)
liveWords.postValue(words) // <-- just doing this will trigger the observer and do next thing, such as, updating ui
}
}
}
Then in your viewModel:
class WordViewModel(application: Application): AndroidViewModel(application) {
// ... ...
val liveWords: LiveData<List<Word>> = repository.mutableWords
fun getWordsAsync(today: String) {
repository.getWordsAsync(today)
}
}
Then finally inside your activity / fragment:
fun viewModelDemo() {
mWordViewModel.liveWords.observe(this, Observer{
// todo: update the ui, eg
someTextView.text = it.toString() // <-- here you get the output
})
someButton.setOnClickListener{
// here you give the input
mWordViewModel.getWordsAsync(today) // get `today` from date picker or something
}
}
Edit
So you have a recyclerView which has an adapter. When the dataset changes, you call notifyDataSetChanged. Suppose, the adapter looks like this:
class MyAdapter: RecyclerView.Adapter<ViewHolder> {
private var words: List<Word> = ArrayList() // initially points to an empty list
override fun getCount() { return words.size }
// ... ... other methods
// public method:
fun submitList(words1: List<Word>) {
this.words = words1 // so now it points to the submitted list
this.notifyDataSetChanged() // this tells recyclerView to update itself
}
}
Then in your activity or fragment:
private lateinit var myAdapter: MyAdapter
override fun onCreate() { // or onViewCreated if using fragment
// ... ... some codes
this.myAdapter = MyAdapter()
binding.recyclerView.adapter = myAdapter
viewModelDemo()
}
fun viewModelDemo() {
mWordViewModel.liveWords.observe(this, Observer{
// todo: update the ui, eg
myAdapter.submitList(it) // <----- Here you call the submitList method
// <-- here you get the output
})
// --- ---
}
I hope this works.
If i get your question correctly then,
First, you need to make a function that will return the liveData object to observe, then you need to use ViewModelProviders that will provide you the object of your ViewModel in your fragment.
mWordViewModel: WordViewModel = ViewModelProvider(this/getActivity()).get(WordViewModel::class.java)
mWordViewModel.getLiveDataFunction().observe(this/lifeCycleOwner, {
process result/response here
}
Then simply use.
mWordViewModel.addWord(today)

Android ViewModel is leaking

LeakCanary is telling me that one of my ViewModels is leaking but after playing around for 2 days I can't get the leak to go away.
Here is why LeakCanary shows
Here is the Fragment getting the ViewModel
viewModel = ViewModelProvider(this).get(ViewBreederViewModel::class.java).apply {
getStrains(arguments?.getString(BREEDER_ID_KEY, "")!!)
}
Here is the ViewModel
class ViewBreederViewModel(application: Application) : AndroidViewModel(application) {
private val breederRepository = BreederRepository(application)
val strainList = MutableLiveData<List<MinimalStrain>>()
fun getStrains(breederId: String) {
viewModelScope.launch {
breederRepository.getMinimalStrains(breederId).observeForever {
strainList.value = it
}
}
}
}
Here is the BreederRepository:
class BreederRepository(context: Context) {
private val dao: BreederDao
private val breederApi = RetrofitClientInstance.getInstance(context).breederAndStrainIdsApi
init {
val database: Db = Db.getInstance(
context
)!!
dao = database.breederDao()
}
suspend fun getMinimalStrains(breederId: String): LiveData<List<MinimalStrain>> =
withContext(Dispatchers.IO) {
dao.getMinimalStrains(breederId)
}
}
Here is the Db class
#Database(
entities = [Breeder::class, Strain::class],
version = 1,
exportSchema = true)
#TypeConverters(RoomDateConverter::class)
abstract class Db : RoomDatabase() {
abstract fun breederDao(): BreederDao
companion object {
private var instance: Db? = null
#JvmStatic
fun getInstance(context: Context): Db? {
if (instance == null) {
synchronized(Db::class) {
instance = Room.databaseBuilder(
context.applicationContext,
Db::class.java, "seedfinder_db"
)
.build()
}
}
return instance
}
}
}
You're using observeForever, which, as the name suggest, will keep observing forever, even after your ViewModel is cleared. Room does not require using a suspend method for DAO methods that return a LiveData and that is never the right approach in any case - LiveData is already asynchronous.
Instead, you should be transforming your LiveData, using your breederId as the input to your strainList LiveData:
class ViewBreederViewModel(application: Application) : AndroidViewModel(application) {
private val breederRepository = BreederRepository(application)
private val currentBreederId = MutableLiveData<String>()
// Here we use the switchMap method from the lifecycle-livedata-ktx artifact
val strainList: LiveData<String> = currentBreederId.switchMap {
breederId -> breederRepository.getMinimalStrains(breederId)
}
private fun setBreederId(breederId: String) {
currentBreederId.value = breederId
}
}
Where your getMinimalStrains becomes:
fun getMinimalStrains(breederId: String): LiveData<List<MinimalStrain>> =
dao.getMinimalStrains(breederId)
And you use it by setting your breederId in your UI and observing your strainList as before:
viewModel = ViewModelProvider(this).get(ViewBreederViewModel::class.java).apply {
setBreederId(arguments?.getString(BREEDER_ID_KEY, "")!!)
}
viewModel.strainList.observe(viewLifecycleOwner) { strainList ->
// use your updated list
}
If you're using Saved State module for ViewModels (which is the default if you're using the latest stable Fragments / Activity libraries), then you can use SavedStateHandle, which is automatically populated from your Fragment's arguments and skip the setBreederId() entirely:
class ViewBreederViewModel(
application: Application,
savedStateHandle: SavedStateHandle
) : AndroidViewModel(application) {
private val breederRepository = BreederRepository(application)
// Here we use the switchMap method from the lifecycle-livedata-ktx artifact
val strainList: LiveData<String> = savedStateHandle
.getLiveData(BREEDER_ID_KEY) // Automatically populated from arguments
.switchMap {
breederId -> breederRepository.getMinimalStrains(breederId)
}
}
Which means your code can simply become:
viewModel = ViewModelProvider(this).get(ViewBreederViewModel::class.java)
viewModel.strainList.observe(viewLifecycleOwner) { strainList ->
// use your updated list
}
And if you use the fragment-ktx artifact, you can simplify this further to:
// Move this to where you declare viewModel
val viewModel: ViewBreederViewModel by viewModels()
viewModel.strainList.observe(viewLifecycleOwner) { strainList ->
// use your updated list
}

How to use MediatorLiveData with an abstract source

I have an abstract class, with a MediatorLiveData object in it. This object has a number of sources, one of which depends on the childs class, and is abstract in the parent class.
Adding the sources in an init block causes a NullPointerException at runtime, because at the time the init block adds the source, it is still abstract (or so I have been led to believe).
Is there a way to use an abstract LiveData as a source for a MediatorLiveData without having to set that source in a child class? I just want to override val and be done with it, since I definitely will forget to call the addSources() function at some time in the future.
(I am aware that this example is not the most useful way to do this exact thing, but I didn't want to add unneccesary complexity)
Example:
abstract class MyClass: ViewModel(){
private val _myMediator = MediatorLiveData<String>()
protected abstract val mySource: LiveData<String>
val myObservable: LiveData<String>
get() = _myMediator
// This will cause a NullPointerException at runtime
init{
_myMediator.addSource(mySource){ _myMediator.value = it }
}
//This should work, but requires this to be called in child class
protected fun addSources(){
_myMediator.addSource(mySource){ _myMediator.value = it }
}
}
class myChild: MyClass(){
override val mySource = Transformations.map(myRepository.someData) { it.toString() }
// This is where init { addSources() } would be called
}
After reading Stachu's anwser, I decided to go with this, which I didn't test butI think should work:
abstract class MyFixedClass: ViewModel(){
private val _myMediator: MediatorLiveData<String> by lazy{
MediatorLiveData<String>().apply{
addSource(mySource){ this.value = it }
}
}
protected abstract val mySource: LiveData<String>
val myObservable: LiveData<String>
get() = _myMediator
}
class MyChild: MyFixedClass(){
override val mySource = Transformations.map(myRepository.someData) { it.toString() }
}
how about using lazy evaluation, e.g. something like this
abstract class MyClass : ViewModel() {
private val _myMediator = MediatorLiveData<String>()
private val _mySource: LiveData<String> by lazy { mySource() }
protected abstract fun mySource(): LiveData<String>
val myObservable: LiveData<String>
get() = _myMediator
init {
_myMediator.addSource(_mySource) { _myMediator.value = it }
}
}
class myChild : MyClass() {
override fun mySource() = Transformations.map(myRepository.someData) { it.toString() }
}

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

Room not retrieve data when androidx.paging.DataSource.Factory (using LiveData + Kotlin Coroutines)

I have a AmbassadorDAO that has a getAll() : List<Ambassador> that return correctly the list of Ambassadors.
The problem becomes when I refactory my existent code to use DataSource.Factory to paginate my list
Here is the code
Presation Module
Activity
class AmbassadorActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
val viewModel by viewModel<AmbassadorViewModel>()
val adapter = AmbassadorAdapter(this)
list_of_ambassadors.adapter = adapter
viewModel.ambassadors.observe(this, Observer { adapter.submitList(it) })
viewModel.listAmbassadors()
...
}
...
}
Viewmodel
class AmbassadorViewModel(
...,
private val getAllAmbassadorInteractor: GetAllAmbassadorInteractor
) : ViewModel() {
...
// not working
private val _ambassadors = MutableLiveData<PagedList<Ambassador>>()
// it's working
//private val _ambassadors = MutableLiveData<List<Ambassador>>()
...
// not working
val ambassadors : LiveData<PagedList<Ambassador>>
get() = _ambassadors
// it's working
//val ambassadors : LiveData<List<Ambassador>>
// get() = _ambassadors
...
fun listAmbassadors() {
viewModelScope.launch {
try {
...
// not working
// the data not return anything
// the livedata is notified with null
val data = getAllAmbassadorInteractor.exec()
_ambassadors.value = LivePagedListBuilder(data, 20).build().value
// it's working
//_ambassadors.value = getAllAmbassadorInteractor.exec()
} catch (e: Exception) {
e.printStackTrace()
} finally {
...
}
}
}
}
Domain Module
Boundary between PRESENTATION (my usecase interface)
interface GetAllAmbassadorInteractor {
//suspend fun exec() : List<Ambassador>
suspend fun exec() : DataSource.Factory<Int, Ambassador>
}
Usecase implementation
class GetAllAmbassadorInteractorImpl(
private val repository: AmbassadorRepository
) : GetAllAmbassadorInteractor {
override suspend fun exec() = withContext(Dispatchers.IO) { repository.getAll() }
}
Boundary between DATA (my repository interface)
interface AmbassadorRepository {
...
//suspend fun getAll() : List<Ambassador>
suspend fun getAll() : DataSource.Factory<Int, Ambassador>
...
}
Data Module
Repository implementation
class AmbassadorRepositoryImpl(
private val ambassadorDAO: AmbassadorDAO
) : AmbassadorRepository {
...
override suspend fun getAll() = ambassadorDAO.getAll().map { it.toDomain() }
...
}
My DAO
#Dao
interface AmbassadorDAO {
...
#Query("SELECT * FROM ${AmbassadorEntity.TABLE_NAME} ORDER BY name DESC")
fun getAll(): DataSource.Factory<Int, AmbassadorEntity>
//fun getAll(): List<AmbassadorEntity>
...
}
Where am I doign wrong?
I guess your mistake is on this line in AmbassadorViewModel class:
_ambassadors.value = LivePagedListBuilder(data, 20).build().value
Instead of that use:
_ambassadors.value = LivePagedListBuilder(data, 20).build()
Also refer to this post, maybe it will help.
With the support of Kotlin extension (LifecycleScope) we can easily connect LiveData with Coroutine and you don't need to use backing properties like _ambassadors and make it MutableLiveData.
androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha01 or higher.
Like this is a function which is using Coroutine and returning a LiveData
/**
* Get all news rows livedata pageList from DB using Coroutine.
*/
suspend fun getAllNewsLiveData(): LiveData<PagedList<News>> {
return withContext(Dispatchers.IO) {
val data = mDao.getAllNews()
LivePagedListBuilder(data, Constants.PAGINATION_SIZE).build()
}
}
Now in UI class we can simply call this function using lifescope extension
lifecycleScope.launchWhenStarted {
newsViewModel.getNews()?.observe(this#NewsActivity, Observer { pagedNewsList -> pagedNewsList.let { newsAdapter.submitList(pagedNewsList) } })
}

Categories

Resources