I'm new to Room and Kotlin, and I'm trying to build a simple Room's database and present it in a RecyclerView that sits in a ViewPager.
The Activity contains a ViewPager object, that ViewPager contains a Fragment, and that Fragment contains a RecyclerView:
Activity --> ViewPager --> Fragment --> RecyclerView
The problems I have is that I get null when I'm trying to receive the database (after an insertion).
code:
#Entity(tableName = "Guests_table")
data class Guest(#NonNull #ColumnInfo (name = "Name") var name: String,
#ColumnInfo (name = "Phone number") var phoneNumber: String,
#ColumnInfo (name = "Coming") var coming: Boolean = false,
#ColumnInfo (name = "Participants") var participants: Int)
{
#PrimaryKey (autoGenerate = true)
var id: Long = 0
init
{
phoneNumber = PhoneNumberUtils.formatNumber(phoneNumber, Locale.getDefault().country)
}
}
dao:
#Dao
interface GuestsDAO
{
#Insert
fun insert(guest: Guest)
#Delete
fun delete(guest: Guest)
#Query ("DELETE FROM Guests_table")
fun deleteAll()
#Query ("SELECT * FROM Guests_table ORDER BY name ASC")
fun getAllGuests(): List<Guest>
}
database:
#Database(entities = [Guest::class], version = 1)
abstract class InviterRoomDatabase: RoomDatabase()
{
abstract fun guestsDao(): GuestsDAO
companion object
{
private var INSTANCE: InviterRoomDatabase ?= null
fun getDatabase(context: Context): InviterRoomDatabase?
{
if (INSTANCE == null)
{
synchronized(InviterRoomDatabase::class)
{
INSTANCE = Room.databaseBuilder(context.applicationContext,InviterRoomDatabase::class.java,"Guests.db").build()
}
}
return INSTANCE
}
fun destroyInstance()
{
INSTANCE = null
}
}
}
activity:
class EventActivity : AppCompatActivity()
{
private lateinit var viewPager: ViewPager
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_event)
viewPager = findViewById(R.id.guests_view_pager)
val pagerAdapter = EventPagerAdapter(supportFragmentManager)
viewPager.adapter = pagerAdapter
}
}
adapter:
class EventPagerAdapter(fragmentManager: FragmentManager): FragmentPagerAdapter(fragmentManager)
{
override fun getCount(): Int
{
return 1
}
override fun getItem(position: Int): Fragment
{
return GuestListFragment.newInstance()
}
}
fragment:
class GuestListFragment : Fragment()
{
private var guestsDataBase: InviterRoomDatabase? = null
private lateinit var dbWorkerThread: DBWorkerThread
companion object
{
fun newInstance(): GuestListFragment
{
val fragment = GuestListFragment()
return fragment
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View?
{
dbWorkerThread = DBWorkerThread("workerThread")
dbWorkerThread.start()
guestsDataBase = InviterRoomDatabase.getDatabase(this.context!!)
insertGuestToDB(Guest("Joe","052352332",false,0))
var rootView = inflater.inflate(R.layout.fragment_guest_list, container, false)
var rv: RecyclerView = rootView.findViewById(R.id.guests_list_recycler_view)
rv.layoutManager = LinearLayoutManager(context)
var d = getAllDataFromDB()
rv.adapter = GuestsRecycleViewAdapter.newInstance(d)
return rootView
}
private fun insertGuestToDB(guest: Guest)
{
val task = Runnable {guestsDataBase?.guestsDao()?.insert(guest)}
dbWorkerThread.postTask(task)
}
private fun getAllDataFromDB(): List<Guest>
{
var data: List<Guest> = emptyList()
val task = Runnable { data = guestsDataBase?.guestsDao()?.getAllGuests()!!}
dbWorkerThread.postTask(task)
return data
}
}
You need to understand the concept of multi-threading to understand why your code does not work. In brief, you are getting an empty list because you are trying to get the list in main thread while your insertion and query running on the worker thread have not completed yet.
I put the comment in the code to explain the order of execution. The number in the comment is the order of completion:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View?
{
// [1] onCreateView starts on main thread.
dbWorkerThread = DBWorkerThread("workerThread")
dbWorkerThread.start()
guestsDataBase = InviterRoomDatabase.getDatabase(this.context!!)
// [2] calls insertGuestToDB on main thread.
insertGuestToDB(Guest("Joe","052352332",false,0))
var rootView = inflater.inflate(R.layout.fragment_guest_list, container, false)
var rv: RecyclerView = rootView.findViewById(R.id.guests_list_recycler_view)
rv.layoutManager = LinearLayoutManager(context)
// [4] calls getAllDataFromDB on main thread.
var d = getAllDataFromDB()
// [6] get "d" on main thread. "d" is an empty list.
rv.adapter = GuestsRecycleViewAdapter.newInstance(d)
return rootView
}
private fun insertGuestToDB(guest: Guest)
{
// [2] insertGuestToDB executed on main thread.
val task = Runnable {
guestsDataBase?.guestsDao()?.insert(guest)
// [7] insertion finished on worker thread.
}
dbWorkerThread.postTask(task)
// [3] insertGuestToDB finishes on main thread.
}
private fun getAllDataFromDB(): List<Guest>
{
// [4] getAllDataFromDB on main thread. "data" points to an empty list
var data: List<Guest> = emptyList()
val task = Runnable {
data = guestsDataBase?.guestsDao()?.getAllGuests()!!
// [8] query finishes on worker thread, but "data" is now lost.
}
dbWorkerThread.postTask(task)
// [5] returns on main thread. "data" still points to an empty list
return data
}
What you need to do is to wait for dbWorkerThread to complete its job before assigning data list to the adapter. Take a look at one of the simplest solution:
var rv: RecyclerView
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View?
{
dbWorkerThread = DBWorkerThread("workerThread")
dbWorkerThread.start()
var rootView = inflater.inflate(R.layout.fragment_guest_list, container, false)
rv= rootView.findViewById(R.id.guests_list_recycler_view)
rv.layoutManager = LinearLayoutManager(context)
guestsDataBase = InviterRoomDatabase.getDatabase(this.context!!)
insertGuestToDbAndUpdateRv(Guest("Joe","052352332",false,0))
return rootView
}
private fun insertGuestToDbAndUpdateRv(guest: Guest)
{
val task = Runnable {
// 1. Insert on a worker thread.
guestsDataBase?.guestsDao()?.insert(guest)
// 2. Query on a worker thread.
var data = guestsDataBase?.guestsDao()?.getAllGuests()!!
this#GuestListFragment.getActivity().runOnUiThread {
// 3. Once they are done, update ui on main thread.
rv.adapter = GuestsRecycleViewAdapter.newInstance(data)
}
}
dbWorkerThread.postTask(task)
}
Be aware that this is definitely not the best way to tackle this problem. This is a very common problem and there are tons of different solutions that are much more readable, flexible and maintainable. It is just that other solutions require deeper understanding and multi-threading is such a broad topic that I cannot explain all of them in a single SO answer. I hope you get the general idea though and encourage you to explore other solutions to this.
Related
I'm making a screen similar to the image.
The data set in advance is taken from the Room DB and the data is set for each tab.
Each tab is a fragment and displays the data in a RecyclerView.
Each tab contains different data, so i set Tab to LiveData in ViewModel and observe it.
Therefore, whenever tabs change, the goal is to get the data for each tab from the database and set it in the RecyclerView.
However, even if I import the data, it is not set in RecyclerView.
I think the data comes in well even when I debug it.
This is not an adapter issue.
What am I missing?
WorkoutList
#Entity
data class WorkoutList(
#PrimaryKey(autoGenerate = true)
val id: Long = 0,
val chest: List<String>,
val back: List<String>,
val leg: List<String>,
val shoulder: List<String>,
val biceps: List<String>,
val triceps: List<String>,
val abs: List<String>
)
ViewModel
class WorkoutListViewModel(application: Application) : AndroidViewModel(application){
private var _part :MutableLiveData<BodyPart> = MutableLiveData()
private var result : List<String> = listOf()
private val workoutDao = WorkoutListDatabase.getDatabase(application).workoutListDao()
private val workoutListRepo = WorkoutListRepository(workoutDao)
val part = _part
fun setList(part : BodyPart) : List<String> {
_part.value = part
viewModelScope.launch(Dispatchers.IO){
result = workoutListRepo.getWorkoutList(part)
}
return result
}
}
Repository
class WorkoutListRepository(private val workoutListDao: WorkoutListDao) {
suspend fun getWorkoutList(part: BodyPart) : List<String> {
val partList = workoutListDao.getWorkoutList()
return when(part) {
is BodyPart.Chest -> partList.chest
is BodyPart.Back -> partList.back
is BodyPart.Leg -> partList.leg
is BodyPart.Shoulder -> partList.shoulder
is BodyPart.Biceps -> partList.biceps
is BodyPart.Triceps -> partList.triceps
is BodyPart.Abs -> partList.abs
}
}
}
Fragment
class WorkoutListTabPageFragment : Fragment() {
private var _binding : FragmentWorkoutListTabPageBinding? = null
private val binding get() = _binding!!
private lateinit var adapter: WorkoutListAdapter
private lateinit var part: BodyPart
private val viewModel: WorkoutListViewModel by viewModels()
companion object {
#JvmStatic
fun newInstance(part: BodyPart) =
WorkoutListTabPageFragment().apply {
arguments = Bundle().apply {
putParcelable("part", part)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let { bundle ->
part = bundle.getParcelable("part") ?: throw NullPointerException("No BodyPart Object")
}
}
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
_binding = FragmentWorkoutListTabPageBinding.inflate(inflater, container, false)
binding.apply {
adapter = WorkoutListAdapter()
rv.adapter = adapter
}
val result = viewModel.setList(part)
// Set data whenever tab changes
viewModel.part.observe(viewLifecycleOwner) { _ ->
// val result = viewModel.setList(part)
adapter.addItems(result)
}
return binding.root
}
} viewModel.part.observe(viewLifecycleOwner) { _ ->
adapter.addItems(result)
}
return binding.root
}
}
The problem you are seeing is that in setList you start an asynchronous coroutine on the IO thread to get the list, but then you don't actually wait for that coroutine to run but just return the empty list immediately.
One way to fix that would be to observe a LiveData object containing the list, instead of observing the part. Then, when the asynchronous task is complete
you can post the retrieved data to that LiveData. That would look like this in the view model
class WorkoutListViewModel(application: Application) : AndroidViewModel(application) {
private val _list = MutableLiveData<List<String>>()
val list: LiveData<List<String>>
get() = _list
// "part" does not need to be a member of the view model
// based on the code you shared, but if you wanted it
// to be you could do it like this, then
// call "viewModel.part = part" in "onCreateView". It does not need
// to be LiveData if it's only ever set from the Fragment directly.
//var part: BodyPart = BodyPart.Chest
// calling getList STARTS the async process, but the function
// does not return anything
fun getList(part: BodyPart) {
viewModelScope.launch(Dispatchers.IO){
val result = workoutListRepo.getWorkoutList(part)
_list.postValue(result)
}
}
}
Then in the fragment onCreateView you observe the list, and when the values change you add them to the adapter. If the values may change several times you may need to clear the adapter before adding the items inside the observer.
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
//...
// Set data whenever new data is posted
viewModel.list.observe(viewLifecycleOwner) { result ->
adapter.addItems(result)
}
// Start the async process of retrieving the list, when retrieved
// it will be posted to the live data and trigger the observer
viewModel.getList(part)
return binding.root
}
Note: The documentation currently recommends only inflating views in onCreateView and doing all other setup and initialization in onViewCreated - I kept it how you had it in your question for consistency.
I am an beginner. My experience when i create an apps, usually i using clean archi and mvvm for the architect, and when using room, it will be use live data also.
But when i create a simple apps don't want to use them, i have trouble with the data which is cannot load directly, please help me. Below is my code
WisataDao.kt
#Dao
interface WisataDao {
#Query("SELECT * from wisata")
fun getAll(): List<WisataEntity>
#Query("SELECT * from wisata WHERE id = :id")
fun getById(id: String): Boolean
#Insert(onConflict = REPLACE)
fun insert(wisata: WisataEntity)
#Delete
fun delete(wisata: WisataEntity)
}
WisataDatabase.kt
#Database(entities = [WisataEntity::class], version = 1)
abstract class WisataDatabase : RoomDatabase() {
abstract fun wisataDao(): WisataDao
companion object {
private var INSTANCE: WisataDatabase? = null
fun getInstance(context: Context): WisataDatabase? {
if (INSTANCE == null) {
synchronized(WisataDatabase::class) {
INSTANCE = Room.databaseBuilder(context.applicationContext,
WisataDatabase::class.java, "wisata.db")
.build()
}
}
return INSTANCE
}
fun destroyInstance() {
INSTANCE = null
}
}
}
FavoriteFrament.kt
class FavoriteFragment : Fragment() {
private lateinit var binding: FragmentFavoriteBinding
private lateinit var database: WisataDatabase
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
binding = FragmentFavoriteBinding.inflate(layoutInflater, container, false)
return binding.root
}
#DelicateCoroutinesApi
#SuppressLint("NotifyDataSetChanged")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
database = WisataDatabase.getInstance(requireContext())!!
binding.rvWisata.layoutManager = LinearLayoutManager(context)
val list = ArrayList<WisataEntity>()
var adapter = HomeAdapter(list)
GlobalScope.launch {
coroutineContext.run {
list.addAll(database.wisataDao().getAll())
adapter = HomeAdapter(list)
adapter.notifyDataSetChanged()
}
}
binding.rvWisata.adapter = adapter
adapter.setOnItemClickCallback(object : HomeAdapter.OnItemClickCallback {
override fun onItemClicked(data: WisataEntity) {
val intent = Intent(context, DetailActivity::class.java)
intent.putExtra("data", data)
startActivity(intent)
}
})
}
}
Please help me guys
The problem is you are initializing the adapter twice but only assigning it once.
//Here 1st initialization
var adapter = HomeAdapter(list)
GlobalScope.launch {
coroutineContext.run {
list.addAll(database.wisataDao().getAll())
//Here 2nd initialization
//but no assignment now the RecyclerView has an adapter
//that doesn't exist anymore because you overwrote it
adapter = HomeAdapter(list)
adapter.notifyDataSetChanged()
}
}
//Here 1 assignment
//You assign the 1st initialized adapter
//that has no data.
binding.rvWisata.adapter = adapter
What you want to do is add a function to the Adapter that accepts a List and pass in the new List to the Adapter and notify the changes.
GlobalScope.launch {
coroutineContext.run {
list.addAll(database.wisataDao().getAll())
//Here 2nd initialization
adapter.setList(list)
adapter.notifyDataSetChanged()
}
}
I am missing some basic coding knowledge here I think, I want to present value to the fragment by assigning the function to the variable in a viewModel. When I call the function directly, I get correct value. When I assign function to variable and pass the variable to the fragment it is always null, why?
View Model
class CartFragmentViewModel : ViewModel() {
private val repository = FirebaseCloud()
private val user = repository.getUserData()
val userCart = user?.switchMap {
repository.getProductsFromCart(it.cart)
}
private fun calculateCartValue(): Long? {
val list = userCart?.value
return list?.map { it.price!! }?.sum()
}
//val cartValue = userCart?.value?.sumOf { it.price!! } <- THIS will be null
val cartValue = calculateCartValue() <- THIS will be null
val cartSize = userCart?.value?.size <- THIS will be null
}
Fragment
class CartFragment : RootFragment(), OnProductClick, View.OnClickListener {
private lateinit var cartViewModel: CartFragmentViewModel
private lateinit var binding: FragmentCartBinding
private val cartAdapter = CartAdapter(this)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_cart,
container,
false
)
setAnimation()
cartViewModel = CartFragmentViewModel()
binding.buttonToCheckout.setOnClickListener(this)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.recyclerCart.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = cartAdapter
}
cartViewModel.userCart?.observe(viewLifecycleOwner, { list ->
cartAdapter.setCartProducts(list)
updateCart()
})
}
override fun onClick(view: View?) {
when (view) {
binding.buttonToCheckout -> {
navigateToCheckout(cartViewModel.cartValue.toString())
cartViewModel.sendProductEvent(
cartAdapter.cartList,
ProductEventType.CHECKOUT
)
}
}
}
override fun onProductClick(product: Product, position: Int) {
cartViewModel.removeFromCart(product)
cartAdapter.removeFromCart(product, position)
updateCart()
}
private fun updateCart() {
binding.textCartTotalValue.text = cartViewModel.cartValue.toString() <- NULL
binding.textCartQuantityValue.text = cartViewModel.cartSize.toString() <- NULL
}
}
Thanks!
It looks like userCart is some sort of observable variable which initially holds a null value and then gets populated with the data from your repository after the network call (or something similar) completes.
The reason that all your variables are null are because you are declaring their value immediately, so by the time those statements get executed, the network call hasn't yet completed and userCart?.value is null. However calling the calculateCartValue() function later on in the code might yield a value if the fetch is complete.
I have a ShopFilterFragmentProductFilter which is inside a ShopFilterFragmentHolder which itself holds a ViewPager2. This ShopFilterFragmentHolder is a DialogFragment which is opened inside my ShopFragment. So ShopFragment -> ShopFilterFragmentHolder (Dialog, ViewPager2) -> ShopFilterFragmentProductFilter. ALL of these Fragments should share the same navgraphscoped viewmodel.
The problem I have is, that when I attach an observer inside my ShopFilterFragmentProductFilter to get my recyclerview list from cloud-firestore, this observer never gets called and therefore I get the error message "No Adapter attached, skipping layout". I know that this is not a problem with how I instantiate and assign the adapter to my recyclerview, because when I set a static list (e.g creating a list inside my ShopFilterFragmentProductFilter) everything works.
Why do I don't get the livedata value? To my mind, there is a problem with the viewmodel creation.
Here is my current approach:
ShopFilterFragmentProductFilter
#AndroidEntryPoint
class ShopFilterFragmentProductFilter : Fragment() {
private var _binding: FragmentShopFilterItemBinding? = null
private val binding: FragmentShopFilterItemBinding get() = _binding!!
private val shopViewModel: ShopViewModel by navGraphViewModels(R.id.nav_shop) { defaultViewModelProviderFactory }
#Inject lateinit var shopFilterItemAdapter: ShopFilterItemAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentShopFilterItemBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
bindObjects()
submitAdapterList()
}
override fun onDestroyView() {
super.onDestroyView()
binding.rvShopFilter.adapter = null
_binding = null
}
private fun bindObjects() {
with(binding) {
adapter = shopFilterItemAdapter
}
}
private fun submitAdapterList() {
shopViewModel.shopProductFilterList.observe(viewLifecycleOwner) {
shopFilterItemAdapter.submitList(it)
shopFilterItemAdapter.notifyDataSetChanged()
toast("SUBMITTED LIST") // this does never get called
}
/* // this works
shopFilterItemAdapter.submitList(
listOf(
ShopFilterItem(0, "ITEM 1"),
ShopFilterItem(0, "ITEM 2"),
ShopFilterItem(0, "ITEM 3"),
ShopFilterItem(0, "ITEM 4"),
ShopFilterItem(0, "ITEM 5"),
)
)
*/
}
}
ViewModel
class ShopViewModel #ViewModelInject constructor(
private val shopRepository: ShopRepository,
private val shopFilterRepository: ShopFilterRepository
) : ViewModel() {
private val query = MutableLiveData(QueryHolder("", ""))
val shopPagingData = query.switchMap { query -> shopRepository.search(query).cachedIn(viewModelScope) }
val shopProductFilterList: LiveData<List<ShopFilterItem>> = liveData { shopFilterRepository.getProductFilterList() }
val shopListFilterList: LiveData<List<ShopFilterItem>> = liveData { shopFilterRepository.getListFilterList() }
fun search(newQuery: QueryHolder) {
this.query.value = newQuery
}
}
ShopFilterRepositoryImpl
class ShopFilterRepositoryImpl #Inject constructor(private val db: FirebaseFirestore) : ShopFilterRepository {
override suspend fun getProductFilterList(): List<ShopFilterItem> = db.collection(FIREBASE_SERVICE_INFO_BASE_PATH)
.document(FIREBASE_SHOP_FILTER_BASE_PATH)
.get()
.await()
.toObject<ShopFilterItemHolder>()!!
.productFilter
override suspend fun getListFilterList(): List<ShopFilterItem> = db.collection(FIREBASE_SERVICE_INFO_BASE_PATH)
.document(FIREBASE_SHOP_FILTER_BASE_PATH)
.get()
.await()
.toObject<ShopFilterItemHolder>()!!
.listFilter
}
Nav_graph
Probably, you should define it as MutableLiveData:
private val shopProductFilterList: MutableLiveData<List<ShopFilterItem>> = MutableLiveData()
And in a method in your viewModel that gets the data through repository, you should post the LiveData value:
fun getProductFilterList() = viewModelScope.launch {
val dataFetched = repository. getProductFilterList()
shopProductFilterList.postValue(dataFetched)
}
I am going through Guide to app architecture and trying to implement MVVM and LiveData in one of my apps. I am using realm and I am using this to create a RealmLiveData as shown below
class RealmLiveData<T : RealmModel>(private val results: RealmResults<T>) : MutableLiveData<RealmResults<T>>() {
private val listener = RealmChangeListener<RealmResults<T>> { results -> value = results }
override fun onActive() {
results.addChangeListener(listener)
}
override fun onInactive() {
results.removeChangeListener(listener)
}
}
This how I am updating the list to recyclerview
var mList:ArrayList<Notes> = ArrayList()
lateinit var historyViewModel: HistoryViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_history, container, false)
mRCview = view.findViewById(R.id.list)
historyViewModel = ViewModelProviders.of(activity!!).get(HistoryViewModel::class.java)
// this is how I observe
historyViewModel.getList().observe(this, Observer{
(mRCview.adapter as MyHistoryRecyclerViewAdapter).setData(it)
})
with(mRCview) {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(mContext)
mList = ArrayList()
adapter = MyHistoryRecyclerViewAdapter(
mContext as OnListFragmentInteractionListener
)
}
return view
}
This is how I get the data in my repository class
class HistoryRepository {
fun getHistory(): RealmLiveData<Notes> {
val realmInstance = Realm.getDefaultInstance()
val realmResults = realmInstance
.where(Notes::class.java)
.findAll()
.sort("lastUpdatedTimeStamp", Sort.DESCENDING)
return realmResults.asLiveData()
}
fun <T:RealmModel> RealmResults<T>.asLiveData() = RealmLiveData(this)
}
EDIT
Here is the ViewModel
class HistoryViewModel: ViewModel() {
val repository = HistoryRepository()
fun getList(): RealmLiveData<Notes> {
return repository.getHistory()
}
}
The issue is that the observer is not getting triggered for the first time. If I update the realmresult, the live data update gets invoked and updates my list. Please let me know how I can fix the issue.
We need to notify the Observer of the existing data. When the first Observer registers to historyViewModel.getList() you are registering the realm callback. At this point we need to trigger a change just to notify this Observer of the existing data.
Something like
class RealmLiveData<T : RealmModel>(private val results: RealmResults<T>) : MutableLiveData<RealmResults<T>>() {
private val listener = RealmChangeListener<RealmResults<T>> { results -> value = results }
override fun onActive() {
results.addChangeListener(listener)
listener.onChange(results) // notify the added Observer of the existing data.
}
override fun onInactive() {
results.removeChangeListener(listener)
}
}