How to handle android rotate screen if I have a fragment that observe the data from view model to fill recyclerview list ?!
when rotation happened the data lost, so how can I handle it with viewmodel so the user didn't feel any different after rotation?
this is the part of the view model the return data:
private fun getImageList(keyWord: String) {
responseManager.loading()
val disposable = imageListUseCase.execute(keyWord, { success ->
responseManager.hideLoading()
_observeImageListData.value = Event(success)
}, { error ->
responseManager.failed(error)
})
compositeDisposable.add(disposable)
}
and here I set the recycler data on the fragment:
UPDATE
#AndroidEntryPoint
class ImagePickerFragment : Fragment() {
private lateinit var imagePickerBinding: FragmentImagePickerBinding
private val imagePickerViewModel: ImagePickerViewModel by activityViewModels()
private lateinit var imagePickerAdapter: ImagePickerAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
imagePickerBinding = FragmentImagePickerBinding.inflate(inflater, container, false)
observeImageList()
observeSearchText()
return imagePickerBinding.root
}
private fun observeImageList() {
imagePickerViewModel.observeImageListData.observe(
viewLifecycleOwner,
EventObserver { imageList ->
imagePickerAdapter = ImagePickerAdapter(imageList, imagePickerViewModel)
imagePickerBinding.apply {
rvImageList.setHasFixedSize(true)
rvImageList.layoutManager = GridLayoutManager(requireContext(),2)
rvImageList.adapter = imagePickerAdapter
}
})
}
Any tips?
UPDATE :
The viewModel extends from BaseViewModel :
abstract class BaseViewModel: ViewModel() {
val compositeDisposable = CompositeDisposable()
override fun onCleared() {
super.onCleared()
compositeDisposable.clear()
}
}
and this is the full viewmodel:
#HiltViewModel
class ImagePickerViewModel #Inject constructor(
private val imageListUseCase: ImageListUseCase,
private val responseManager: ResponseManager
) : BaseViewModel() {
private val _observeImageListData = MutableLiveData<Event<ArrayList<Image>>>()
init {
getImageList(Constants.KEY_WORD)
}
private fun getImageList(keyWord: String) {
responseManager.loading()
val disposable = imageListUseCase.execute(keyWord, { success ->
responseManager.hideLoading()
_observeImageListData.value = Event(success)
}, { error ->
responseManager.failed(error)
})
compositeDisposable.add(disposable)
}
fun filterSearchKeyWord(filteredKeyWord: String) {
if(filteredKeyWord.isNotEmpty())
getImageList(filteredKeyWord)
}
//getters:
val observeImageListData: LiveData<Event<ArrayList<Image>>>
get() = _observeImageListData
}
In your manifest, try putting this in your activity tag:
android:configChanges="orientation"
So it looks like:
<activity
android:name=".MainActivity"
android:configChanges="orientation"
android:exported="true">
Related
i have a really simple vocabulary note app contains 2 fragment and 1 root activity. In HomeFragment i have a button "addVocabularyButton". When it is clicked a BottomSheetDialogFragment appears and user gives 3 inputs and with a viewmodel it is saved in DB. My problem is when i save the input to the DB it works fine but i cannot see in HomeFragment that word instantaneously. I have to re-run the app to see in home fragment. I am using Navigation library and recycler view in home fragment.
Github link : https://github.com/ugursnr/MyVocabularyNotebook
Home Fragment
class HomeFragment : Fragment() {
private var _binding : FragmentHomeBinding? = null
private val binding get() = _binding!!
private var vocabularyAdapter = VocabulariesHomeAdapter()
private lateinit var sharedViewModel: AddVocabularySharedViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(layoutInflater,container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//sharedViewModel = ViewModelProvider(this)[AddVocabularySharedViewModel::class.java]
sharedViewModel = (activity as MainActivity).sharedViewModel
sharedViewModel.getAllVocabulariesFromDB()
observeAllVocabularies()
prepareRecyclerView()
addVocabularyOnClick()
vocabularyAdapter.onItemDeleteClicked = {
sharedViewModel.deleteVocabulary(it)
observeAllVocabularies()
}
}
private fun prepareRecyclerView(){
binding.recyclerViewHome.apply {
layoutManager = LinearLayoutManager(context)
adapter = vocabularyAdapter
}
}
private fun addVocabularyOnClick(){
binding.addVocabularyButton.setOnClickListener{
val action = HomeFragmentDirections.actionHomeFragmentToAddVocabularyBottomSheetFragment()
Navigation.findNavController(it).navigate(action)
}
}
private fun observeAllVocabularies(){
sharedViewModel.allVocabulariesLiveData.observe(viewLifecycleOwner, Observer {
vocabularyAdapter.updateVocabularyList(it)
})
}
}
Dialog Fragment
class AddVocabularyBottomSheetFragment : BottomSheetDialogFragment() {
private var _binding : FragmentAddVocabularyBottomSheetBinding? = null
private val binding get() = _binding!!
private lateinit var sharedViewModel: AddVocabularySharedViewModel
private var vocabularyInput : String? = null
private var translationInput : String? = null
private var sampleSentenceInput : String? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentAddVocabularyBottomSheetBinding.inflate(layoutInflater,container,false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//sharedViewModel = ViewModelProvider(this)[AddVocabularySharedViewModel::class.java]
sharedViewModel = (activity as MainActivity).sharedViewModel
binding.addOrUpdateVocabularyButton.setOnClickListener {
vocabularyInput = binding.vocabularyActualET.text.toString()
translationInput = binding.vocabularyTranslationET.text.toString()
sampleSentenceInput = binding.vocabularySampleSentenceET.text.toString()
val inputVocabulary = Vocabulary(vocabularyInput,translationInput,sampleSentenceInput)
insertVocabularyToDB(inputVocabulary)
sharedViewModel.getAllVocabulariesFromDB()
dismiss()
}
}
private fun insertVocabularyToDB(vocabulary: Vocabulary){
sharedViewModel.insertVocabulary(vocabulary)
}
}
Shared ViewModel
class AddVocabularySharedViewModel(application: Application) : AndroidViewModel(application) {
private var _allVocabulariesLiveData = MutableLiveData<List<Vocabulary>>()
private var _vocabularyLiveData = MutableLiveData<Vocabulary>()
val allVocabulariesLiveData get() = _allVocabulariesLiveData
val vocabularyLiveData get() = _vocabularyLiveData
val dao = VocabularyDatabase.makeDatabase(application).vocabularyDao()
val repository = VocabularyRepository(dao)
fun insertVocabulary(vocabulary: Vocabulary) = CoroutineScope(Dispatchers.IO).launch {
repository.insertVocabulary(vocabulary)
}
fun updateVocabulary(vocabulary: Vocabulary) = CoroutineScope(Dispatchers.IO).launch {
repository.updateVocabulary(vocabulary)
}
fun deleteVocabulary(vocabulary: Vocabulary) = CoroutineScope(Dispatchers.IO).launch {
repository.deleteVocabulary(vocabulary)
}
fun getAllVocabulariesFromDB() = CoroutineScope(Dispatchers.IO).launch {
val temp = repository.getAllVocabulariesFromDB()
withContext(Dispatchers.Main){
_allVocabulariesLiveData.value = temp
}
}
fun getVocabularyDetailsByID(vocabularyID : Int) = CoroutineScope(Dispatchers.IO).launch {
val temp = repository.getVocabularyDetailsByID(vocabularyID).first()
withContext(Dispatchers.Main){
_vocabularyLiveData.value = temp
}
}
}
Adapter
class VocabulariesHomeAdapter : RecyclerView.Adapter<VocabulariesHomeAdapter.VocabulariesHomeViewHolder>() {
lateinit var onItemDeleteClicked : ((Vocabulary) -> Unit)
val allVocabulariesList = arrayListOf<Vocabulary>()
class VocabulariesHomeViewHolder(val binding : RecyclerRowBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VocabulariesHomeViewHolder {
return VocabulariesHomeViewHolder(RecyclerRowBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: VocabulariesHomeViewHolder, position: Int) {
val vocabulary = allVocabulariesList[position]
holder.binding.apply {
actualWordTV.text = vocabulary.vocabulary
translationWordTV.text = vocabulary.vocabularyTranslation
deleteButtonRV.setOnClickListener {
onItemDeleteClicked.invoke(vocabulary)
notifyItemRemoved(position)
}
}
}
override fun getItemCount(): Int {
return allVocabulariesList.size
}
fun updateVocabularyList(newList : List<Vocabulary>){
allVocabulariesList.clear()
allVocabulariesList.addAll(newList)
notifyDataSetChanged()
}
}
I know there are lots of codes up there but i have a really big problems about using these dialog fragments. Thank you for your help.
This is because multiple instances of the same View Model are created by the Navigation Library for each Navigation Screen.
You need to tell the Navigation Library to share the same ViewModel between all navigation screens.
Easiest way to fix this is to scope the viewModel to the Activity rather than a Fragment and using it in all your fragments.
val viewModel = ViewModelProvider(requireActivity()).get(MyViewModel::class.java)
This way, the viewModel is scoped to the Application instance rather than Fragment. This will keep the state in the viewModel persistent across the Application.
You can also do this by scoping the viewModel to the navigation graph.
val myViewModel: MyViewModel by navGraphViewModels(R.id.your_nested_nav_id)
Alternate method, if you're using dependency injection libraries
val navController = findNavController();
val navBackStackEntry = navController.currentBackStackEntry!!
If you use hilt, you can just pass your NavBackStackEntry of the NavGraph to hiltViewModel()
val viewModel = hiltViewModel<MyViewModel>(//pass NavBackStackEntry)
This will give you a viewModel that is scoped to NavBackStackEntry and will only be recreated when you pop the NavBackStackEntry(ie Navigate out of the navigation screens.)
Well I am a beginner with android and kotlin so I have been trying to send a variable semesterSelected from the fragment ViewCourses to my viewmodel UserViewModel is the codes are down below.
`class ViewCourses(path: String) : ReplaceFragment() {
private var semesterSelected= path
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
container?.removeAllViews()
return inflater.inflate(R.layout.fragment_view_courses, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
userRecyclerView = view.findViewById(R.id.recyclerView)
userRecyclerView.layoutManager = LinearLayoutManager(context)
userRecyclerView.setHasFixedSize(true)
adapter = MyAdapter()
userRecyclerView.adapter = adapter
makeToast(semesterSelected)
// The variable I am trying to send to UserViewModel is -->> semesterSelected
var viewModel: UserViewModel = ViewModelProvider(this)[UserViewModel::class.java]
viewModel.allUsers.observe(viewLifecycleOwner) {
adapter.updateUserList(it)
}
}
}
class UserViewModel : ViewModel() {
private val repository: UserRepository = UserRepository("CSE/year3semester1").getInstance()
private val _allUsers = MutableLiveData<List<CourseData>>()
val allUsers: LiveData<List<CourseData>> = _allUsers
init {
repository.loadUsers(_allUsers)
}
}
The reason I am doing this is I am wanting a to send a variable to my repository UserRepository all the way from ViewCourses and thought sending this via UserViewModel might be a way .
class UserRepository(semesterSelected: String) {
// The variable I am expecting to get from UserViewModel
private var semesterSelected = semesterSelected
private val databaseReference: DatabaseReference =
FirebaseDatabase.getInstance().getReference("course-list/$semesterSelected")
#Volatile
private var INSTANCE: UserRepository? = null
fun getInstance(): UserRepository {
return INSTANCE ?: synchronized(this) {
val instance = UserRepository(semesterSelected)
INSTANCE = instance
instance
}
}
fun loadUsers(userList: MutableLiveData<List<CourseData>>) {
databaseReference.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
try {
val courseList: List<CourseData> = snapshot.children.map { dataSnapshot ->
dataSnapshot.getValue(CourseData::class.java)!!
}
userList.postValue(courseList)
} catch (e: Exception) {
}
}
override fun onCancelled(error: DatabaseError) {
}
})
}
}
I tried something like below
class ViewCourses(path: String) : ReplaceFragment() {
private var semesterSelected= path
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
container?.removeAllViews()
return inflater.inflate(R.layout.fragment_view_courses, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
userRecyclerView = view.findViewById(R.id.recyclerView)
userRecyclerView.layoutManager = LinearLayoutManager(context)
userRecyclerView.setHasFixedSize(true)
adapter = MyAdapter()
userRecyclerView.adapter = adapter
makeToast(semesterSelected)
**// Sending the variable as parameter**
var viewModel: UserViewModel = ViewModelProvider(this)[UserViewModel(semesterSelected)::class.java]
viewModel.allUsers.observe(viewLifecycleOwner) {
adapter.updateUserList(it)
}
}
}
class UserViewModel(semesterSelected: String) : ViewModel() {
private val repository: UserRepository = UserRepository("CSE/year3semester1").getInstance()
private val _allUsers = MutableLiveData<List<CourseData>>()
val allUsers: LiveData<List<CourseData>> = _allUsers
init {
repository.loadUsers(_allUsers)
}
}
but doing this my app crashes . how can this be done ?
Thanks in Advance.
var viewModel: UserViewModel = ViewModelProvider(this)[UserViewModel(semesterSelected)::class.java]
UserViewModel(semesterSelected)::class.java NOR UserViewModel::class.java is a constructor for the view model.
If you would want to have ViewModel with that NEEDS initial parameters, you will have to create your own factory for that - which is a tad more complicated and for your case, it might be overkill for what you are trying to do but in the longterm it will pay off(Getting started with VM factories).
With that said, your needs can be easily solved by one function to initialize the view model.
class UserViewModel() : ViewModel() {
private lateinit var repository: UserRepository
private val _allUsers = MutableLiveData<List<CourseData>>()
val allUsers: LiveData<List<CourseData>> = _allUsers
fun initialize(semesterSelected: String) {
repository = UserRepository("CSE/year3semester1").getInstance()
repository.loadUsers(_allUsers)
}
}
A ViewModel must be created using a ViewModelProvider.Factory. But there is a default Factory that is automatically used if you don't specify one. The default factory can create ViewModels who have constructor signatures that are one of the following:
empty, for example MyViewModel: ViewModel.
saved state handle, for example MyViewModel(private val savedStateHandle: SavedStateHandle): ViewModel
application, for example MyViewModel(application: Application): AndroidViewModel(application)
both, for example MyViewModel(application: Application, private val savedStateHandle: SavedStateHandle): AndroidViewModel(application)
If your constructor doesn't match one of these four above, you must create a ViewModelProvider.Factory that can instantiate your ViewModel class and use that when specifying your ViewModelProvider. In Kotlin, you can use by viewModels() for easier syntax. All the instructions for how to create your ViewModelFactory are here.
I am studying the MVVM pattern.
I have a question regarding LiveData while using ViewModel class.
Even if I do not change the value of LiveData with setValue or postValue, it continues to observe and execute the fragment.
When addRoutine() is called, vm.observe also continues to run.
As you can see there is no setValue or postValue in addRoutine(), so LiveData has no value change at all.
But why does vm.observe keep running?
This is my code.
ViewModel.kt
class WriteRoutineViewModel : ViewModel() {
private val _items: MutableLiveData<List<RoutineModel>> = MutableLiveData(listOf())
private val rmList = arrayListOf<RoutineModel>()
val items: LiveData<List<RoutineModel>> = _items
fun addRoutine(workout: String) {
val rmItem = RoutineModel(UUID.randomUUID().toString(), workout, "TEST")
rmItem.getSubItemList().add(RoutineDetailModel("2","3","3123"))
rmList.add(rmItem)
// _items.postValue(rmList)
}
fun getListItems() : List<RoutineItem> {
val listItems = arrayListOf<RoutineItem>()
for(testRM in rmList) {
listItems.add(RoutineItem.RoutineModel(testRM.id,testRM.workout,testRM.unit))
val childListItems = testRM.getSubItemList().map { detail ->
RoutineItem.DetailModel("2","23","55")
}
listItems.addAll(childListItems)
}
return listItems
}
}
Fragment
class WriteRoutineFragment : Fragment() {
private var _binding : FragmentWriteRoutineBinding? = null
private val binding get() = _binding!!
private val vm : WriteRoutineViewModel by viewModels { WriteRoutineViewModelFactory() }
private lateinit var epoxyController : RoutineItemController
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
_binding = FragmentWriteRoutineBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
getTabPageResult()
// RecyclerView(Epoxy) Update
vm.items.observe(viewLifecycleOwner) { updatedItems ->
epoxyController.setData(vm.getListItems())
}
}
private fun getTabPageResult() {
val navController = findNavController()
navController.currentBackStackEntry?.also { stack ->
stack.savedStateHandle.getLiveData<String>("workout")?.observe(
viewLifecycleOwner, Observer { result ->
vm.addRoutine(result)
stack.savedStateHandle?.remove<String>("workout")
}
)
}
}
}
My app contains a Room db of cocktail recipes that it downloads via a Retrofit api call, all of that is working well. To focus in on where my problem lies, my use case is a user adding a cocktail to a list. This is done via a DialogFragment and here the DialogFragment displays, the transaction executes, the DialogFragment goes away and the Room db is updated. The cocktail fragment does not get the update though - if you navigate away and back, the update is visible so I know the transaction worked as expected. One thing I just noticed is if I rotate the device, the update gets picked up as well.
Here is the relevant section of my fragment:
class CocktailDetailFragment : BaseFragment<CocktailDetailViewModel, FragmentCocktailDetailBinding, CocktailDetailRepository>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.cocktail.observe(viewLifecycleOwner, Observer {
when(it){
is Resource.Success -> {
updateCocktail(it.value)
}
}
})
}
private fun updateCocktail(cocktail: Cocktail) {
with(binding){
detailCocktailName.text = cocktail.cocktailName
//...
//this is the piece of functionality i'm expecting the LiveData observer to execute and change the drawable
if(cocktail.numLists > 0) {
detailCocktailListImageView.setImageResource(R.drawable.list_filled)
} else {
detailCocktailListImageView.setImageResource(R.drawable.list_empty)
}
}
}
override fun getViewModel() = CocktailDetailViewModel::class.java
override fun getFragmentBinding(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentCocktailDetailBinding.inflate(inflater,container,false)
override fun getFragmentRepository(): CocktailDetailRepository {
val dao = GoodCallDatabase(requireContext()).goodCallDao()
return CocktailDetailRepository(dao)
}
}
BaseFragment:
abstract class BaseFragment<VM: BaseViewModel, B: ViewBinding, R: BaseRepository>: Fragment() {
protected lateinit var userPreferences: UserPreferences
protected lateinit var binding: B
protected lateinit var viewModel: VM
protected val remoteDataSource = RemoteDataSource()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
userPreferences = UserPreferences(requireContext())
binding = getFragmentBinding(inflater, container)
val factory = ViewModelFactory(getFragmentRepository())
viewModel = ViewModelProvider(this, factory).get(getViewModel())
return binding.root
}
abstract fun getViewModel() : Class<VM>
abstract fun getFragmentBinding(inflater: LayoutInflater, container: ViewGroup?): B
abstract fun getFragmentRepository(): R
}
ViewModelFactory:
class ViewModelFactory(
private val repository: BaseRepository
): ViewModelProvider.NewInstanceFactory() {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return when{
modelClass.isAssignableFrom(CocktailDetailViewModel::class.java) -> CocktailDetailViewModel(repository as CocktailDetailRepository) as T
else -> throw IllegalArgumentException("ViewModel class not found")
}
}
}
ViewModel:
class CocktailDetailViewModel(
private val repository: CocktailDetailRepository
): BaseViewModel(repository) {
private val _cocktail: MutableLiveData<Resource<Cocktail>> = MutableLiveData()
val cocktail: LiveData<Resource<Cocktail>>
get() = _cocktail
fun getCocktailByCocktailId(cocktailId: Int) = viewModelScope.launch {
_cocktail.value = Resource.Loading
_cocktail.value = repository.getCocktail(cocktailId)
}
}
Repository:
class CocktailDetailRepository(
private val dao: GoodCallDao
):BaseRepository(dao) {
suspend fun getCocktail(cocktailId: Int) = safeApiCall {
dao.getCocktail(cocktailId)
}
}
Safe Api Call (I use this so db/api calls run on IO):
interface SafeApiCall {
suspend fun <T> safeApiCall(
apiCall: suspend () -> T
): Resource<T> {
return withContext(Dispatchers.IO) {
try {
Resource.Success(apiCall.invoke())
} catch (throwable: Throwable) {
when (throwable) {
is HttpException -> {
Resource.Failure(false, throwable.code(), throwable.response()?.errorBody())
}
else -> {
Resource.Failure(true, null, null)
}
}
}
}
}
}
Resource:
sealed class Resource<out T> {
data class Success<out T>(val value: T) : Resource<T>()
data class Failure(
val isNetworkError: Boolean,
val errorCode: Int?,
val errorBody: ResponseBody?
): Resource<Nothing>()
object Loading: Resource<Nothing>()
}
Dao:
#Query("SELECT * FROM cocktail c WHERE c.cocktail_id = :cocktailId")
suspend fun getCocktail(cocktailId: Int): Cocktail
Thank you in advance for any help! Given the issue and how the app is working, I believe I have provided all the relevant parts but please advise if more of my code is required to figure this out.
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)
}