My app doesn't show LiveData<String> into a text - android

The LiveData is supposed to print a String in playerOneName text attribute that we took from the TextInputEditText earlier but I tried even in Log.d it shows no result at all.
Here's the code
ViewModel
class GameViewModel: ViewModel() {
private val _playerOne = MutableLiveData<String>()
val playerOne: LiveData<String> = _playerOne
private val _playerTwo = MutableLiveData<String>()
val playerTwo: LiveData<String> = _playerTwo
private val _playerOneScore = MutableLiveData<Int>(0)
val playerOneScore: LiveData<Int> = _playerOneScore
private val _playerTwoScore = MutableLiveData<Int>(0)
val playerTwoScore: LiveData<Int> = _playerTwoScore
fun setPlayerOne(name: String) {
_playerOne.value = name
}
fun setPlayerTwo(name: String) {
_playerTwo.value = name
}
}
XML of StartFragment
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/player_one_name"
style="#style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="180dp"
android:layout_height="wrap_content"
android:hint="#string/player_one_name"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="10dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="#id/start_img">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/edit_player_one"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:maxLines="1" />
XML for GameFragment
<TextView
android:id="#+id/player_one_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:text="#{viewModel.playerOne.toString()}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
GameFragment
class GameFragment : Fragment() {
private var _binding: FragmentGameBinding? = null
private val binding get() = _binding!!
private val sharedViewModel: GameViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentGameBinding.inflate(inflater, container, false)
binding.resetBtn.setOnClickListener {
findNavController().navigate(R.id.action_gameFragment_to_startFragment)
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.apply {
lifecycleOwner = viewLifecycleOwner
viewModel = sharedViewModel
gameFragment = this#GameFragment
}
Log.d("GameFragment", "Player One:" + sharedViewModel.playerOne.toString())
}
}
I tried playerOneScore but as i expected the is the same problem here's the code for Log.d in the viewModel class
Log.d("GameViewModel", _playerOneScore.value.toString() + " Score: " + playerOneScore.value)
and here's the logcat
D/GameViewModel: 0 Score: null

Try following Code I hope your problem will be solved
Gradle file
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
ViewModel
class GameViewModel: ViewModel() {
private val _playerOne = MutableLiveData<String>()
val playerOne: LiveData<String> = Transformations.map(_playerOne) { it }
private val _playerTwo = MutableLiveData<String>()
val playerTwo: LiveData<String> = Transformations.map(_playerTwo) { it }
private val _playerOneScore = MutableLiveData<Int>(0)
val playerOneScore: LiveData<Int> = Transformations.map(_playerOneScore) { it }
private val _playerTwoScore = MutableLiveData<Int>(0)
val playerTwoScore: LiveData<Int> = Transformations.map(_playerTwoScore) { it }
fun setPlayerOne(name: String) {
_playerOne.value = name
}
fun setPlayerTwo(name: String) {
_playerTwo.value = name
}
}
GameFragment
class GameFragment : Fragment() {
private lateinit var sharedViewModel: GameViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedViewModel= ViewModelProvider(this).get(GameViewModel::class.java)
sharedViewModel.playerOne.observe(viewLifecycleOwner) {
Log.d("GameFragment", "Player One: $it")
}
// use this method to change the value
sharedViewModel.setPlayerOne("Some Player")
}

