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.
Related
I have two fragments that share information with each other, in the first one I have an edit text and button widget. The second fragment is just a listview. When the user clicks the button, it displays whatever is in the edit text widget in the second fragment.
So if the user enters the text study and clicks the button the second fragment will display
Study
If the user then enters the text eat and clicks the button, the second fragment will display
Study
Eat
I am having so issues with displaying the texts
So far this is what I have done
class FirstFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
viewModel = activity?.run { ViewModelProvider(this)[MyViewModel::class.java]
} ?: throw Exception("Invalid Activity")
val view = inflater.inflate(R.layout.one_fragment, container, false)
val button = view.findViewById<Button>(R.id.vbutton)
val value = view.findViewById<EditText>(R.id.textView)
button.setOnClickListener {
}
return view;
}
}
class SecondFragment : Fragment() {
lateinit var viewModel: MyViewModel
#SuppressLint("MissingInflatedId")
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
viewModel = activity?.run { ViewModelProvider(this)[MyViewModel::class.java]
} ?: throw Exception("Invalid Activity")
val view = inflater.inflate(R.layout.page3_fragment, container, false)
val valueView = v.findViewById<TextView>(R.id.textView)
return view
The problem I am having is how to display the texts
If I undestand you correctly, you want to share data between fragments? If yes, you can do that with "shared" viewModel. For example:
class FirstFragment : Fragment() {
private var _binding: FragmentFirstBinding? = null
private val binding get() = _binding!!
private val sharedViewModel by activityViewModels<SharedViewModel>()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentFirstBinding.inflate(inflater, container, false)
binding.buttonChangeFragment.setOnClickListener {
/*
You can change data here, or in navigateWithNavController() from
activity (You already have an instance of your viewModel in activity)
*/
sharedViewModel.changeData(binding.myEditText.text.toString())
if (requireActivity() is YourActivity)
(requireActivity() as YourActivity).navigateWithNavController()
}
return binding.root
}
}
class SecondFragment : Fragment() {
private var _binding: FragmentSecondBinding? = null
private val binding get() = _binding!!
private val sharedViewModel by activityViewModels<SharedViewModel>()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentSecondBinding.inflate(inflater, container, false)
binding.secondFragmentText.text = sharedViewModel.someData.value
return binding.root
}
}
and your activity:
class YourActivity: AppCompatActivity() {
private lateinit var binding: YourActivityBinding
private lateinit var appBarConfiguration: AppBarConfiguration
private val sharedViewModel: SharedViewModel by lazy {
ViewModelProvider(
this
)[SharedViewModel::class.java]
}
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = YourActivityBinding.inflate(LayoutInflater.from(this))
setContentView(binding.root)
navController = this.findNavController(R.id.nav_host_fragment)
appBarConfiguration = AppBarConfiguration(navController.graph)
}
/*
This function is just for test
*/
fun navigateWithNavController() {
navController.navigate(R.id.secondFragment)
}
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(navController, appBarConfiguration)
}
}
And your viewModel should look something like this:
class SharedViewModel : ViewModel() {
private val _someData = MutableLiveData("")
val someData: LiveData<String>
get() = _someData
fun changeData(newData: String?) {
_someData.value = newData ?: _someData.value
}
}
Your view model should have a backing list of the entered words. When a word is added, the list can be updated, and in turn you can update a LiveData that publishes the latest version of the list.
class MyViewModel: ViewModel() {
private val backingEntryList = mutableListOf<String>()
private val _entryListLiveData = MutableLiveData("")
val entryListLiveData : LiveData<String> get() = _entryListLiveData
fun addEntry(word: String) {
backingEntryList += word
_entryListLiveData.value = backingEntryList.toList() // use toList() to to get a safe copy
}
}
Your way of creating the shared view model is the hard way. The easy way is by using by activityViewModels().
I also suggest using the Fragment constructor that takes a layout argument, and then setting things up in onViewCreated instead of onCreateView. It's less boilerplate code to accomplish the same thing.
In the first fragment, you can add words when the button's clicked:
class FirstFragment : Fragment(R.layout.one_fragment) {
private val viewModel by activityViewModels<MyViewModel>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val button = view.findViewById<Button>(R.id.vbutton)
val value = view.findViewById<EditText>(R.id.textView)
button.setOnClickListener {
viewModel.addEntry(value.text.toString())
}
}
}
In the second fragment, you observe the live data:
class SecondFragment : Fragment(R.layout.page3_fragment) {
private val viewModel by activityViewModels<MyViewModel>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val valueView = view.findViewById<TextView>(R.id.textView)
viewModel.entryListLiveData.observe(viewLifecycleOwner) { entryList ->
valueView.text = entryList.joinToString(" ")
}
}
}
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)
I have tried this problem all days long and still cannot be resolved.
I am writing an app which has three fragments, connected by Navigation Component (with only one activity).
Fragment A
Fragment B
Fragment C
And I observed one LiveData variable from ViewModel in these three fragments.
But only Fragment B is responding. The other two are not.
I have read almost all similar problems from this stack over flow. But still can't see the answer.
Please suggest me.
I have tried removing the observer from FragmentB (which is ok with observing Live Data). But the other two is not still observing)
Here is my code:
Fragment B: (Observe Working)
class ServerFragment : Fragment() {
private lateinit var binding: ServerLayoutBinding
private val vm: DataModel by viewModels()
private lateinit var devices_observer: Observer<MutableList<ClientSocket>>
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = ServerLayoutBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
var client_list_adapter = ClientListAdapter(layoutInflater)
binding.clientListRcv.apply {
layoutManager = LinearLayoutManager(this.context)
addItemDecoration(
DividerItemDecoration(
this.context,
DividerItemDecoration.VERTICAL
)
)
adapter = client_list_adapter
}
client_list_adapter.submitList(vm.clientDevices.value?.toList())
vm.clientDevices.observe(viewLifecycleOwner, Observer {
client_list_adapter.submitList(vm.clientDevices.value?.toList())} )
}
}
Fragment C: (Observe Not Working)
class FileListFragment: Fragment() {
private val vm: DataModel by viewModels()
private lateinit var binding: FileListBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding=FileListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//Here I am populating my recycler view with ViewModel data
//And ViewModel live dat is obeserving here and
vm.clientDevices.observe(viewLifecycleOwner, Observer{
///Here UI updating work will be done. But did'not responded. Here is the problem point I think.
Toast.makeText(context, "OKKK", Toast.LENGTH_SHORT).show()
})
}
}
My recyclerview doesn't udpate with the livedata when I remove an
item.
The function jokeisclicked() opens up a dialog for the user which
can choose to edit or delete an item of the room database.
The delete completes, but only when I refresh the tab. How can I complete the delete without refreshing the tab?
class DashboardFragment : Fragment() {
private lateinit var dashboardViewModel: DashboardViewModel
lateinit var binding: FragmentDashboardBinding
lateinit var adapter: JokeRecyclerViewAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
//init repo and viewmodel
val dao: JokeDao = RoomDB.getInstance(requireContext()).JokeDAO
val jokerepo = JokeRepo(dao, RetrofitBuilder.jokeservice)
val factory = DashboardViewModelFactory(jokerepo)
dashboardViewModel = ViewModelProvider(this, factory).get(DashboardViewModel::class.java)
//init binding
binding = FragmentDashboardBinding.inflate(inflater, container, false)
binding.lifecycleOwner = this
//recycler observe from livedata
dashboardViewModel.jokes.observe(viewLifecycleOwner, {
binding.recyclerview.layoutManager = LinearLayoutManager(this.requireContext())
adapter = JokeRecyclerViewAdapter(it, { selected: Joke -> jokeIsClicked(selected) })
binding.recyclerview.adapter = adapter
})
return binding.root
}
fun jokeIsClicked(joke: Joke) {
//show pop up dialog
val dialog = PopUpFragment(joke)
getFragmentManager()?.let { dialog.show(it, "popUpDialog") }
}
}
here is the popupfragment class
class PopUpFragment(private val selectedJoke : Joke) : DialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
//init viewmodel
val dao = RoomDB.getInstance(this.requireContext()).JokeDAO
val jokerepo = JokeRepo(dao,RetrofitBuilder.jokeservice)
val factory = PopUpViewModelFactory(jokerepo)
val popupViewModel : PopUpFragmentViewModel = ViewModelProvider(this,factory).get(PopUpFragmentViewModel::class.java)
//init binding
val binding = FragmentPopUpBinding.inflate(inflater,container,false)
//edit clicked
binding.editjoke.setOnClickListener{
}
//delete clicked
binding.deletejoke.setOnClickListener {
//TODO
popupViewModel.deleteJoke(selectedJoke)
this.dismiss()
}
return binding.root
}
}
I think you dont update value of livedata at viewModel. Could you share viewmodles also ? If dialogViewModel removes joke from database than you have to return at #Dao's method LiveData or Flow.
Any way don't set adapert and layout manager at observe method - it's point less. For updating list at adaper you could create method and call in it notifiDataSetChanged() or (the best way of handling changes at adaper) diffUtils.
You'll need to do 2 things:
1 - Remove the data from your variable i.e if you're storying it within a an array then use array = ArrayUtils.remove(array, index);
2 - run .notifyDataSetChanged() on your RecyclerView's Adapter in order refresh the view based on the values of the new data set.
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.