I want to use livedata in an recyclerview. But I only want to observe Livedata with a certain ID. The data gets loaded, but it doesn't update.
So here is the function in m Dao:
#Query("SELECT * FROM zutaten_table NATURAL JOIN table_ref WHERE table_ref.rezeptid = :id")
fun getZutatenforRezept(id:Int): LiveData<List<ZutatenData>>
I use a Viewmodel and a repository like this:
class LiveDataZutatenRepository(private val rezeptDao: AllDao, rezeptID: Int){
val Dao = rezeptDao
val allZutaten = Dao.getZutatenforRezept(rezeptID)
}
class SpecialZutatViewmodel(application: Application): AndroidViewModel(application){
private val repository: JustGetSpecialTypesRepository
private lateinit var repositoryLiveData: LiveDataZutatenRepository
lateinit var ZutatenforRezept : LiveData<List<ZutatenData>>
val Dao : AllDao
init {
Dao = EssenRoomDatabase.getDatabase(application, viewModelScope).allDao()
repository = JustGetSpecialTypesRepository(Dao)
}
suspend fun getRezeptWithZutat(id: Int):RezeptWithZutat{
return repository.getRezeptWithZutatFromID(id)
}
suspend fun getMengen(rezid: Int): List<RefZutatRezept>{
return repository.getMengen(rezid)
}
fun setLiveData(rezeptid: Int){
repositoryLiveData = LiveDataZutatenRepository(Dao, rezeptid )
ZutatenforRezept = repositoryLiveData.allZutaten
}
}
an in my view I use an observer to get the livedata:
val zutatViewmodel = ViewModelProvider(this).get(SpecialZutatViewmodel::class.java)
lifecycleScope.launch {
zutatViewmodel.setLiveData(rezeptid)
}
zutatViewmodel.ZutatenforRezept.observe(this, Observer { zutaten ->
zutaten?.let { adapterzut.setZutaten(it) }
})
Viewholder function:
override fun onBindViewHolder(holder: ZutatenViewHolder, position: Int) {
val current = zutaten[position]
holder.rezepteItemView.text = current.zutname
if(current.bild>=0) {
holder.rezeptePicView.setImageResource(current.bild)
holder.rezeptePicView.drawable.isFilterBitmap = false
}
}
unfortenatly the list doesn't update when the database is changed, but is loaded correctly the first time. What am I doing wrong?
it seems you are using room. So, instead of passing the entire list to the adapter, you can pass a list of all the ids in the database. Then, in the onBindViewholder, you can call the rest of the elements by using the id of the element. the code sample below might give you a better idea -
override fun onBindViewHolder(holder: PassViewHolder, position: Int) {
viewModel.getById(getItem(position)).asLiveData().observe(lifecycleOwner) {
try {
holder.bind(it)
}catch (e:Exception){
Log.e(TAG,"PassData passed = null")
e.printStackTrace()
}
}
}
I had the same problem where the views weren't getting updated but the changes where still being recorded. this method fixed it all.
the below piece of code returns the elements linked to the id as a flow.
viewModel.getById(getItem(position))
And then you covert it to live data and add an observer.
if you want, you can have a look at the project where I implemented this
Related
I've been stuck trying to figure out how to update the list that my RecyclerView is showing.
What I'm trying to do is show a subset of a shown list when a spinner is changed. I have a collection of animals in my database and some have their pet attribute set as true and others have it set as false.
Using Room Database with repositories and viewModels, and what I've been trying to piece together is that it's good to have three different lists that I can tune into, so in m
Repository:
class AnimalRepository(private val animalDao: AnimalDao) {
val allAnimals: Flow<List<Animal>> = animalDao.getAnimalsByCategory()
val pets: Flow<List<Animal>> = animalDao.getAnimalsByPetStatus(true)
val nonPets: Flow<List<Animal>> = animalDao.getAnimalsByPetStatus(false)
#Suppress("RedundantSuspendModifier")
#WorkerThread
suspend fun insert(animal: Animal) {
animalDao.insert(animal)
}
#WorkerThread
suspend fun get(id: Int): Animal {
return animalDao.get(id)
}
#WorkerThread
suspend fun delete(id: Int) {
animalDao.delete(id)
}
}
ViewModel
class AnimalViewModel(private val repository: AnimalRepository) : ViewModel() {
var allAnimals: LiveData<List<Animal>> = repository.allAnimals.asLiveData()
val pets: LiveData<List<Animal>> = repository.pets.asLiveData()
val nonPets: LiveData<List<Animal>> = repository.nonPets.asLiveData()
var result: MutableLiveData<Animal> = MutableLiveData<Animal>()
var mode: VIEW_MODES = VIEW_MODES.BOTH
/*
* Launching a new coroutine to insert the data in a non-blocking way
* */
fun insert(animal: Animal) = viewModelScope.launch {
repository.insert(animal)
}
/*
* Launching a new coroutine to get the data in a non-blocking way
* */
fun get(id: Int) = viewModelScope.launch {
result.value = repository.get(id)
}
fun delete(id: Int) = viewModelScope.launch {
repository.delete(id)
}
}
class AnimalViewModelFactory(private val repository: AnimalRepository) : ViewModelProvider.Factory {
override fun <T: ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(AnimalViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return AnimalViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
In my MainActivity I have it set up where I have an observer on these three lists and depending on which view mode is active (the spinner sets the view mode), that list is fed into the my RecyclerView's ListAdapter's submitList
animalViewModel.allAnimals.observe(this) { animals ->
if (viewMode == VIEW_MODES.BOTH) {
animals.let {
adapter.submitList(it)
// recyclerView.adapter = adapter
}
}
}
animalViewModel.pets.observe(this) { animals ->
if (viewMode == VIEW_MODES.PETS) {
animals.let {
adapter.submitList(it)
// recyclerView.adapter = adapter
}
}
}
animalViewModel.nonPets.observe(this) { animals ->
if (viewMode == VIEW_MODES.NON_PETS) {
animals.let {
adapter.submitList(it)
}
}
}
I am changing the mode with my spinner doing
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
when (position) {
0 -> {
viewMode = VIEW_MODES.BOTH
}
1 -> {
viewMode = VIEW_MODES.PETS
}
2 -> {
viewMode = VIEW_MODES.NON_PETS
}
}
adapter.notifyDataSetChanged()
}
This works fine if add or remove an animal after changing the view mode since the observers fire and the correct one is allowed to populate the adapter, but the notifyDataSetChanged() isn't doing anything and I've been stuck on getting the adapter to update without having to add or remove from the lists
I also tried resetting the adapter in the observer but that didn't do anything either
I am extremely new to kotlin and android programming, and I'm sure that I'm going about this the wrong way, but is there a way force a list refresh?
Update:
I think I may have found a found a solution but I worry that it's hacky. In my ViewModel I am replacing the contents of my allAnimals with the filtered lists
fun showBoth() {
allAnimals = repository.allAnimals.asLiveData()
}
fun showPets() {
allAnimals = repository.pets.asLiveData()
}
fun showNonPets() {
allAnimals = repository.nonPets.asLiveData()
}
and then in my main activity I changed my logic on when handling the spinner change to tell the view model to do its thing and then to remove the observer and slap it back on
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
when (position) {
0 -> {
animalViewModel.showBoth()
}
1 -> {
animalViewModel.showPets()
}
2 -> {
animalViewModel.showNonPets()
}
}
refreshObserver()
}
private fun refreshObserver() {
animalViewModel.allAnimals.removeObservers(this)
animalViewModel.allAnimals.observe(this) { animals ->
animals.let {
adapter.submitList(it)
}
}
}
this seems to work to get the recycler view to update, but is it hacky?
As far as I can see it makes perfect sense that notifyDataSetChanged isn't doing anything, you don't submit any new data before that call. However I think what you're trying to do is to get the adapter to react to a change in viewMode.
If this is the case, I would recommend also having your viewMode as a LiveData object and then expose a single list for your adapter to observe, which changes depending on the viewMode selected.
The Transformations.switchMap(LiveData<X>, Function<X, LiveData<Y>>) method (or its equivalent Kotlin extension function) would probably do most of the work for you here. In summary it maps the values of one LiveData to another. So in your example, you could map your viewMode to one of the allAnimals, pets and nonPets.
Here is a simple pseudocode overview for some clarity:
AnimalViewModel {
val allAnimals: LiveData<List<Animal>>
val pets: LiveData<List<Animal>>
val nonPets: LiveData<List<Animal>>
val modes: MutableLiveData<VIEW_MODES>
val listAnimals = modes.switchMap {
when (it) {
VIEW_MODES.BOTH -> allAnimals
...
}
}
}
fun onItemSelected {
viewModel.onModeChanged(position)
}
viewModel.listAnimals.observe {
adapter.submitList(it)
}
I have Room Entity Class "Symptom" with name of Symptom and id of it.
#Entity(tableName = "symptoms")
data class Symptom(
#PrimaryKey #NonNull val id: Int,
val name: String) {
override fun toString(): String {
return "Symptom $id: $name"
}
}
I'm getting it in the following classses:
SymptomDao
#Dao
interface SymptomDao {
#Query("SELECT * FROM symptoms WHERE id=:id LIMIT 1")
fun getSymptom(id: Int): Symptom
#Query("SELECT * FROM symptoms")
fun getAllSymptoms(): LiveData<List<Symptom>>
}
SymptomRepository
class SymptomRepository(private val symptomDao: SymptomDao) {
fun getSymptom(id: Int) = symptomDao.getSymptom(id)
fun getAllSymptoms() = symptomDao.getAllSymptoms()
}
SymptomsViewModel
class SymptomsViewModel(symptomRepository: SymptomRepository): ViewModel() {
private val symptomsList = symptomRepository.getAllSymptoms()
private val symptomsItemsList: MutableLiveData<List<SymptomItem>> = MutableLiveData()
fun getAllSymptoms(): LiveData<List<Symptom>> {
return symptomsList
}
fun getAllSymptomsItems(): LiveData<List<SymptomItem>> {
return symptomsItemsList
}
}
I have RecyclerView with list of SymptomItem with Checkboxes to remember which Symptoms of a list users chooses:
data class SymptomItem(
val symptom: Symptom,
var checked: Boolean = false)
Question
My question is how can I get LiveData<List<SymptomItem>> by LiveData<List<Symptom>>? I have just started learning MVVM and I can't find a simply answer how to do that. I have already tried to fill this list in various ways, but It loses checked variable every time I rotate my phone. I'll be grateful for any hints.
You'll need to store which items are checked by storing their Ids in a List within the ViewModel. Then you'll have combine the list of your Symptom objects and the list of which items are checked, and generate the list of SymptomItem objects.
I'm going to use Kotlin Flow to achieve this.
#Dao
interface SymptomDao {
#Query("SELECT * FROM symptoms")
fun flowAllSymptoms(): Flow<List<Symptom>>
}
class SymptomRepository(private val symptomDao: SymptomDao) {
fun flowAllSymptoms() = symptomDao.flowAllSymptoms()
}
class SymptomsViewModel(
private val symptomRepository: SymptomRepository
) : ViewModel() {
private val symptomsListFlow = symptomRepository.flowAllSymptoms()
private val symptomsItemsList: MutableLiveData<List<SymptomItem>> = MutableLiveData()
private var checkedIdsFlow = MutableStateFlow(emptyList<Int>())
init {
viewModelScope.launch {
collectSymptomsItems()
}
}
private suspend fun collectSymptomsItems() =
flowSymptomsItems().collect { symptomsItems ->
symptomsItemsList.postValue(symptomsItems)
}
private fun flowSymptomsItems() =
symptomsListFlow
.combine(checkedIdsFlow) { list, checkedIds ->
list.map { SymptomItem(it, checkedIds.contains(it.id)) }
}
fun checkItem(id: Int) {
(checkedIdsFlow.value as MutableList<Int>).add(id)
checkedIdsFlow.value = checkedIdsFlow.value
}
fun uncheckItem(id: Int) {
(checkedIdsFlow.value as MutableList<Int>).remove(id)
checkedIdsFlow.value = checkedIdsFlow.value
}
fun getSymptomsItems(): LiveData<List<SymptomItem>> {
return symptomsItemsList
}
}
In your Fragment, observe getSymptomsItems() and update your adapter data.
The code is not tested, you may have to make small adjustments to make it compile.
Working with Androind and Room for the first time, and i was able to follow a few codelabs and tutorials to achieve inserting and showing a list of my entities, but i cant seem to be able to use my other Repository methods in my ViewModel due to a type mismatch, here is my ViewModel file
class CustomerViewModel(application: Application) : AndroidViewModel(application) {
// The ViewModel maintains a reference to the repository to get data.
private val repository: CustomerRepository
// LiveData gives us updated words when they change.
val allCustomers: LiveData<List<Customer>>
init {
// Gets reference to Dao from db to construct
// the correct repo.
val customerDao = AppDatabase.getInstance(application).customerDao()
repository = CustomerRepository(customerDao)
allCustomers = repository.getCustomers()
}
fun insert(customer: Customer) = viewModelScope.launch {
repository.insert(customer)
}
}
and im trying to add a method like
fun find(id: Int) = viewModelScope.launch {
return repository.getCustomerByLocalId(id)
}
but the ide says there's a type mismatch here? Required: Customer, Found: Job
here is my repository:
class CustomerRepository(private val customerDao: CustomerDao) {
fun getCustomers(): LiveData<List<Customer>> = customerDao.getAlphabetizedCustomers()
suspend fun getCustomerByLocalId(local_Id: Int): Customer =
customerDao.customerByLocalId(local_Id)
suspend fun insert(customer: Customer) {
customerDao.insert(customer)
}
companion object {
// For Singleton instantiation
#Volatile
private var instance: CustomerRepository? = null
fun getInstance(customerDao: CustomerDao) =
instance ?: synchronized(this) {
instance ?: CustomerRepository(customerDao).also { instance = it }
}
}
}
methods in CustomerDao
#Query("SELECT * from customers ORDER BY name ASC")
fun getAlphabetizedCustomers(): LiveData<List<Customer>>
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(customer: Customer)
#Query("SELECT * FROM customers WHERE local_id = :localId")
suspend fun customerByLocalId(localId: Int): Customer
EDIT
I tried #lena-bru 's suggestion but the error is still there, there appears to be 2 different ones, the type mismatch and that there should not be a return. are you supposed to create this method in a different location?
The IDE error
change this:
fun find(id: Int) = viewModelScope.launch {
return repository.getCustomerByLocalId(id)
}
to this:
fun find(id: Int): Customer = viewModelScope.launch {
withContext(Dispatchers.IO){
repository.getCustomerByLocalId(id)
}
}
Your find method as defined above is void, it needs to return type Customer
Also you need to provide a context, and remove the return keyword
I'm rewriting an app that involves retrieving data from a server via REST, saving that to the database on each Android device, and then displaying that data to the user. The data being retrieved from the server has a "since" parameter, so it won't return all data, just data that has changed since the last retrieval.
I have the retrieval from the server working fine, but I'm not sure the best way to save that data to the database, then show it to the user. I'm using Kotlin, Retrofit, Room and LiveData.
The code below is a simplified version of what I'm actually doing, but it gets the point across.
MyData.kt (model)
#Entity(tableName = "MyTable")
data class MyData(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "id")
var id Int? = null,
#SerializedName("message")
#ColumnInfo(name = "message")
var message: String? = null
) {
companion object {
fun fromContentValues(values: ContentValues): MyData {
val data = MyData()
// Do this for id and message
if (values.containsKey("id") {
data.id = values.getAsInteger("id")
}
}
}
}
DataViewModel.kt
class DataViewModel(application: Application) : AndroidViewModel(application) {
private val repository = DataRepository()
fun data(since: Long) =
liveData(Dispatchers.IO) {
val data = repository.getDataFromServer(since)
emit(data)
}
fun saveData(data: List<MyData>) =
liveData(Dispatchers.Default) {
val result = repository.saveDataToDatabase(data)
emit(result)
}
fun data() =
liveData(Dispatchers.IO) {
val data = repository.getDataFromDatabase()
emit(data)
}
}
DataRepository.kt
class DataRepository(application: Application) {
// I won't add how the Retrofit client is created, it's standard
private var client = "MyUrlToGetDataFrom"
private var myDao: MyDao
init {
val myDatabase = MyDatabase.getDatabase(application)
myDao = myDatabase!!.myDao()
}
suspend fun getDataFromServer(since: Long): List<MyData> {
try {
return client.getData(since)
} catch (e: Exception) {
}
}
fun getDataFromDatabase(): List<MyData> = myDao.getAll()
suspend fun insertData(data: List<MyData>) =
myDao.insertData(data)
}
MyDao.kt
#Dao
interface PostsDao {
#Query("SELECT * FROM " + Post.TABLE_NAME + " ORDER BY " + Post.COLUMN_ID + " desc")
suspend fun getAllData(): List<MyData>
#Insert
suspend fun insertData(data: List<MyData>)
}
ListActivity.kt
private lateinit var mDataViewModel: DataViewModel
override fun onCreate(savedInstanceBundle: Bundle?) {
super.onCreate(savedInstanceBundle)
mDataViewModel = ViewModelProvider(this, DataViewModelFactory(contentResolver)).get(DataViewModel::class.java)
getData()
}
private fun getData() {
mDataViewModel.data(getSince()).observe(this, Observer {
saveData(it)
})
}
private fun saveData(data: List<MyData>) {
mDataViewModel.saveData(data)
mDataViewModel.data().observe(this, Observer {
setupRecyclerView(it)
})
}
ListActivity.kt, and possibly the ViewModel and Repository classes where it uses coroutines, are where I'm stuck. getData() retrieves the data from the server without a problem, but when it comes to saving it in the database, then taking that saved data from the database and displaying it to the user I'm unsure of the approach. As I mentioned I'm using Room, but Room will not let you access the database on the main thread.
Remember, I have to save in the database first, then retrieve from the database, so I don't want to call mDataViewModel.data().observe until after it saves to the database.
What is the proper approach to this? I've tried doing CoroutineScope on the mDataViewModel.saveData() then .invokeOnCompletion to do mDataViewModel.data().observe, but it doesn't save to the database. I'm guessing I'm doing my Coroutines incorrectly, but not sure where exactly.
It will also eventually need to delete and update records from the database.
Updated Answer
After reading comments and updated question I figured out that you want to fetch a small list of data and store it to database and show all the data stored in the database. If this is what you want, you can perform the following (omitted DataSouce for brevity) -
In PostDao You can return a LiveData<List<MyData>> instead of List<MyData> and observe that LiveData in the Activity to update the RecyclerView. Just make sure you remove the suspend keyword as room will take care of threading when it returns LiveData.
#Dao
interface PostsDao {
#Query("SELECT * FROM " + Post.TABLE_NAME + " ORDER BY " + Post.COLUMN_ID + " desc")
fun getAllData(): LiveData<List<MyData>>
#Insert
suspend fun insertData(data: List<MyData>)
}
In Repository make 2 functions one for fetching remote data and storing it to the database and the other just returns the LiveData returned by the room. You don't need to make a request to room when you insert the remote data, room will automatically update you as you are observing a LiveData from room.
class DataRepository(private val dao: PostsDao, private val dto: PostDto) {
fun getDataFromDatabase() = dao.getAllData()
suspend fun getDataFromServer(since: Long) = withContext(Dispatchers.IO) {
val data = dto.getRemoteData(since)
saveDataToDatabase(data)
}
private suspend fun saveDataToDatabase(data: List<MyData>) = dao.insertData(data)
}
Your ViewModel should look like,
class DataViewModel(private val repository : DataRepository) : ViewModel() {
val dataList = repository.getDataFromDatabase()
fun data(since: Long) = viewModelScope.launch {
repository.getDataFromServer(since)
}
}
In the Activity make sure you use ListAdapter
private lateinit var mDataViewModel: DataViewModel
private lateinit var mAdapter: ListAdapter
override fun onCreate(savedInstanceBundle: Bundle?) {
...
mDataViewModel.data(getSince())
mDataViewModel.dataList.observe(this, Observer(adapter::submitList))
}
Initial Answer
First of all, I would recommend you to look into Android Architecture Blueprints v2. According to Android Architecture Blueprints v2 following improvements can be made,
DataRepository should be injected rather than instantiating internally according to the Dependency Inversion principle.
You should decouple the functions in the ViewModel. Instead of returning the LiveData, the data() function can update an encapsulated LiveData. For example,
class DataViewModel(private val repository = DataRepository) : ViewModel() {
private val _dataList = MutableLiveData<List<MyData>>()
val dataList : LiveData<List<MyData>> = _dataList
fun data(since: Long) = viewModelScope.launch {
val list = repository.getData(since)
_dataList.value = list
}
...
}
Repository should be responsible for fetching data from remote data source and save it to local data source. You should have two data source i.e. RemoteDataSource and LocalDataSource that should be injected in the Repository. You can also have an abstract DataSource. Let's see how can you improve your repository,
interface DataSource {
suspend fun getData(since: Long) : List<MyData>
suspend fun saveData(list List<MyData>)
suspend fun delete()
}
class RemoteDataSource(dto: PostsDto) : DataSource { ... }
class LocalDataSource(dao: PostsDao) : DataSource { ... }
class DataRepository(private val remoteSource: DataSource, private val localSource: DataSource) {
suspend fun getData(since: Long) : List<MyData> = withContext(Dispatchers.IO) {
val data = remoteSource.getData(since)
localSource.delete()
localSource.save(data)
return#withContext localSource.getData(since)
}
...
}
In your Activity, you just need to observe the dataList: LiveData and submit it's value to ListAdapter.
private lateinit var mDataViewModel: DataViewModel
private lateinit var mAdapter: ListAdapter
override fun onCreate(savedInstanceBundle: Bundle?) {
...
mDataViewModel.data(since)
mDataViewModel.dataList.observe(this, Observer(adapter::submitList))
}
I'm fairly new to Kotlin/Android development, and am trying to figure out the best way to update data in a Room database. After following some tutorials, I currently have an architecture that looks like this:
Room Database with tables and DAOs -> Repository -> ViewModel -> Activity
So the activity has a ViewModel that calls the Repository, which in turn updates the database.
The ViewModel for the activity has a LiveData list of the object (there's also a factory to create the ViewModel, but that's just to allow the bookId to be passed in):
class ViewBookViewModel(application: Application, bookId: Int) : AndroidViewModel(application) {
private val repository: AppRepository
internal val flashCards: LiveData<List<FlashCard>>
init {
val flashCardDao = AppDatabase.getDatabase(application, viewModelScope).flashCardDao()
val bookDao = AppDatabase.getDatabase(application, viewModelScope).bookDao()
repository = AppRepository(flashCardDao, bookDao)
flashCards = flashCardDao.getByBookId(bookId)
}
fun insert(flashCard: FlashCard) = viewModelScope.launch(Dispatchers.IO){
repository.insert(flashCard)
}
fun setIsFavorited(cardUid: Long, favorited: Boolean) = viewModelScope.launch(Dispatchers.IO) {
repository.setIsFavorited(cardUid, favorited)
}
}
//The actual query that gets called eventually
#Query("UPDATE flashcard SET is_favorited = :favorited WHERE uid LIKE :cardUid")
fun setFavorited(cardUid: Long, favorited: Boolean)
And the Activity sets up the viewModel and also creates an observer on the
class ViewBookActivity : AppCompatActivity() {
private lateinit var flashCards: LiveData<List<FlashCard>>
private var layoutManager: RecyclerView.LayoutManager? = null
private lateinit var viewModel: ViewBookViewModel
private var bookId: Int = 0
private lateinit var bookTitle: String
override fun onCreate(savedInstanceState: Bundle?) {
...
bookId = intent.extras["bookId"] as Int
bookTitle = intent.extras["bookTitle"].toString()
layoutManager = LinearLayoutManager(this)
flashCardRecyclerView.layoutManager = layoutManager
viewModel = ViewModelProviders.of(this, ViewBookViewModelFactory(application, bookId as Int)).get(ViewBookViewModel::class.java)
flashCards = viewModel.flashCards
flashCards.observe(this, Observer { flashCards:List<FlashCard> ->
flashCardRecyclerView.adapter = FlashCardRecyclerAdapter(flashCards, viewModel)
})
}
}
Finally, I have a custom RecyclerAdapter, which is where I'm running into trouble. I have it set up so that when the user taps the "favorite" button on the Flash Card, it updates the database. However, this also causes the Activity to "refresh", scrolling to the top. I assume this is because it is observing LiveData, and that data is being changed.
custom RecylcerAdapter with ViewHolder code (stripped not-relevant code):
class FlashCardRecyclerAdapter(val flashCards: List<FlashCard>, val viewModel: ViewBookViewModel) : RecyclerView.Adapter<FlashCardRecyclerAdapter.FlashCardViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FlashCardViewHolder {
val v: View = LayoutInflater
.from(parent.context)
.inflate(R.layout.flash_card, parent, false)
return FlashCardViewHolder(v)
}
override fun onBindViewHolder(holder: FlashCardViewHolder, position: Int) {
val card = flashCards[position]
holder.isFavorited = card.isFavorited
holder.uid = card.uid
holder.modifyFavoriteButtonImage(holder.isFavorited)
}
override fun getItemCount(): Int {
return flashCards.size
}
inner class FlashCardViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
var mFavorited: Button
var frontShowing: Boolean
var isFavorited: Boolean = false
var uid: Long = 0
init {
mFavorited = itemView.findViewById(R.id.favoriteButton)
mFavorited.setOnClickListener { _ ->
isFavorited = !isFavorited
viewModel.setIsFavorited(uid, isFavorited) // Here is the database call
modifyFavoriteButtonImage(isFavorited)
}
}
fun modifyFavoriteButtonImage(isFavorited: Boolean){
// Code removed, just updates the image to be a filled/empty star based on favorited status
}
}
I feel like I am probably doing something wrong, as passing the ViewModel into the recylcer adapter in order to update the DB does not seem correct. Is there a pattern I should be using for this sort of situation, or should I change the code to not be using LiveData? Any help would be greatly appreciated.
flashCards.observe(this, Observer { flashCards:List<FlashCard> ->
flashCardRecyclerView.adapter = FlashCardRecyclerAdapter(flashCards, viewModel)
}
you should not be making a new adapter instance here, instead, assign the values you get from the live data to the existing adapter (adapter.flashCards = flashCards, LiveData value) and call adapter.notifyDataSetChanged, this will tell your adapter that new data came in and it needs to update.
you should not be passing your ViewModel to your adapter (or anything).
you can do something like this instead:
class FlashCardRecyclerAdapter(val flashCards: List<FlashCard>, val callback:(FlashCard) -> Unit)
then, where you declare your adapter, you do this :
val adapter = FlashCardRecyclerAdapter(...) {
viewModel.update(it)
}
and then :
override fun onBindViewHolder(holder: FlashCardViewHolder, position: Int) {
val card = flashCards[position]
holder.isFavorited = card.isFavorited
holder.uid = card.uid
holder.itemView.setOnClickListener {
callback.invoke(card)
}
holder.modifyFavoriteButtonImage(holder.isFavorited)
}
In your repository method, I am not sure what you are doing there but rather than passing in a livedata instance, you should pass in the underlying data of the livedata instance. That way, the observer in the main activity doesn't get triggered everytime you call setIsFavorited(). If you do want to trigger the observer, then you can just call postValue() on the livedata instance. As for the adapter question, I do not know the best practices but I usually create a listener interface so I don't have to pass around my viewmodels everywhere. All of my viewmodels are contained within my fragments and never goes anywhere else. Let me know if this answers your questions.
Also, if you are using viewmodels with recyclerview, consider using list adapters. They are made to work seamlessly with viewmodels. https://developer.android.com/reference/android/support/v7/recyclerview/extensions/ListAdapter
It makes it much simpler to use viewmodels with recyclerview.