in the StartFragment.kt you need to override onPause() function so that when you go to the next fragment it saves the name of the the players in the ViewModel.
StartFragment.kt
override fun onPause() {
super.onPause()
var playerOneName: String = binding.editPlayerOne.text.toString()
sharedViewModel.setPlayerOne(playerOneName)
var playerTwoName: String = binding.editPlayerTwo.text.toString()
sharedViewModel.setPlayerTwo(playerTwoName) }
For the player's score i did the following
ViewModel
init {
resetData()
}
fun playerOneWon() {
_playerOneScore.value = _playerOneScore.value?.plus(1)
}
fun playerTwoWon() {
_playerTwoScore.value = _playerTwoScore.value?.plus(1)
fun resetData() {
_playerOneScore.value = 0
_playerTwoScore.value = 0 }
I used the playerOneWon() and playerTwoWon() functions to increment the score in the GameFragment.kt and for the xml i used
android:text="#{viewModel.playerOneScore.toString()}"

Related

Show BottomSheetDialogFragment when Click BottomNavigationView

I am new to Android Development. I like to show Bottom Sheet Dialog Fragment when I click one of the menu buttons at the Bottom Navigation View. When I click the favorite button on the languages, it shows empty for the bottom sheet dialog. Is there other way to do it? Thanks.
Main Activity
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
replacementFragment(HomeFragment())
binding.bottomNavigationView.setOnItemSelectedListener {
when (it.itemId) {
R.id.home -> replacementFragment(HomeFragment())
R.id.language -> replacementFragment(LanguageFragment())
// I cannot show the list on the fragment
R.id.favourite -> showFavouriteBottomFragment()
}
true
}
// val bottomSheetFragment = FavouriteFragment()
// bottomSheetFragment.show(supportFragmentManager, bottomSheetFragment.getTag() )
}
private fun replacementFragment(fragment: Fragment) {
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.fragmentContainerView, fragment)
fragmentTransaction.commit()
}
// I cannot get the following part to work....
private fun showFavouriteBottomFragment() {
val bottomSheetFragment = FavouriteFragment()
bottomSheetFragment.show(supportFragmentManager, bottomSheetFragment.getTag() )
}
}
Favourite Fragment
#AndroidEntryPoint
class FavouriteFragment: BottomSheetDialogFragment(){
private var _binding: FavouriteBottomSheetBinding ?= null
private val binding get() = _binding!!
private val viewModel: FavouriteViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FavouriteBottomSheetBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// val binding = FavouriteBottomSheetBinding.bind(view)
val favouriteAdapter = FavouriteAdapter()
binding.apply {
favouriteLanguageList.apply{
adapter = favouriteAdapter
layoutManager = LinearLayoutManager(requireContext())
setHasFixedSize(true)
}
}
viewModel.favouriteLanguage.observe(viewLifecycleOwner){
favouriteAdapter.submitList(it)
}
}
}
Bottom Menu
<menu xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/home"
android:icon="#drawable/ic_baseline_home_24"
android:title="Home"
tools:ignore="HardcodedText" />
<item
android:id="#+id/favourite"
android:icon="#drawable/ic_baseline_favorite_24"
android:title="Favourite"
tools:ignore="HardcodedText" />
<item
android:id="#+id/language"
android:icon="#drawable/ic_baseline_language_24"
android:title="Language"
tools:ignore="HardcodedText" />
</menu>
AppModule
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Provides
fun provideTestString() = "This is a string we will inject"
#Provides
#Singleton
fun provideDatabase(
app: Application,
callback: LanguageDatabase.Callback
) = Room.databaseBuilder(app, LanguageDatabase::class.java, "language_database")
.fallbackToDestructiveMigration()
.addCallback(callback)
.build()
#Provides
fun provideLanguageDao(db: LanguageDatabase) = db.languageDao()
#ApplicationScope
#Provides
#Singleton
fun provideApplicationScope() = CoroutineScope(SupervisorJob())
}
#Retention(AnnotationRetention.RUNTIME)
#Qualifier
annotation class ApplicationScope
Favourite Adapter
class FavouriteAdapter : ListAdapter<Language, FavouriteAdapter.FavouriteViewAHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FavouriteViewAHolder {
val binding = ItemFavouriteBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return FavouriteViewAHolder(binding)
}
override fun onBindViewHolder(holder: FavouriteViewAHolder, position: Int) {
val currentItem = getItem(position)
holder.bind(currentItem)
}
inner class FavouriteViewAHolder(private val binding: ItemFavouriteBinding) : RecyclerView.ViewHolder(binding.root){
fun bind(language: Language){
binding.apply {
rbIsClicked.isChecked = language.isChecked
tvFavouriteLanguage.text = language.language
}
}
}
class DiffCallback : DiffUtil.ItemCallback<Language>() {
override fun areItemsTheSame(oldItem: Language, newItem: Language) =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Language, newItem: Language) = oldItem == newItem
}
}
Favourite View Model
#HiltViewModel
class FavouriteViewModel #Inject constructor(
private val languageDao: LanguageDao,
) : ViewModel() {
val favouriteLanguage = languageDao.getFavouriteLanguageByName().asLiveData()
}
Preferences Manager
private const val TAG = "PreferencesManager"
private val Context.dataStore by preferencesDataStore("user_preferences")
enum class SortOrder { BY_NAME}
data class FilterPreferences(val sortOrder: SortOrder, val hideSelectedLanguage: Boolean, val selectedLanguage: String)
#Singleton
class PreferencesManager #Inject constructor(#ApplicationContext context: Context){
private val dataStore = context.dataStore
val preferencesFlow = dataStore.data
.catch { exception ->
if(exception is IOException){
Log.e(TAG, "Error reading preferences", exception)
emit(emptyPreferences())
}else{
throw exception
}
}
.map{ preferences ->
val sortOrder = SortOrder.valueOf(
preferences[PreferencesKeys.SORT_ORDER] ?:SortOrder.BY_NAME.name
)
val hideSelectedLanguage = preferences[PreferencesKeys.HIDE_SELECTED_LANGUAGE] ?: false
val selectedLanguage = preferences[PreferencesKeys.SELECTED_LANGUAGE]?: "English"
FilterPreferences(sortOrder, hideSelectedLanguage, selectedLanguage)
}
suspend fun updateSortOrder(sortOrder: SortOrder){
dataStore.edit { preferences ->
preferences[PreferencesKeys.SORT_ORDER] = sortOrder.name
}
}
suspend fun updateHideSelectedLanguage(hideSelectedLanguage: Boolean){
dataStore.edit { preferences ->
preferences[PreferencesKeys.HIDE_SELECTED_LANGUAGE] = hideSelectedLanguage
}
}
suspend fun updateSelectedLanguage(selectedLanguage: String){
dataStore.edit{ preferences ->
preferences[PreferencesKeys.SELECTED_LANGUAGE] = selectedLanguage
}
}
private object PreferencesKeys{
val SORT_ORDER = stringPreferencesKey("sort_order")
val HIDE_SELECTED_LANGUAGE = booleanPreferencesKey("hide_selected_language")
val SELECTED_LANGUAGE = stringPreferencesKey("selected_language")
}
}
I have a Language Fragment which looks similar to Favourite Fragment.
I find a temporary solution...but I still have a bug as I have the press the favourite button twice to show a list in the BottomSheetDialogFragment.
Is there a way, I can solve the problem? The first time I press the favourite button it doesn't shows a BottomSheetDialog fragment.... I have to press it again to show the list.
KC
In the MainActivity, I declare the class favouriteFragment.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// declare the favroute fragment.
val favouriteFragment = FavouriteFragment()
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
replacementFragment(HomeFragment())
binding.bottomNavigationView.setOnItemSelectedListener {
when (it.itemId) {
R.id.home -> replacementFragment(HomeFragment())
R.id.language -> replacementFragment(LanguageFragment())
R.id.favourite ->
// Pass the instace in the function.
showFavouriteBottomFragment(favouriteFragment)
}
true
}
In the function in Main Activity
private fun showFavouriteBottomFragment(favouriteFragment: FavouriteFragment) {
favouriteFragment.show(supportFragmentManager, favouriteFragment.tag)
}
I remove "setHasFixedSize(true)" and it can load the first time when I press the favorite button.

