I have one fragment where I update a total integer in my sharedViewModel, this is the shopsFragment
class ShopFragment : Fragment(), AppBarLayout.OnOffsetChangedListener {
private val model: SharedViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.updateTotal(200)
}
}
Now, my other fragment that I need this data to be shared between, is a BottomSheetDialogFragment , in this Fragment I get an instance of the sharedViewModel by doing this
class CartBottomSheet: BottomSheetDialogFragment() {
private val model: SharedViewModel by viewModels ({requireParentFragment()})
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.getTotal().observe(viewLifecycleOwner, Observer { total ->
sheet_total_price.text = "$$total.00"
})
}
Now, when I try to get the 200 that I posted in the other Fragment, it shows 0, and that means that the instance of that sharedViewModel is a new instance because it returns 0 because my viewmodel instance initializes a common shared total with 0
class SharedViewModel: ViewModel() {
private val totalData = MutableLiveData<Int>()
private var sharedTotal = 0
fun updateTotal(total:Int){
sharedTotal = total
totalData.value = sharedTotal
}
fun getTotal():LiveData<Int>{
return totalData
}
Now, my question is, do I need to pass as a bundle to the BottomDialogFragment this instance of the sharedViewmodel to work with, or is there any way to get the same instance to get the value of total
Thanks
You can set ShopFragment as targetFragment for the CartBottomSheet fragment. In this way when you create the Shared VM you will get the same instance. Basically if you put this together you can achieve it by the code below :-
class CartBottomSheet: BottomSheetDialogFragment() {
private val model: SharedViewModel?=null
companion object {
fun show(fragmentManager: FragmentManager, parentFragment: Fragment) {
val sheet = CartBottomSheet()
sheet.setTargetFragment(parentFragment, 13)
sheet.show(fragmentManager, sheet.tag)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
targetFragment?.let {
// Create model here with it
}
}
}
So now for opening sheet you should call
CartBottomSheet.show(fragmentManager!!, this)
Related
I am currently reading data from a Bluetooth Sensor, hence the data changes in real-time and continuously changes. I have stored the data in a variable: liveData:ByteArray
Now I am trying to send liveData from MainActivity to Sensordisplayfragment.
UPDATE
Based on #CTD's comment, this is what I have tried, unfortunately I do not have much knowledge on viewModel, and online research is just confusing as there seems to be many methods to implement a viewModel.
In my MainActivity class where variable liveData is stored:
val model:MyViewModel by viewModels()
private fun processLiveData(liveData : ByteArray){
livedata = liveData
model.uploadData(livedata)
}
In MyViewModel.class where the viewModel is at:
class MyViewModel: ViewModel() {
private val realtimedata = MutableLiveData<ByteArray>()
fun uploadData(data:ByteArray){
realtimedata.value = data
}
fun loadData():LiveData<ByteArray>{
return realtimedata
}
}
Finally, in my Sensordisplay fragment where I am fetching the data:
val model:MyViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
model.loadData().observe(viewLifecycleOwner,Observer<ByteArray>{
passandprocessLiveData(it)
})
return inflater.inflate(R.layout.sensordisplay, container, false)
}
override fun onResume(){
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
model.loadData().observe(viewLifecycleOwner,Observer<ByteArray>{
passandprocessLiveData(it)
})
super.onResume()
}
fun passandprocessLiveData(data:Bytearray){
//extract information from data and make
//cardviews move in realtime according to the extracted data
}
Unfortunately,nothing is getting transferred and my cardviews are not moving. I can guarantee there is no error in the moving of the cardview codes. Anyone able to advice on what I can add? Apparently there is an init() function that I need to use.
class MyViewModel : ViewModel() {
private val realtimedata = MutableLiveData<ByteArray>()
val sensorData: LiveData<ByteArray> = realtimedata
fun update(data: ByteArray){
realtimedata.value = data
}
}
class MainActivity: Activity() {
private val viewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bluetoothSensorCallBack { data ->
// Update the realtimedata
viewModel.update(data)
}
}
}
class SensordisplayFragment : Fragment() {
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: MyViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.sensorData.observe(viewLifecycleOwner, Observer<ByteArray> { data ->
// Update the UI
})
}
}
This question already has answers here:
Passing data between a fragment and its container activity
(16 answers)
Closed 1 year ago.
How to correctly transfer data from a fragment to an activity? I do as follows:
1- Create an interface
interface IProfileToActivity {
fun profileInfo(data: AllHeroes.Global)
}
2- Then I inheritance in the activity
class ProfileActivity : AppCompatActivity(), IProfileToActivity {
private lateinit var myBinding: ActivityProfileBinding
override fun profileInfo(data: AllHeroes.Global) {
myBinding.tvUsername.text = data.name
myBinding.tvDivision.text = data.rank.rankName
Log.i("Apex Info 3", data.toString())
}
}
3- sending from a fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(activity as? IProfileToActivity)?.profileInfo(allInfoApexResponse.global)
mHeroesAdapter.heroesList(allAdapterListHero)
}
but nothing happens, why? what did I do wrong?
You need not create an interface here. You can use requireActivity() to get a reference to the parent activity. Using it you can access public fields and functions of you activity.
class ProfileActivity : AppCompatActivity() {
private lateinit var myBinding: ActivityProfileBinding
fun profileInfo(data: AllHeroes.Global) {
myBinding.tvUsername.text = data.name
myBinding.tvDivision.text = data.rank.rankName
Log.i("Apex Info 3", data.toString())
}
}
And in your fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(requireActivity as ProfileToActivity).profileInfo(allInfoApexResponse.global)
mHeroesAdapter.heroesList(allAdapterListHero)
}
There are many ways to pass data from fragment to activity:
Using shared ViewModel.
A ViewModel is used to manage and store UI related data in a
lifecycle conscious way.
~Read more
class SharedViewModel: ViewModel() {
private val currItems: MutableLiveData<List<Item>> =
MutableLiveData<List<Item>>(listOf())
fun getCurrItem(): LiveData<List<Item>> {
return currItems
}
fun sendCurrItems(items: MutableList<Item>) {
currItems.postValue(items)
}
}
class ItemFragment: Fragment() {
private val sharedModel: SharedViewModel by activityViewModels()
}
MainActivity: AppCompactActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val model = ViewModelProvider(this).get(SharedViewModel::class.java)
}
}
In the above class, data is being stored and updated using an MutableList. Thing to be noted here is, the above class is a singleton class, i.e. once it is created, it gets destroyed only when the activity is ended.
Let us assume that an item has to be shared from a ItemFragment to the MainActivity
One callback has to be implemented the MainActivity. For that, one can use an Interface
interface ItemListener{
fun sendItem(item : MutableList<Item>)
}
ItemFragment:
class ItemFragment: Fragment() {
override fun sendItems(items: MutableList<Item>?) {
// Send an Item from here as well as update it
}
// Or just simply call sendItem method.
}
MainActivity:
class MainActivity: AppCompactActivity(){
fun receiveItem(context : Context){
private var mCallback: ItemListener? = null
mCallback = context
}
}
I have 2 fragments that i navigate throught navigation component from Jetpack. When i go back from fragment 2 to fragment 1, i fire an event throught livedata from fragment 2 and fragment1 should be observing. Althought, fragment 1 didnt observed and when i sste a breakpoint to check how many observers liveData has, it say 0.
Code below
class Fragment1 :
Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
myViewModel.selectedItem.observe(viewLifecycleOwner, {
println("Observed")
})
findNavController().navigate(
Fragment1Directions.actionToFragment2()
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
myViewModel = ViewModelProvider(requireActivity()).get(MyViewModel::class.java)
}
private lateinit var myViewModel: MyViewModel
}
class Fragment2 :
Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
myButton.setOnClickListener{
MyViewModel().selectItem(true)
findNavController().navigateUp()
}
}
}
class MyViewModel : ViewModel() {
private val mutableSelectedItem = MutableLiveData<Boolean>()
val selectedItem: LiveData<Boolean> get() = mutableSelectedItem
fun selectItem(value: Boolean) {
mutableSelectedItem.value = value
}
}
It's strange to not have observer for
myViewModel.selectedItem.observe(viewLifecycleOwner, {
println("Observed")
})
when it's called onViewCreated and with viewLifecycleOwner, but it looks like you have a mistake for creating common ViewModel for Fragment1 and Fragment2
Both should use
myViewModel = ViewModelProvider(requireActivity()).get(MyViewModel::class.java) to get same ViewModel for both fragments
LiveData is not observing on child viewpager fragments where the child fragments have one shared viewmodel to access data.
Here FragmentA and FragmentB are part of a viewpager and both of them are sharing one viemodel SharedViewModel.
public class SharedViewModel extends AndroidViewModel { //in Java
private final MutableLiveData<Data> mLiveData = new MutableLiveData<>();
public LiveData<Data> getLiveData() {
return mLiveData;
}
//for updating data through LiveData, using post as and when I get the response from DataSource as shown below.
mLiveData.postValue(response); //getting the response on debugging
}
class FragmentA : Fragment() { //in Kotlin
override fun onCreate(#Nullable savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mViewModel = ViewModelProvider(this).get(SharedViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
....
mViewModel.liveData.observe(viewLifecycleOwner, {
//no call coming in this block so unable to update view
})
}
}
class FragmentB : Fragment() { //in Kotlin
override fun onCreate(#Nullable savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mViewModel = ViewModelProvider(this).get(SharedViewModel::class.java)
}
}
Need some help as to why the live data is not able to observe the changes.
Thanks in advance.
Your ViewModel is not shared. you have named it as sharedViewmodel but the way you are getting an instance of it by passing a Unique Owner it will also be a unique instance.
the correct way of sharing view model is
class SharedViewModel : ViewModel() {
private val myLiveData = MutableLiveData<Data>()
fun getMyLiveDta():LiveData {
return myLiveData
}
}
Now in the first fragment
class MasterFragment : Fragment() {
private lateinit var itemSelector: Selector
// Get instance of viewmodel in fragment like this
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
}
And in the second fragment like this
class DetailFragment : Fragment() {
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.selected.observe(viewLifecycleOwner, Observer<Item> { item ->
// Update the UI
})
}
}
I'm trying to send data from DialogFragment to Fragment using ViewModel but it seems both fragment and Dialog fragment are referencing different instances of ViewModel. so I can't access data . Is there any way I can fix this issue? thanks
Here is my Fragment
#AndroidEntryPoint
class FragmentToReceiveData:BaseFragment(R.layout.fragment_1){
private val viewModel: AddScheduleViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Log.d(TAG, "onViewCreated: $viewModel") // will print ...MyViewModel#62274cc
viewModel.dataFromDialog.observe(viewLifecycleOwner){
//nothing happens
}
}
.
.
.
private fun openDialog(){
val action=FragmentToReceiveDataDirections.actionFragmentToReceiveDataToExampleDialog()
findNavController().navigate(action)
//exampleDialog.show(requireActivity().supportFragmentManager, "alarmDialog") //same issue
}
}
Here is ViewModel:
class MyViewModel #ViewModelInject constructor(){
var dataFromDialog=MutableLiveData<SomeClass>()
fun saveDataFromDialog(data:SomeClass){
dataFromDialog.value=data
}
}
Here is my DialogFragment
#AndroidEntryPoint
class ExampleDialog:DialogFragment() {
val viewModel:MyViewModel by viewModels()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
Log.d(TAG, "onCreateDialog: $viewModel") // will print ...MyViewModel#125436
.
.
.
viewMode.saveDataFromDialog(data)
}
}
P.S: I'm using single activity architecture, So I'm not sure if activityViewModels() is a good idea
in order to share ViewModel between fragments, you can use activityViewModels(). for instance,
class SharedViewModel : ViewModel() {
...
}
class MasterFragment : Fragment() {
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
...
}
}
class DetailFragment : Fragment() {
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
...
}
}
please read more in the android documentation here: https://developer.android.com/topic/libraries/architecture/viewmodel#sharing