1

I'm new to Android development and making an Android map app. Right now, I have three fragments: one is the map, and two are recycler view lists of locations. All of them are in the nav host in my main activity. What I want to do is click the location in the list and it takes me to the map fragment and put a marker on the map.

My idea is to set the view model in my list fragment and get the view model in my map fragment.

My view model:

private val _location = MutableLiveData<Location>()
val location: LiveData<Location> = _location

fun setLocation(location: Location) {
    _location.value = location
}

My click listener in location fragment:

private var clickListenerImpl = object : LocationsAdapter.OnClickListener {
    override fun onNameClick(location: Location) {
        viewModel.setLocation(location)
        view?.findNavController()?.navigate(
            LocationsFragmentDirections.actionLocationsToMap()
        )
    }
}

My on map ready callback:

viewModel.location.observe(viewLifecycleOwner, { location ->
    googleMap.addMarker(
        MarkerOptions()
            .position(LatLng(location.lat, location.lng))
    )
})

Am I using the view model right? I couldn't get my view model data in the map fragment, nor my other list fragment. I tried to use log in both location and map fragment. It seems it sets the view model in location fragment properly, but the map fragment still thinks it's null, so it won't update. Or is there a better way to do this instead of using the view model?


Thanks, Everybody. I really want to give all an upvote, but my reputation is too low to do that.

Although I really want to use ViewModel, I still couldn't get it to work in the activity scope. Maybe when I initiate it in a new fragment, it overwrites its old value? I don't know.

private val viewModel: LocationViewModel by activityViewModels {
    LocationViewModelFactory((requireActivity().application as LocationApplication).repository)
}

My solution is changed to use SafeArgs. It requires an extra step to convert latitude and longitude to float type but gets the job done.

tdyin
  • 11
  • 3

3 Answers3

1

Two ways of achieving sharing ViewModel data between fragments comes to mind.

  1. Use an activityViewModels(). This will keep the ViewModel alive as long as the parent Activity is alive. See Share Data Between Fragments Using ViewModels .

private val model: SharedViewModel by activityViewModels()

Edit: Using by activityViewModels with a ViewModelFactory

private val model: SharedViewModel by activityViewModels() {
   ExampleViewModelFactory(
     repository
   )
}
  1. Second way is to instantiate a ViewModel in the Activity then in each Fragment grab that object to use. Almost like a Singleton.
CJMobileApps
  • 177
  • 1
  • 12
  • Hi, I look this article. I can't use it because I'm also using Room. So I have to initiate ViewModel is by using ViewModel Factory with an argument of my repository class. But I think your second way is a good idea. Let me try it. – tdyin Nov 20 '21 at 21:03
  • I updated the answer with an example of using` activityViewModels()` with a ViewModel Factory. Just pass the same instance of your repository class into the Factory. You can again use the idea of instantiating the repository class in the activity, using a Singleton, and/or if you really want to get advanced a DI framework like Dagger. – CJMobileApps Nov 20 '21 at 21:27
0

Are you using an activity scoped ViewModel? You need to use an activity scoped ViewModel or nav scoped viewModel for this to work. If you use a fragment scoped ViewModel in your map and list fragments. The fragment would get different instances of your ViewModel. In my opinion, I'd simply just pass the location data as a fragment argument instead of sharing a ViewModel.

0

The ViewModel works like this:

  1. Each ViewModel is (shold be) bound to a single LifeCycleOwner, which refers to Fragment or Activity. In other words, Each ViewModel is bound to a single Fragment or Activity.

  2. For sharing (ViewModel's) data between Fragments, use SharedViewModel pattern, the idea is, this ViewModel will be bound to the outer (parent) Activity, and for each its child Fragment you can get this ViewModel instance by using:

     ViewModelProvider(requireActivity()).get(MainViewModel::class.java) 
    
Sam Chen
  • 7,597
  • 2
  • 40
  • 73
  • Hi, because I'm also using Room. I followed this Codelab: https://developer.android.com/codelabs/android-room-with-a-view-kotlin#9. So my ViewModel is bound to an Application like this private val wordViewModel: WordViewModel by viewModels { WordViewModelFactory((application as WordsApplication).repository) } Is this the reason I can't get my instance? – tdyin Nov 20 '21 at 20:57
  • @tdyin This may help: https://stackoverflow.com/questions/65352324/view-model-initialization-using-by-viewmodels-vs-viewmodelproviderthis-ge. – Sam Chen Nov 20 '21 at 21:50
  • Oh, I see. They're the same. Thanks. – tdyin Nov 20 '21 at 22:07