How to pass argument or return value from a function placed in view model and called in xml using data binding

I have been trying to pass some argument and get a boolean as a return for a function placed in view model class and calling that function in xml using data binding.
View model:
class ChatViewModel: ViewModel() {
val latestMessageFromFirst = MutableLiveData<String>()
private val emailOfUser = MutableLiveData<String>()
val isEmailValid = MutableLiveData<Boolean>()
fun setEmailOfUser(email: String) {
emailOfUser.value = email
}
fun setLatestMessageFromFirst(data: String) {
latestMessageFromFirst.value = data
}
fun verifyEmailAddress() {
emailOfUser.value?.let { email ->
isEmailValid.value = email.isNotEmpty() && Patterns.EMAIL_ADDRESS.matcher(email).matches()
}
}
}
XML:
<androidx.appcompat.widget.AppCompatButton
android:id="#+id/btnVerifyEmail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/_30sdp"
android:background="#drawable/stroke_button"
android:text="#string/verify"
android:onClick="#{() -> viewModel.verifyEmailAddress()}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/etEmailOfUser"
app:layout_constraintWidth_percent="0.3" />
Fragment:
class UserTwoFragment : Fragment() {
private lateinit var binding: FragmentUserTwoBinding
private val viewModel: ChatViewModel by activityViewModels()
override fun onResume() {
binding.viewModel = viewModel
super.onResume()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_user_two, container, false)
binding.viewModel = viewModel
binding.lifecycleOwner = this
val view = binding.root
binding.etEmailOfUser.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
var email = ""
s?.let {
email = it.toString()
}
viewModel.setEmailOfUser(email)
viewModel.isEmailValid.observe(viewLifecycleOwner) {
binding.tvIsEmailValid.text = if (it) {
getString(R.string.valid_email_text)
} else {
getString(R.string.invalid_email_text)
}
}
binding.viewModel = viewModel
}
override fun afterTextChanged(s: Editable?) { }
})
return view
}
}
Can anyone suggest how can I achieve the result of the function(called in xml) in my fragment(activity if not using fragments)?

