James Jordan Taylor
James Jordan Taylor

Reputation: 1718

LiveData Observer not Called

I have an activity, TabBarActivity that hosts a fragment, EquipmentRecyclerViewFragment. The fragment receives the LiveData callback but the Activity does not (as proofed with breakpoints in debugging mode). What's weird is the Activity callback does trigger if I call the ViewModel's initData method. Below are the pertinent sections of the mentioned components:

TabBarActivity

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    initVM()
    setContentView(R.layout.activity_nav)
    val equipmentRecyclerViewFragment = EquipmentRecyclerViewFragment()
    supportFragmentManager
            .beginTransaction()
            .replace(R.id.frameLayout, equipmentRecyclerViewFragment, equipmentRecyclerViewFragment.TAG)
            .commit()
    navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)

}

var eVM : EquipmentViewModel? = null
private fun initVM() {
    eVM = ViewModelProviders.of(this).get(EquipmentViewModel::class.java)
    eVM?.let { lifecycle.addObserver(it) } //Add ViewModel as an observer of this fragment's lifecycle
    eVM?.equipment?.observe(this, loadingObserver)//        eVM?.initData() //TODO: Not calling this causes Activity to never receive the observed ∆
}
val loadingObserver = Observer<List<Gun>> { equipment ->
    ...}

EquipmentRecyclerViewFragment

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    columnCount = 2
    initVM()
}

//MARK: ViewModel Methods
var eVM : EquipmentViewModel? = null
private fun initVM() {
    eVM = ViewModelProviders.of(this).get(EquipmentViewModel::class.java)
    eVM?.let { lifecycle.addObserver(it) } //Add ViewModel as an observer of this fragment's lifecycle
    eVM?.equipment?.observe(this, equipmentObserver)
    eVM?.initData()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    val view = inflater.inflate(R.layout.fragment_equipment_list, container, false)
    if (view is RecyclerView) { // Set the adapter
        val context = view.getContext()
        view.layoutManager = GridLayoutManager(context, columnCount)
        view.adapter = adapter
    }
    return view
}

EquipmentViewModel

class EquipmentViewModel(application: Application) : AndroidViewModel(application), LifecycleObserver {
var equipment = MutableLiveData<List<Gun>>()
var isLoading = MutableLiveData<Boolean>()

fun initData() {
    isLoading.setValue(true)
    thread { Thread.sleep(5000) //Simulates async network call
        var gunList = ArrayList<Gun>()
        for (i in 0..100){
            gunList.add(Gun("Gun "+i.toString()))
        }
        equipment.postValue(gunList)
        isLoading.postValue(false)
    }
}

The ultimate aim is to have the activity just observe the isLoading MutableLiveData boolean, but since that wasn't working I changed the activity to observe just the equipment LiveData to minimize the number of variables at play.

Upvotes: 27

Views: 29361

Answers (5)

Cenker Canbulut
Cenker Canbulut

Reputation: 41

Just for those who are confused between definitions of SharedViewModel vs Making two fragments use one View Model:

SharedViewModel is used to share 'DATA' (Imagine two new instances being created and data from view model is being send to two fragments) where it is not used for observables since observables look for 'SAME' instance to take action. This means you need to have one viewmodel instance being created for two fragments.

IMO: Google should somehow mention this in their documentation since I myself thought that under the hood they are same instance where it is basically not and it actually now makes sense.

EDIT : Solution in Kotlin: 11/25/2021

In Your activity -> val viewModel : YourViewModel by viewModels()

In Fragment 1 - >

val fragmentViewModel =
                ViewModelProvider(requireActivity() as YourActivity)[YourViewModel::class.java]

In Fragment 2 - >

val fragmentViewModel =
                ViewModelProvider(requireActivity() as YourActivity)[YourViewModel::class.java]

This Way 2 fragments share one instance of Activity viewmodel and both fragments can use listeners to observe changes between themselves.

Upvotes: 3

Manisha
Manisha

Reputation: 863

When you create fragment instead of getting viewModel object by viewModels() get it from activityViewModels()

import androidx.fragment.app.activityViewModels

class WeatherFragment : Fragment(R.layout.fragment_weather) {

    private lateinit var binding: FragmentWeatherBinding
    private val viewModel: WeatherViewModel by activityViewModels() // Do not use viewModels()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        binding = FragmentWeatherBinding.inflate(inflater, container, false)

        binding.viewModel = viewModel

        // Observing for testing & Logging
        viewModel.cityName.observe(viewLifecycleOwner, Observer {
            Log.d(TAG, "onCreateView() | City name changed $it")
        })
        return binding.root
    }
}

Upvotes: 2

canerkaseler
canerkaseler

Reputation: 7468

Kotlin Answer

Remove these two points in your function if you are using:

  1. = viewModelScope.launch { }
  2. suspend

Upvotes: 0

Pavel Poley
Pavel Poley

Reputation: 5577

To get same reference of ViewModel of your Activity you need to pass the same Activity instance, you should use ViewModelProviders.of(getActivity). When you pass this as argument, you receive instance of ViewModel that associates with your Fragment.

There are two overloaded methods:

ViewModelProvider.of(Fragment fragment)

ViewModelProvider.of(FragmentActivity activity)

For more info Share data between fragments

Upvotes: 44

iamkdblue
iamkdblue

Reputation: 3622

I put this code inside the onActivityCreated fragment, don't underestimate getActivity ;)

if (activity != null) {            
     globalViewModel = ViewModelProvider(activity!!).get(GlobalViewModel::class.java)
    }


globalViewModel.onStop.observe(viewLifecycleOwner, Observer { status ->
            Log.d("Parent Viewmodel", status.toString())
        })

This code helps me to listening Parent ViewModel changes in fragment.

Upvotes: 3

Related Questions