I am learning mvvm. I want to pass frgment bundle argument in ViewModel savedstatehandle .I am using Hilt-Dagger. Please help me. Everytime savedstatehandle is null.
here is my code.
FIRST FRAGMENT:
#AndroidEntryPoint
class CustomerOrderSubmit : Fragment() {
private lateinit var binding: FragmentCustomerOrderSubmitBinding
val customerViewModel: CustomerViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
binding = FragmentCustomerOrderSubmitBinding.inflate(inflater, container, false)
binding.customerModel = customerViewModel
binding.lifecycleOwner = viewLifecycleOwner
customerViewModel.btnClick.observe(viewLifecycleOwner) {
when (it) {
true -> {
val args = Bundle()
args.putParcelable("customer",customerViewModel.args.value)
val orderReview = OrderReview()
orderReview.arguments = args
Log.d("PARC",args.toString())
val fragmentManager = requireActivity().supportFragmentManager.beginTransaction()
fragmentManager.replace(R.id.main_frame_layout,orderReview)
.addToBackStack(null).commit()
}
else -> {}
}
}
SECOND FRAGMENT: HERE I AM GETTING BUNDLE VALUE. BUT I WANT IT IN ViewModel
#AndroidEntryPoint
class OrderReview : Fragment() {
private lateinit var binding: FragmentOrderReviewBinding
private val customerViewModel: CustomerViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
binding = FragmentOrderReviewBinding.inflate(inflater, container, false)
binding.argsModel = customerViewModel
binding.lifecycleOwner = viewLifecycleOwner
val args : CustomerModel? = arguments?.getParcelable("customer")
Log.d("PARCF",args.toString())
return binding.root
}
}
VIEW MODEL: But getArgs.value is always null.
#HiltViewModel
class CustomerViewModel #Inject constructor(
private val cusomerRepository: CusomerRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val customerList: LiveData<List<CustomerModel>>
//get arguments
val getArgs = MutableLiveData<CustomerModel>(savedStateHandle["customer"])
I am getting Bundle data from fragments one to two but can't get the same data from Fragment one to ViewmodelSavedstatehandle but null every time. What have I done wrong? I need data in ViewModel.
Please help I am a new learner in MVVM.
I don't think you can pass data in this way from fragment to ViewModel.Pass this bundle data by accessing the method where it is needed.
ViewModel method
fun aMethodName(customr:String){
//your code here
//access customer
//Data type String can be changed to customer DataType
}
Fragment
from onCreateView or onViewCreated you can send the data to viewmodel
customerViewModel.aMethodName(customer)
Related
I have a parent fragment which fetches a list from API using ViewModel and Retrofit, the ViewModel is injected with Hilt.
After the list gets fetched the parent fragment will pass to its child fragment that is inside of parent fragment.
but the problem is that ViewModel is instantiated one more time in the child fragment.
Parent Fragment
#AndroidEntryPoint
class ParentFragment : Fragment() {
override val mViewModel: URLViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
mViewBinding = getViewBinding(inflater, container)
mViewModel.liveData.observe(this, { data ->
{
childFragmentManager.beginTransaction().apply {
replace(
mViewBinding.fragmentContainer.id,
ChildFragment(data)
)
}
commit()
} })
mViewModel.getURL("TEST", "2021-06-18", "2021-07-18", 1 , 0 , -1, false)
return mViewBinding.root
}
}
ChildFragment
#AndroidEntryPoint
class ChildFragment(val data: List<Item>) : Fragment() {
override val mViewModel: URLViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
mViewBinding = getViewBinding(inflater, container)
// mViewModel is instantiated again and some all strings properties of it is null.
return mViewBinding.root
}
}
URLViewModel
#HiltViewModel
class URLViewModel #Inject constructor(private val urlApi: URLApi): ViewModel() {
private val _urlLiveData = MutableLiveData<State<Any?>>()
val urlLiveData: LiveData<State<Any?>> = _urlLiveData
var urlName: String? = null
var beginDate: String? = null
var endDate: String? = null
var adultCount = 0
var childrenCount = 0
var airportId = 0
var isRoundTrip = false
init {
Log.e("URLViewModel", "iniialed again" )
}
#ExperimentalStdlibApi
fun getUrl(urlName: String, beginDate: String, endDate: String, adultCount: Int, childCount: Int, airportId: Int, isRoundTrip: Boolean){
Log.e("XXXXXX", "getUrl: called with url of " + urlName )
this.urlName = urlName
this.beginDate = beginDate
this.endDate = endDate
this.adultCount = adultCount
this.childrenCount = childCount
this.airportId = airportId
this.isRoundTrip = isRoundTrip
val mutableLiveData = MutableLiveData<State<Any?>>()
mutableLiveData.value = State.loading()
viewModelScope.launch {
val res = urlApi.getURL(urlName,beginDate,endDate,adultCount,childCount,airportId,isRoundTrip)
Log.e("URLVIewModel", "getUrl: response received" )
_urlLiveData.value = res
}
}
}
when I wanna access some properties like beginDate, they are null, because the ViewModel is instantiated again,
viewModels() delegation create view model against the same instance i.e Fragment's instance in your case. What you need to do is to create a shared View model .
There is helper delegate available for it with ktx libraries.
add the ktx dependency which you already have i guess from here.
implementation "androidx.fragment:fragment-ktx:1.3.4"
And create view model with
private val viewModel by activityViewModels<UrlViewModel>()
You do not have to use activity shared view model. Simply request view model from parent fragment in ChildFragment.
private val viewModel by viewModels<UrlViewModel>(ownerProducer = { requireParentFragment() })
You are trying to use share viewmodel. Try to following code for reference.
#AndroidEntryPoint
class ParentFragment : Fragment() {
private lateinit var viewModel: URLViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(requireActivity()).get(URLViewModel::class.java)
}
}
#AndroidEntryPoint
class ChildFragment : Fragment() {
private lateinit var viewModel: URLViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(requireActivity()).get(URLViewModel::class.java)
}
}
I will try to explain my issue step by step.
I created data variable within xml file
<data>
<variable
name="SLC90RViewModel"
type="com.example.poy.ui.questionnaires.tools.SCL90RViewModel"/>
</data>
In my ViewModel i need to retrieve string array from resources (which is stored in xml file), so i passed app context into ViewModel constructor. Now it looks like this:
class SCL90RViewModel(val context: Context) : ViewModel() {
val item = context.getResources().getStringArray(R.array.firstArray)
}
Finally, in my Fragment i want to assign LifecycleOwner and ViewModel to the binding:
class QuestionnaireFragment : Fragment(R.layout.fragment_questionnaire) {
private var binding: FragmentQuestionnaireBinding? = null
private val scl90rViewModel: SCL90RViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val fragmentBinding = FragmentQuestionnaireBinding.inflate(inflater, container, false)
binding = fragmentBinding
return fragmentBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.apply {
lifecycleOwner = viewLifecycleOwner
SCL90RViewModel = scl90rViewModel
}
}
}
However, SCL90RViewModel in binding?.apply{} is outlined with red and message "Classifier 'SCL90RViewModel' does not have a companion object, and thus must be initialized here" pops up. How can i fix it?
SCL90RViewModel is ambiguous here, it can be the binding variable or the ViewModel class. So use this inside the apply block to refer to the binding variable.
binding?.apply {
lifecycleOwner = viewLifecycleOwner
this.SCL90RViewModel = scl90rViewModel
}
I try to preserve viewModel data against activity re-creation , but on onCreate() method from Fragment I got the following error : java.lang.IllegalArgumentException: SavedStateProvider with the given key is already registered
Even if the viewModel has never been create before, it causes a crash on the first app run
This is the relevent code:
//DI
viewModel { (param: String) -> MyViewModel(param, get(), get(), get()) }
//Fragment
private lateinit var viewModel: MyViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
super.onCreateView(inflater, container, savedInstanceState)
viewModel = getStateViewModel() { parametersOf(args.param)
}
//VM
class MyViewModel: ViewModel()(
private val param: String,
private val savedState: SavedStateHandle,
private val service1: Service1,
private val service2: Service2
)
Any help?
I have a tablayout inside a fragment. The tab layout has 3 tabs. Which tab has a fragment.
And, in the first fragment I insert a string and add it to a viewModel MutableLiveData<ArrayList>> variable.
Then, I want to observe in the third fragment.
My main ViewModel:
class MainViewModel : ViewModel() {
val message = MutableLiveData<ArrayList<String>>(arrayListOf())
fun myMessage(msg: String) {
message.value?.add(msg)
}
}
Third Fragment:
class Fragment3 : Fragment() {
lateinit var model: MainViewModel
private lateinit var viewModel: MainViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view= inflater.inflate(R.layout.fragment3_fragment, container, false)
model = activity?.let { ViewModelProvider(it).get(MainViewModel::class.java) }!!
model.message.observe(viewLifecycleOwner, Observer {
Log.e("aqui", "aqui$it")
})
return view
}
}
My first Fragment:
class Fragment1 : Fragment() {
lateinit var model: MainViewModel
private lateinit var viewModel: MainViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view= inflater.inflate(R.layout.fragment1_fragment, container, false)
model = activity?.let { ViewModelProvider(it).get(MainViewModel::class.java) }!!
var btnTest= view.findViewById<Button>(R.id.btn_test)
btnTest.setOnClickListener {
model.myMessage(et_text.text.toString())
}
return view
}
}
When I open the Third fragment it observes as planned. When I come back to first Fragment and add more Strings to the array list, the third does not observes again, I don't know why.
First Observer:
My layout:
My observer only observes once.
You have to change the value of the live data instead of just adding an item to the already set list.
Change your function myMessage as:
fun myMessage(msg: String) {
val list = message.value
list.add(msg)
message.value = list
}
This will take your existing list then add your new item and then set the value of the live data so that the observe will be called.
I want first fragment to observe information via LiveData comming from second fragment. I tried doing the same but only in 1 Fragment and it worked, but as soon as I want to recieve the data in other fragment it stops working (textView has no text). How should I fix this problem?
SharedViewModel:
class SharedViewModel : ViewModel() {
private val selected : MutableLiveData<Person> = MutableLiveData<Person>()
fun select(person: Person){
selected.value = person
}
fun getSelected(): LiveData<Person>{
return selected
}
}
First fragment:
class FirstFragment : Fragment() {
private lateinit var sharedViewModel: SharedViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
sharedViewModel =
ViewModelProviders.of(this).get(SharedViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_home, container, false)
val textView: TextView = root.findViewById(R.id.text_home)
sharedViewModel.getSelected().observe(viewLifecycleOwner, Observer{
textView.text = it.name
})
return root
}
}
Second Fragment:
class SecondFragment : Fragment() {
private lateinit var sharedViewModel: SharedViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
sharedViewModel =
ViewModelProviders.of(this).get(SharedViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_dashboard, container, false)
val textView: TextView = root.findViewById(R.id.text_dashboard)
val person = Person("John")
val newPerson = Person("Anton")
val button2: Button = root.findViewById(R.id.button2)
val button: Button = root.findViewById(R.id.button)
button2.setOnClickListener {
sharedViewModel.select(person)
}
button.setOnClickListener {
sharedViewModel.select(newPerson)
}
return root
}
}
Class Person:
class Person (var name: String) {
}
I think you are using https://developer.android.com/reference/android/arch/lifecycle/ViewModelProviders#of(android.support.v4.app.Fragment) but instead you should be using https://developer.android.com/reference/android/arch/lifecycle/ViewModelProviders#of(android.support.v4.app.FragmentActivity). That's how sharing works.