Issue passing return of a function to textView in android

I am currently trying to calculate loan and want to get a monthly fee a user need to pay.
The calculation happends in a shared ViewModel and I want to send the return to a TextView.
But I only get a Null when the app build the view. Not sure how to call the function and then display the result.
AnnuitetViewModel.kt
fun getMonthlyPayment(): Double? {
val monthlyInterestRate = annualInterestRate?.div(100)
val x = monthlyInterestRate?.let { loanAmount?.times(it) }
val y =
numberOfYears?.let { Math.pow((1 + annualInterestRate!!).toDouble(), it.toDouble()) }
?.div((Math.pow((1 + annualInterestRate!!).toDouble(), numberOfYears!!.toDouble()) - 1))
val monthlyPayment = y?.let { x?.times(it) }
return monthlyPayment
}
fragment_annuitet_plan.xml
<TextView
android:id="#+id/tvTest4"
android:layout_width="150dp"
android:layout_height="30dp"
android:text="#{`` + viewmodel.monthlyPayment}"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.79" />
AnnuitetPLanFragment.kt
class AnnuitetPlanFragment : Fragment() {
private var _binding: FragmentAnnuitetPlanBinding? = null
private val binding get() = _binding!!
private lateinit var viewModel: AnnuitetViewModel
private val AnnuitetViewModel: AnnuitetViewModel by activityViewModels()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
_binding = FragmentAnnuitetPlanBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this)[AnnuitetViewModel::class.java]
AnnuitetViewModel.antallRenter.observe(viewLifecycleOwner) { progressRenter ->
binding.tvTest.text = progressRenter.toString()
}
AnnuitetViewModel.progressAar.observe(viewLifecycleOwner) { progressAar ->
binding.tvTest2.text = progressAar.toString()
}
AnnuitetViewModel.loanNumber.observe(viewLifecycleOwner) { loanNumber ->
binding.tvTest3.text = loanNumber.toString()
}
binding.tilbakeTilAnnuitet.setOnClickListener {
findNavController().navigate(R.id.action_annuitetPlanFragment_to_annuitetFragment)
}
}
}

