I have a Fragment with a RecyclerView in it. I use a ViewModel to hold the LiveData to show from a Room database and try to update the RecyclerView by observing the data in the ViewModel. But the Observer only ever gets called once when I open the fragment. I update the Room databse from a different Fragment than the Observer is on.
Wheter I add a new Event or delete or update one, the Observer never gets called! How can I get the Observer to be called properly? Where is my mistake?
Fragment
The code in onViewCreated does not work in onCreate, it return null on the line val recyclerview = upcoming_recycler.
You also see at the end of onViewCreated where I open a new fragment, from which the database gets updated. Note that the UpcomingFragment is in a different FragmentLayout than the EventEditFragment!
class UpcomingFragment : Fragment(R.layout.fragment_upcoming) {
private val clubDb by lazy {
ClubDatabase.getClubDatabase(requireContext().applicationContext)
}
private val eventAdapter = EventAdapter(null, this)
private val upcomingViewModel: UpcomingViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val recyclerView = upcoming_recycler
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.setHasFixedSize(true)
upcomingViewModel.eventsToShow.observe(viewLifecycleOwner, Observer { events ->
Log.d(TAG, "Live data changed in upcomingfragment!!!")
eventAdapter.setData(events.toTypedArray())
})
recyclerView.adapter = eventAdapter
// add a new Event
upcoming_fab.setOnClickListener {
parentFragmentManager.beginTransaction()
.replace(R.id.main_fragment_layout_overlay, EventEditFragment())
.addToBackStack(EVENT_EDIT_FRAGMENT)
.commit()
}
// and more stuff...
}
//the rest of the class
}
ViewModel
class UpcomingViewModel(application: Application) : ViewModel() {
val eventsToShow: LiveData<List<Event>>
init {
val roundToDay = SimpleDateFormat("dd.MM.yyy", Locale.GERMAN)
var today = Date()
today = roundToDay.parse(roundToDay.format(today))!!
val tomorrow = Date(today.time + 86400000L)
eventsToShow = ClubDatabase.getClubDatabase(application.applicationContext).clubDao()
.getEventsByClubIdAfterDate(CURRENT_CLUB_ID, tomorrow)
}
}
EventAdapter
class EventAdapter(
private var dataSet: Array<Event>?,
private val onEventItemClickListener: OnEventItemClickListener
) : RecyclerView.Adapter<EventAdapter.EventViewHolder>() {
class EventViewHolder(val view: View) : RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EventViewHolder {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.event_item_layout, parent, false)
return EventViewHolder(view)
}
override fun onBindViewHolder(holder: EventViewHolder, position: Int) {
// show the item & add onEventItemClickListener for updating
}
fun setData(new: Array<Event>) {
this.dataSet = new
this.notifyDataSetChanged()
}
override fun getItemCount(): Int {
return dataSet?.size ?: 0
}
}
Database
#Database(
entities = [Event::class, Member::class, RequiredMembersForEvents::class, AttendedMembersForEvents::class],
version = 9,
exportSchema = false
)
#TypeConverters(Converters::class)
abstract class ClubDatabase : RoomDatabase() {
abstract fun clubDao(): ClubDao
companion object {
#Volatile
private var INSTANCE: ClubDatabase? = null
fun getClubDatabase(context: Context): ClubDatabase {
return INSTANCE ?: synchronized(this) {
val instance = INSTANCE
return if (instance != null) {
instance
} else {
Room.databaseBuilder(
context.applicationContext,
ClubDatabase::class.java,
"club-db"
)
.allowMainThreadQueries()
.fallbackToDestructiveMigration()
.build()
}
}
}
}
}
DAO
#Dao
interface ClubDao {
#Query("SELECT * FROM events WHERE clubId = :clubId AND dateTimeFrom > :date ORDER BY dateTimeFrom ASC")
fun getEventsByClubIdAfterDate(clubId: String, date: Date): LiveData<List<Event>>
// the rest of the DAO
}
Check your database singleton implementation, since variable INSTANCE there - is always null. You should set it at first time when you've got the instance of the class. Otherwise your app has a deal with different instances of your Database class.
Probably that causes a problem, when though some changes were made to database, but LiveData's observer for these changes was not triggered.
Related
It's first time using Room Data while also using MVVM pattern. The aim is that I want my data to appeard on the RecyclerList but it's doesn't shut down nor shows me any error it's just appears empty.
Here is my Database class:
#Database(entities = [Plant::class, Plant_Category::class], version = 1)
abstract class PlantDatabase:RoomDatabase() {
abstract fun plantDao(): PlantOperations
abstract fun plantCategoryDao(): PlantCategoryOperations
companion object {
private var INSTANCE: PlantDatabase? = null
fun getDatabase(context: Context): PlantDatabase {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context.applicationContext,
PlantDatabase::class.java, DB_NAME // contains directory of sqlite database
)
.fallbackToDestructiveMigration()
.build()
}
return INSTANCE!!
}
}
}
My dao class:
#Dao
interface PlantOperations {
#Query("SELECT * FROM Plant")
fun getAll(): Flow<List<Plant>>
#Insert
fun insertPlant( plant: Plant)
#Delete
fun delete(plant:Plant)
#Update
fun updatePlant(plant:Plant)}
This is my repository class:
class PlantRepository(application:Application){
private var allPlants = MutableLiveData<List<Plant>>()
private val plantDAO = PlantDatabase.getDatabase(application).plantDao()
init {
CoroutineScope(Dispatchers.IO).launch {
val plantData = plantDAO.getAll()
plantData.collect{
allPlants.postValue(it)
}
}
}
fun getAllPlants(): MutableLiveData<List<Plant>> {
return allPlants
}
}
My Viewmodel class:
class PlantViewModel(
application: Application
): AndroidViewModel(application) {
private var repository = PlantRepository(application)
private var _allPlants = repository.getAllPlants()
val allPlants: MutableLiveData<List<Plant>>
get() = _allPlants
}
My Recycler in Fragment:
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?, savedInstanceState: Bundle?): View? {
lateinit var photoAdapter: Photo_Adapter
lateinit var plantViewModel: PlantViewModel
val view: View = inflater.inflate(R.layout.fragment_edit__form, container, false)
val fab = view.findViewById(R.id.floatingActionButton) as FloatingActionButton
val recyclerView = view.findViewById(R.id.recyclerView) as RecyclerView
recyclerView.layoutManager = GridLayoutManager(context, 2)
photoAdapter = Photo_Adapter(context)
recyclerView.adapter = photoAdapter
plantViewModel = ViewModelProvider(this).get(PlantViewModel::class.java)
plantViewModel.allPlants.observe(viewLifecycleOwner, androidx.lifecycle.Observer {
photoAdapter.setDataList(it)
})
// photoAdapter.setDataList(dataList)
//Floating button that opens the Form in order to add plant
fab?.setOnClickListener {
val intent = Intent(view.context, Edit_Form::class.java)
startActivity(intent);
}
return view
}
This is my adapter class:
class Photo_Adapter(var context: Context?) : RecyclerView.Adapter<Photo_Adapter.ViewHolder>() {
var dataList = emptyList<Plant>()
internal fun setDataList(dataList: List<Plant>) {
this.dataList = dataList
notifyDataSetChanged()
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// Get the data model based on position
var data = dataList[position]
holder.title.text = data.name
holder.desc.text = data.type.toString()
holder.image.setImageResource(data.image)
holder.relativeLayout.setOnClickListener { view -> //Toast.makeText(view.getContext(),"click on item: "+model.getTitle(),Toast.LENGTH_LONG).show();
val intent = Intent(view.context, PlantDetails::class.java)
intent.putExtra("plant_name", data.name)
intent.putExtra("plant_image",data.image)
intent.putExtra("plant_type", data.type.type)
intent.putExtra("plant_water", data.type.water_time)
intent.putExtra("plant_details", data.type.details)
view.context.startActivity(intent)
}
}
// Provide a direct reference to each of the views with data items
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var image: ImageView
var title: TextView
var desc: TextView
var relativeLayout: CardView
init {
image = itemView.findViewById(R.id.image)
title = itemView.findViewById(R.id.title)
desc = itemView.findViewById(R.id.desc)
relativeLayout = itemView.findViewById<View>(R.id.relativeLayout) as CardView
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Photo_Adapter.ViewHolder {
// Inflate the custom layout
var view = LayoutInflater.from(parent.context).inflate(R.layout.photo_layout, parent, false)
return ViewHolder(view)
}
// total count of items in the list
override fun getItemCount() = dataList.size
}
Perhaps I forgot to add something? In Anyway I will be grateful for your help.
You should not observe data on your repository, your activity/view should observe this data. Take a look at this:
First, add this dependency to your gradle:
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
Then in your repository
class PlantRepository(application:Application){
private val plantDAO = PlantDatabase.getDatabase(application).plantDao()
fun getAllPlants(): Flow<List<Plant>> = plantDAO.getAll()
}
In your view model:
class PlantViewModel(
application: Application
): AndroidViewModel(application) {
private var repository = PlantRepository(application)
val allPlants = repository.getAllPlants()
.flowOn(Dispatchers.IO)
.asLiveData()
}
And your activity is fine, but check your adapter, make sure that you're notifing your adapter that your list has changed.
You can also work (collect) flows on your view, but this depends on you
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
}
I'm learning Room with the sample project RoomWordsSample at https://github.com/googlecodelabs/android-room-with-a-view/tree/kotlin.
The following code are from the project.
In my mind, the LiveDate will update UI automatically when the data changed if it was observed.
But in the file WordListAdapter.kt, I find notifyDataSetChanged() is added to the function setWords(words: List<Word>), it's seems that it must notify UI manually when data changed.
Why do it still need launch notifyDataSetChanged() when I have used LiveData ?
MainActivity.kt
class MainActivity : AppCompatActivity() {
private val newWordActivityRequestCode = 1
private lateinit var wordViewModel: WordViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
val adapter = WordListAdapter(this)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this)
wordViewModel = ViewModelProvider(this).get(WordViewModel::class.java)
wordViewModel.allWords.observe(this, Observer { words ->
words?.let { adapter.setWords(it) }
})
}
}
WordViewModel.kt
class WordViewModel(application: Application) : AndroidViewModel(application) {
private val repository: WordRepository
val allWords: LiveData<List<Word>>
init {
val wordsDao = WordRoomDatabase.getDatabase(application, viewModelScope).wordDao()
repository = WordRepository(wordsDao)
allWords = repository.allWords
}
fun insert(word: Word) = viewModelScope.launch {
repository.insert(word)
}
}
WordListAdapter.kt
class WordListAdapter internal constructor(
context: Context
) : RecyclerView.Adapter<WordListAdapter.WordViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var words = emptyList<Word>() // Cached copy of words
inner class WordViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val wordItemView: TextView = itemView.findViewById(R.id.textView)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordViewHolder {
val itemView = inflater.inflate(R.layout.recyclerview_item, parent, false)
return WordViewHolder(itemView)
}
override fun onBindViewHolder(holder: WordViewHolder, position: Int) {
val current = words[position]
holder.wordItemView.text = current.word
}
internal fun setWords(words: List<Word>) {
this.words = words
notifyDataSetChanged()
}
override fun getItemCount() = words.size
}
Actually, livedata will give you updated data in your activity. But now, it is your activity's job to update the ui. So, whenever live data gives you updated data, you will have to tell the ui to update the data. Hence, notifyDataSetChanged().
notifyDataSetChanged has nothing to do with LiveData, it's part of RecyclerView api.
LiveData - is way of receiving data in lifecycle-aware way, RecyclerView simply displays views.
So I'm trying to build a simple MVVM interface to load trips from the database retrieved by the ViewModel into my TripFragment. However, I keep getting this error saying that my TripViewModel is null:
Attempt to invoke virtual method 'void androidx.lifecycle.LiveData.observe(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Observer)' on a null object reference
I can't seem to figure out why it thinks that it's null. I believe the issue is in TripViewModel and has something to do with how the fact that it inherits from AndroidViewModel and that I'm passing the application's context in the constructor.
class TripFragment : Fragment()
private var tripViewModel: TripViewModel? = null
private var textViewTripName: TextView? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view =
inflater.inflate(R.layout.fragment_trip, container, false)
val recyclerView: RecyclerView = view.recycler_view
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.setHasFixedSize(true)
val adapter = TripAdapter()
recyclerView.adapter = adapter
tripViewModel = ViewModelProvider(this).get(TripViewModel(activity!!.application)::class.java)
// This is the line where it crashes, it never executes past this
tripViewModel!!.getAll().observe(viewLifecycleOwner, Observer<List<Trip>> {
fun onChanged(trips: List<Trip>) {
adapter.setTrips(trips)
Log.d("TripFragment", "Went through observer")
}
})
return view
}
class TripViewModel(application: Application): AndroidViewModel(application)
private var tripRepository: TripRepository = TripRepository(application)
private var allTrips: LiveData<List<Trip>> = getAll()
fun insert(trip: Trip) {
tripRepository.insert(trip)
}
fun update(trip: Trip) {
tripRepository.update(trip)
}
fun delete(trip: Trip) {
tripRepository.delete(trip)
}
fun clear() {
tripRepository.clear()
}
fun getAll(): LiveData<List<Trip>> {
return allTrips
}
class TripRepository(application: Application)
private lateinit var tripDao: TripDao
private lateinit var allTrips: LiveData<List<Trip>>
init {
CoroutineScope(IO).launch {
tripDao = AppDatabase.getDatabase(application.applicationContext).tripDao()
allTrips = tripDao.getAll()
}
}
fun insert(trip: Trip) {
CoroutineScope(IO).launch {
tripDao.insert(trip)
}
}
fun update(trip: Trip) {
CoroutineScope(IO).launch {
tripDao.update(trip)
}
}
fun delete(trip: Trip) {
CoroutineScope(IO).launch {
tripDao.delete(trip)
}
}
fun clear() {
CoroutineScope(IO).launch {
tripDao.clear()
}
}
fun getAll(): LiveData<List<Trip>> {
return allTrips
}
Trip entity
#Entity
data class Trip(var title: String, var startDate: String, var endDate: String?) {
#PrimaryKey(autoGenerate = true)
var tid: Long = 0
}
EDIT: I've printed a bunch of debug logs and pinpointed the error at this line in TripRepository.
init {
CoroutineScope(IO).launch {
// tripDao is never assigned properly,
tripDao = AppDatabase.getDatabase(application.applicationContext).tripDao()
allTrips = tripDao.getAll()
}
}
The line tripDao = AppDatabase.getDatabase(application.applicationContext).tripDao() causes an error which turns tripDao into a null variable. The problem has something to do with how I fetch the database, so I've attached my AppDatabase class below.
#Database(entities = [Trip::class], version = 2, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun tripDao(): TripDao
companion object {
// Singleton prevents multiple instances of database opening at the
// same time.
#Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
Log.d("AppDatabase", "Returning existing database")
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"tripweaver_database"
)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
Log.d("AppDatabase", "Returning new database")
return instance
}
}
}
}
I couldn't find the solution to fixing my database access code so I restructured my whole codebase according to the architecture components tutorial by Google on Codelabs. Surely enough this helped me fix the issues I was having. Link for whoever needs this tutorial in the future: https://codelabs.developers.google.com/codelabs/android-room-with-a-view-kotlin/#13
In your class TripViewModel, you have:
private var allTrips: LiveData<List<Trip>> = getAll()
fun getAll(): LiveData<List<Trip>> {
return allTrips
}
So you have a circular arrangement here. allTrips is assigned an initial value of whatever getAll() returns, but it's calling getAll() before allTrips is assigned. So you found a way to assign a null to a non-nullable!
Seems like maybe you meant to put allTrips = tripRepository.getAll().
I am trying, without success, to solve a problem for days. I would like to update my recyclerView whenever the records of a particular model change in the Database (DB Room). I use ViewModel to handle the model data and the list of records are stored in LiveData.
Database
#Database(entities = arrayOf(Additive::class), version = ElementDatabase.DB_VERSION, exportSchema = false)
abstract class ElementDatabase() : RoomDatabase() {
companion object {
const val DB_NAME : String = "element_db"
const val DB_VERSION : Int = 1
fun get(appContext : Context) : ElementDatabase {
return Room.databaseBuilder(appContext, ElementDatabase::class.java, DB_NAME).build()
}
}
abstract fun additivesModels() : AdditiveDao
}
Model
#Entity
class Additive {
#PrimaryKey #ColumnInfo(name = "id")
var number : String = ""
var dangerousness : Int = 0
var description : String = ""
var names : String = ""
var notes : String = ""
var risks : String = ""
var advice : String = ""
}
Dao
#Dao
interface AdditiveDao {
#Query("SELECT * FROM Additive")
fun getAllAdditives() : LiveData<List<Additive>>
#Query("SELECT * FROM Additive WHERE id = :arg0")
fun getAdditiveById(id : String) : Additive
#Query("DELETE FROM Additive")
fun deleteAll()
#Insert(onConflict = REPLACE)
fun insert(additive: Additive)
#Update
fun update(additive: Additive)
#Delete
fun delete(additive: Additive)
}
ViewModel
class AdditiveViewModel(application: Application) : AndroidViewModel(application) {
private var elementDatabase : ElementDatabase
private val additivesModels : LiveData<List<Additive>>
init {
this.elementDatabase = ElementDatabase.get(appContext = getApplication())
this.additivesModels = this.elementDatabase.additivesModels().getAllAdditives()
}
fun getAdditivesList() : LiveData<List<Additive>> {
return this.additivesModels
}
fun deleteItem(additive : Additive) {
DeleteAsyncTask(this.elementDatabase).execute(additive)
}
private class DeleteAsyncTask internal constructor(private val db: ElementDatabase) : AsyncTask<Additive, Void, Void>() {
override fun doInBackground(vararg params: Additive): Void? {
db.additivesModels().delete(params[0])
return null
}
}
}
Fragment
class AdditivesFragment : LifecycleFragment() {
private var viewModel : AdditiveViewModel? = null
private var adapter : AdditivesAdapter? = null
companion object {
fun newInstance() : AdditivesFragment {
val f = AdditivesFragment()
val args = Bundle()
f.arguments = args
return f
}
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater?.inflate(R.layout.fragment_additives, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
this.adapter = AdditivesAdapter(ArrayList<Additive>())
this.additives_list.layoutManager = GridLayoutManager(this.context, 2, GridLayoutManager.VERTICAL, false)
this.additives_list.adapter = this.adapter
this.viewModel = ViewModelProviders.of(this).get(AdditiveViewModel::class.java)
this.viewModel?.getAdditivesList()?.observe(this, Observer<List<Additive>> { additivesList ->
if(additivesList != null) {
this.adapter?.addItems(additivesList)
}
})
super.onActivityCreated(savedInstanceState)
}
}
Now, my question is why is the observer called only once (at the start of the fragment) and then is not called back again? How can I keep the observer constantly listening to the changes in the DB (insert, update, delete) so that my recyclerView instantly can be updated? Thanks a lot for any suggestion.
This is where you made a mistake:
this.viewModel = ViewModelProviders.of(this).get(AdditiveViewModel::class.java)
you are passing this while you are inside the fragment which is pretty disturbing for some people cause it is not a syntax error but logical. You have to pass activity!! instead, it will be like this:
this.viewModel = ViewModelProviders.of(activity!!).get(AdditiveViewModel::class.java)
UPDATE:
Pass viewLifecycleOwner while being inside fragment while observing the Data
mainViewModel.data(viewLifecycleOwner, Observer{})
If you're using fragmentKtx, you can init viewModel this way:
private val viewModel by viewModels<MainViewModel>()
If You've viewModelFactory:
private val viewModel by viewModels<MainViewModel>{
viewModelFactory
}
with this approach you don't need to call:
// you can omit this statement completely
viewModel = ViewModelProviders.of(this).get(AdditiveViewModel::class.java)
You can simply just start observing the data..