Can I use one ViewModel instead of my two view models(AboutViewModel and AboutListItemViewModel)?
Here is my code:
class AboutAdapter(private val clickListener: OnItemClickListener?) : AbstractListAdapter() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AboutItemViewHolder {
val binding: AboutBinding = DataBindingUtil
.inflate(
LayoutInflater.from(parent.context), R.layout.view_item_about,
parent, false
)
return AboutItemViewHolder(binding = binding)
}
override fun onBindHolder(holder: RecyclerView.ViewHolder, position: Int, item: Any) {
(holder as AboutItemViewHolder).bind(item as SettingsItemViewModel, listener = clickListener)
}
}
My AboutFragment:
class AboutFragment : BaseFragment() {
private lateinit var viewModel: AboutViewModel
private lateinit var viewModelFactory: AboutViewModelFactory
private var onItemClickListener: OnItemClickListener = object : OnItemClickListener {
override fun onItemClick(titleName: Int) {
when (titleName) {
R.string.about_terms_service -> {
activity?.addFragment(WebViewFragment.newInstance(TERMS_LINK, getString(R.string.about_terms_service)))
}
R.string.about_open_source_licenses -> activity?.addFragment(LicensesFragment())
}
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewModelFactory = AboutViewModelFactory(requireContext(), onItemClickListener)
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(AboutViewModel::class.java)
return inflater.inflate(R.layout.fragment_base_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
activityComponents?.updateAppbarTitleWithFabAction(getString(R.string.about_feature_title))
setupViews()
}
private fun setupViews() {
try {
val layoutManager = LinearLayoutManager(activity)
recyclerView?.layoutManager = layoutManager
val dividerItemDecoration = DividerItemDecoration(context, layoutManager.orientation)
ContextCompat.getDrawable(context ?: return, R.drawable.shape_full_divider)?.let {
dividerItemDecoration.setDrawable(it)
}
recyclerView?.addItemDecoration(dividerItemDecoration)
recyclerView?.adapter = AboutAdapter(viewModel.onItemClickListener).apply {
data = viewModel.getListItems()
}
} catch (e: Exception) {
e.log()
}
}
}
My AboutItemViewHolder:
class AboutItemViewHolder(
binding: AboutBinding
) : RecyclerView.ViewHolder(binding.root){
private var aboutBinding: AboutBinding? = null
init {
this.aboutBinding = binding
}
fun bind(item: SettingsItemViewModel, listener: OnItemClickListener?) {
aboutBinding?.about = AboutListItemViewModel(item)
aboutBinding?.onItemClickListener = listener
aboutBinding?.executePendingBindings()
}
}
interface OnItemClickListener {
fun onItemClick(titleName: Int)
}
My first ViewModel which I used in adapter AboutListItemViewModel:
class AboutListItemViewModel(item: SettingsItemViewModel) {
val titleName: Int = item.titleId
val subTitleName: String? = item.value
var isVisible: Boolean = item.value != null
}
My second ViewModel which I used in fragment AboutViewModel:
class AboutViewModel(val appContext: Context, val onItemClickListener: OnItemClickListener): ViewModel() {
fun getListItems(): List<SettingsItemViewModel> {
return listOf(
SettingsItemViewModel(
titleId = R.string.about_app_version,
value = AppTools.getAppVersionName(appContext)
),
SettingsItemViewModel(
titleId = R.string.about_copyright,
value = appContext.getString(R.string.about_copyright_description)
),
SettingsItemViewModel(
titleId = R.string.about_terms_service,
itemIsClickable = true
),
SettingsItemViewModel(
titleId = R.string.about_open_source_licenses,
itemIsClickable = true
)
)
}
}
My AboutViewModelFactory:
class AboutViewModelFactory(val appContext: Context, private val onItemClickListener: OnItemClickListener) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(AboutViewModel::class.java)) {
return AboutViewModel(appContext, onItemClickListener) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
SettingsViewModel is simple data class:
data class SettingsItemViewModel(
#StringRes val titleId: Int,
val value: String? = null,
val switchEnabled: Boolean? = null,
val isChecked: Boolean = false,
val itemIsClickable: Boolean = false,
val colorResId: Int = R.color.black
)
ViewModels are for the MVVM pattern, in which a ViewModel is ignorant of any View type classes, such as an OnItemClickListener, so you have an unintended usage where you are passing listeners to ViewModel factories. This also will leak your Fragment or Activity if there is a configuration change.
I don't see you even using the listener from within the ViewModel, so you could completely eliminate this property and the factory altogether. You can inherit from AndroidViewModel and take a single Context argument in your constructor. Then you can use by viewModels() for your ViewModel property.
Without these view callback arguments, there would be nothing to limit you from putting all of this in a single ViewModel implementation.
It's also odd that your view model has a function that returns a list of other ViewModels. Why can these just be simple data classes? A ViewModel is shouldn't be directly instantiated yourself (except by a factory implementation). Otherwise, its lifecycle won't be managed.
Related
First of all, I am Spanish so my english is not good.
I have an app with Kotlin and room, and it has a Recyclerview.
I have 3 tables: coaster, user and favorite.
The user can add coasters to favorite, and this is done succesfully.
The problem that I have is that when the user clicks on the button to add or delete from favorites, the recyclerview resets, it displays again. So it scrolls to the top of the Screen, and also some odd spaces appears after the element.
I also have a function to search, and it happens the same: spaces appears after each element when I am searching.
I have tried everything: notifyItemChanged,
notifyDataSetChanged... it doesnt work! I also tried removing the observer once from the recyclerview...
My main activity:
class CoasterFragment : Fragment() {
lateinit var coasterListener: CoasterListener
lateinit var usuarioCoaster: List\<UsuarioCoaster\>
private lateinit var searchView: SearchView
private lateinit var cAdapter: CoasterRecyclerViewAdapter
private var \_binding: FragmentCoasterBinding? = null
private val binding get() = \_binding!!
private val viewModel: CoastersViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentCoasterBinding.inflate(inflater, container, false)
val root: View = binding.root
/* val livedata = viewModel.coasters()
livedata.observe(viewLifecycleOwner,object: Observer <List<CoasterFavorito>> {
override fun onChanged(it: List<CoasterFavorito>) {
createRecyclerView(it)
livedata.removeObserver(this)
}
})*/
viewModel.coasters().observe(viewLifecycleOwner){createRecyclerView(it)}
coasterListener = CoasterListenerImpl(requireContext(), viewModel)
searchView = binding.search
searchView.clearFocus()
searchView.setOnQueryTextListener(object: SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
if(query != null){
searchDatabase(query)
}
return true
}
override fun onQueryTextChange(query: String?): Boolean {
if(query != null){
searchDatabase(query)
}
return true
}
})
return root
}
fun createRecyclerView(coasters: List<CoasterFavorito>) {
cAdapter =
CoasterRecyclerViewAdapter(
coasters as MutableList<CoasterFavorito>,
coasterListener,
requireContext()
)
val recyclerView = binding.recyclerCoaster
recyclerView.apply {
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
adapter = cAdapter
addItemDecoration(DividerItemDecoration(context, RecyclerView.VERTICAL))
cAdapter.notifyDataSetChanged()
}
}
fun searchDatabase(query: String) {
val searchQuery = "%$query%"
viewModel.searchDatabase(searchQuery).observe(viewLifecycleOwner) { createRecyclerView(it)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
my adapter:
class CoasterRecyclerViewAdapter(val coasters: List<CoasterFavorito>, val listener: CoasterListener,
val context: Context, ) : RecyclerView.Adapter<CoasterRecyclerViewAdapter.ViewHolder>(){
class ViewHolder private constructor(val binding: CoasterItemBinding, private val listener: CoasterListener,
private val context: Context): RecyclerView.ViewHolder(binding.root){
fun relleno(data: CoasterFavorito){
binding.nombre.text = data.coaster.nombre
binding.parque.text = data.coaster.parque
binding.ciudad.text = data.coaster.ciudad
binding.provincia.text = data.coaster.provincia
binding.comunidad.text = data.coaster.comunidadAutonoma
Glide
.with(context)
.load(data.coaster.imagen)
.centerCrop()
.into(binding.imagen)
binding.check.isChecked = data.favorito
binding.check.setOnClickListener{
if (data.favorito) {
listener.delFavorito(data.coaster.id)
binding.check.isChecked = false
} else {
listener.addFavorito(data.coaster.id)
binding.check.isChecked = true
}
}
}
companion object{
fun crearViewHolder(parent: ViewGroup, listener: CoasterListener, adapter: CoasterRecyclerViewAdapter, context: Context):ViewHolder{
val layoutInflater = LayoutInflater.from(parent.context)
val binding = CoasterItemBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding, listener, context )
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder.crearViewHolder(parent, listener, this, context)
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.relleno(coasters[position])
override fun getItemCount() = coasters.size
}
interface CoasterListener {
fun addFavorito(id: Long)
fun delFavorito(id: Long)
}
I have tried everything: notifyItemChanged,
notifyDataSetChanged... it doesnt work! I also tried removing the observer once from the recyclerview...
Your createRecyclerView function should be invoked only once in a whole lifecycle of the Fragment. You should not create any new RecyclerView.Adapter, or set a LayoutManager to the RecyclerView every time your data set changes.
Therefore the Observer used in viewModel.coasters.observe() should only submit a new List to the existing Adapter and call .notifyDataSetChanged(), or other notifying functions.
hello I have an activity that has a navigation bar and fragments inside, in one of my fragments I save data in firebase, I want to get those data and display it in other fragment with recycler view. I watched a tutorial on youtube to do it cuz my teacher didn't teach us anything, the issue is the fragment that was supposed the display the data shows nothing, it's blank but I didn't understand why cuz I'm really new to this, if someone can take a look at my code and help me I would really appreciate it
This is my data class
data class Event(var eventName: String, var eventTime:String?=null)
This is ViewModel
class EventViewModel :ViewModel() {
private val repository : EventRepository
private val _allEvents = MutableLiveData<List<Event>>()
val allEvents : LiveData<List<Event>> = _allEvents
init {
repository= EventRepository().getInstance()
repository.loadEvents(_allEvents)
}
}
This is Repository
class EventRepository {
private val databaseReference: DatabaseReference= FirebaseDatabase.getInstance().getReference("PetEvents")
#Volatile private var INSTANCE : EventRepository ?=null
fun getInstance() : EventRepository{
return INSTANCE ?: synchronized(this) {
val instance = EventRepository()
INSTANCE=instance
instance
}
}
fun loadEvents(eventList : MutableLiveData<List<Event>>){
databaseReference.addValueEventListener(object:ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
try {
val _eventList : List<Event> = snapshot.children.map { dataSnapshot ->
dataSnapshot.getValue(Event::class.java)!!
}
eventList.postValue(_eventList)
}
catch (e: Exception){
}
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
})
}
}
This is Adapter
class EventAdapter : RecyclerView.Adapter<EventAdapter.MyViewHolder>() {
private var eventList =ArrayList<Event>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(
R.layout.event_item_cell,
parent, false
)
return MyViewHolder(itemView)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem = eventList[position]
holder.eventName.text =currentItem.eventName
holder.eventTime.text =currentItem.eventTime
}
override fun getItemCount(): Int {
return eventList.size
}
fun updateEventList(eventList: List<Event>){
this.eventList.clear()
this.eventList.addAll(eventList)
notifyDataSetChanged()
}
class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
val eventName :TextView = itemView.findViewById(R.id.txEventName)
val eventTime :TextView = itemView.findViewById(R.id.txEventTime)
}
}
And this is the fragment I wish to display the data
private lateinit var viewModel : EventViewModel
private lateinit var eventRecyclerView: RecyclerView
lateinit var adapter: EventAdapter
class MainFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_main, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
eventRecyclerView=view.findViewById(R.id.recyclerView)
eventRecyclerView.layoutManager= LinearLayoutManager(context)
eventRecyclerView.setHasFixedSize(true)
adapter = EventAdapter()
eventRecyclerView.adapter= adapter
viewModel = ViewModelProvider(this).get(EventViewModel::class.java)
viewModel.allEvents.observe(viewLifecycleOwner, Observer {
adapter.updateEventList(it)
})
}
}
In my fragment xml I put a recycler view, there's no issue about that
And my item cell is a cardview idk if it has anything to with the problem I'm getting tho
I want to display the sum of a column from my room database in a textview.
After implementing my Query and setting the text of the Textview, it displays the following:
androidx.lifecycle.CoroutineLiveData#76f6edd
Any idea why this happens? Is it because my Query does not work or my way of implementign it is wrong?
It is my first time ever of actually programming. I know probably most of my code is not 100% correct, but I am working on it.
The Query from my Dao:
#Query("SELECT SUM(total)AS sum_total FROM receipt_table")
fun getSum(): Flow<Float>
The Fragment:
#AndroidEntryPoint
class HistoryFragment : Fragment(R.layout.fragment_history) {
private val viewModel: PurchaseViewmodel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_history, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = FragmentHistoryBinding.bind(view)
val exampleAdapter = ExampleAdapter()
binding.apply{
recyclerView.apply{
layoutManager = LinearLayoutManager(requireContext())
adapter = exampleAdapter
}
totalSumTextView.apply {
val totalSum = viewModel.totalSum
text = totalSum.toString()
}
ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val receipt = exampleAdapter.currentList[viewHolder.adapterPosition]
viewModel.onSwipe(receipt)
}
}).attachToRecyclerView(recyclerView)
}
setFragmentResultListener("add_receipt_request"){_,bundle ->
val result = bundle.getInt("add_receipt_request")
viewModel.onAddResult(result)
}
viewModel.receipts.observe(viewLifecycleOwner){
exampleAdapter.submitList(it)
}
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.addTaskEvent.collect { event->
when(event){
is PurchaseViewmodel.TasksEvent.ShowUndoDelete -> {
Snackbar.make(requireView(),"Tasks deleted", Snackbar.LENGTH_LONG)
.setAction("UNDO"){
viewModel.unDoDeleteClick(event.receipts)
}.show()
}
}
}
}
}
}
And the Viewmodel:
#HiltViewModel
class PurchaseViewmodel #Inject constructor(
private val receiptDao: ReceiptDao
): ViewModel() {
private val tasksEventChannel = Channel<TasksEvent>()
val addTaskEvent = tasksEventChannel.receiveAsFlow()
val receipts = receiptDao.getAllReceipts().asLiveData()
val totalSum = receiptDao.getSum().asLiveData()
fun onAddResult(result: Int){
when (result){
ADD_RECEIPT_RESULT_OK ->showReceiptSavedConfirmation("Receipt has been saved")
}
}
private fun showReceiptSavedConfirmation (text: String) = viewModelScope.launch {
tasksEventChannel.send(TasksEvent.ShowReceiptSavedConfirmation(text))
}
fun onSwipe (receipts: Receipts) = viewModelScope.launch {
receiptDao.delete(receipts)
tasksEventChannel.send(TasksEvent.ShowUndoDelete(receipts))
}
fun unDoDeleteClick (receipts: Receipts) = viewModelScope.launch {
receiptDao.insert(receipts)
}
sealed class TasksEvent {
data class ShowReceiptSavedConfirmation(val msg: String) : TasksEvent()
data class ShowUndoDelete(val receipts: Receipts) : TasksEvent()
}
}
You are getting a LiveData object here, not just a value:
val totalSum = receiptDao.getSum().asLiveData()
When you call:
totalSum.toString()
It calls toString method on the LiveData object that the reason, why you have "androidx.lifecycle.CoroutineLiveData#76f6edd" inside your TextView.
To fix the issue, just replace:
totalSumTextView.apply {
val totalSum = viewModel.totalSum
text = totalSum.toString()
}
with:
viewModel.totalSum.observe(this) { totalSumTextView.text = it}
Detailed review, how to work with a LiveData you can find here:
https://developer.android.com/topic/libraries/architecture/livedata
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 trying to figure out how to pass an integer from a fragment to a viewmodel while using hilt. I have ready that viewmodel factories can be used for this, I am not sure how this would be done using DI.
In the code below, I am trying to figure out how I can pass albumId to the viewModel. The albumId will be used when fetching data from an API endpoint.
Fragment
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_album_details, container, false)
val albumId = arguments?.getInt("album_id")
viewModel.songs.observe(viewLifecycleOwner) {
view.song_recyclerview.apply {
layoutManager = LinearLayoutManager(this.context)
adapter = SongAdapter(viewModel.songs)
}
}
return view
}
ViewModel
class SongViewModel #ViewModelInject constructor(
songRepo: SongRepository,
#Assisted savedStateHandle: SavedStateHandle
) : ViewModel(), LifecycleObserver {
val songs: LiveData<List<Song>> = songRepo.getSongs(1)
}
Repository
class SongRepository constructor(
private val musicService: MusicService
)
{
fun getSongs(album_id: Int): LiveData<List<Song>> {
val data = MutableLiveData<List<Song>>()
musicService.getAlbumTracks(album_id).enqueue(object : Callback<List<Song>> {
override fun onResponse(call: Call<List<Song>>, response: Response<List<Song>>) {
data.value = response.body()
}
override fun onFailure(call: Call<List<Song>>, t: Throwable) {
}
})
return data
}
}
I was finally able to figure out a solution to the problem. I added a field to the viewmodel, and a method to set a value for that field. Basically, I call viewModel.start(int) then call viewModel.songs.
ViewModel
class SongViewModel #ViewModelInject constructor(
songRepo: SongRepository,
#Assisted savedStateHandle: SavedStateHandle
) : ViewModel(), LifecycleObserver {
private val _id = MutableLiveData<Int>() // int field
private val _songs = _id.switchMap { id ->
songRepo.getSongs(id)
}
val songs: LiveData<List<Song>> = _songs
fun start(id: Int) { // method to update field
_id.value = id
}
}
Fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// get data from bundle and pass to start()
arguments?.getInt(("album_id"))?.let { viewModel.start(it)}
viewModel.songs.observe(viewLifecycleOwner) {
view.song_recyclerview.apply {
layoutManager = LinearLayoutManager(this.context)
adapter = SongAdapter(viewModel.songs)
}
}
}