Is it normal for the ViewModel class to be called without setValue on LiveData?

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")
}
)
}
}
}

How to open a fragment from another fragment using MVVM

I have a fragment ProductsFragment in which I have a button AddProduct when it is clicked I want to open a different fragment AddProductFragment.
I am using MVVM architecture
I went through this link and done the below mentioned implementation, but I did not quite understand or did not mention where fragment I want to navigate to
Error message
ProductsFragment - THE ISSUE IS HERE IN ONVIEWCREATED METHOD*
class ProductsFragment: Fragment() {
private lateinit var binding: ProductsBinding
private lateinit var navController: NavController
private lateinit var productsViewModel: ProductsViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.products, container, false)
val dao = SubscriberDatabase.getInstance(activity!!.applicationContext).productDAO
val repository = ProductRepository(dao)
val factory = ProductsViewModelFactory(repository, activity!!.applicationContext)
productsViewModel = ViewModelProvider(this, factory).get(ProductsViewModel::class.java)
binding.productsViewModel = productsViewModel
binding.lifecycleOwner = this
val view = binding.root
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
navController = Navigation.findNavController(view)
productsViewModel.navigateScreen.observe(activity!!, EventObserver {
navController.navigate(it) //issues is here
})
}
}
Products
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android">
<data class=".ProductsBinding">
<variable
name="productsViewModel"
type="com.rao.iremind.ProductsViewModel" />
</data>
<LinearLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Testing text"/>
<Button
android:id="#+id/btn_add_product"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add product"
android:onClick="#{() -> productsViewModel.addProduct()}"/>
<View
android:id="#+id/frgSpace"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
</layout>
ProductViewModel
class ProductsViewModel (
private val repository: ProductRepository,
private val context: Context
): ViewModel() {
private val _navigateScreen = MutableLiveData<Event<Any>>()
val navigateScreen: LiveData<Event<Any>> = _navigateScreen
fun addProduct() {
Toast.makeText(context, "Products view model", Toast.LENGTH_LONG).show()
_navigateScreen.value = Event(R.id.frgSpace)
}
}
Event
open class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}
class EventObserver<Int>(private val onEventUnhandledContent: (Int) -> Unit) : Observer<Event<Int>> {
override fun onChanged(event: Event<Int>?) {
event?.getContentIfNotHandled()?.let {
onEventUnhandledContent(it)
}
}
}
ProductsViewModelFactory
class ProductsViewModelFactory (
private val repository: ProductRepository,
private val context: Context
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ProductsViewModel::class.java)) {
return ProductsViewModel(repository, context) as T
}
throw IllegalArgumentException("Unknown View Model class")
}
}
I want to navigate to this fragment
class AddProductFragment: Fragment() {
private lateinit var binding: AddProductBinding
private lateinit var addProductViewModel: AddProductViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.add_product, container, false)
val dao = SubscriberDatabase.getInstance(activity!!.applicationContext).productDAO
val repository = ProductRepository(dao)
val factory = ProductsViewModelFactory(repository, activity!!.applicationContext)
addProductViewModel = ViewModelProvider(this, factory).get(AddProductViewModel::class.java)
binding.addProductViewModel = addProductViewModel
binding.lifecycleOwner = this
val view = binding.root
return view
}
}
Thanks
R
It seems that your EventObserver class is expecting an Int but you are sending Any in LiveData<Event<Any>>
Try changing
private val _navigateScreen = MutableLiveData<Event<Any>>()
val navigateScreen: LiveData<Event<Any>> = _navigateScreen
to
private val _navigateScreen = MutableLiveData<Event<Int>>()
val navigateScreen: LiveData<Event<Int>> = _navigateScreen
I would also recommend you to replace activity!! with viewLifecycleOwner in this line:
productsViewModel.navigateScreen.observe(viewLifecycleOwner, EventObserver {...})
so that your fragment does not receive any LiveData updates when its view is destroyed.

Categories

Resources