ViewModelProviders is not working inside my Fragment
Asked Answered
C

5

8

This is what I'm trying to do.

  • Set an ArrayList of object inside a Fragment
  • Get that array from an observer within the FragmentActivity container (the activity that hosts all the fragments)

So, What I have done is the following.

First I created the SharedViewModel from where I will set and get the data from :

SharedViewModel.kt

class SharedViewModel: ViewModel() {

    var personArrayObj:MutableLiveData<ArrayList<Person>> = MutableLiveData()

    fun setPersonArray(personArray:ArrayList<Person>){
        personArrayObj.value = personArray
    }

    val getPersonArray:LiveData<ArrayList<Person>>
    get() = personArrayObj

}

Now , I instantiate this ViewModel inside the MainActivity that hosts all the fragments creation, so , I can get the ArrayData from wherever Fragment its set :

MainActivity.kt

class MainActivity: FragmentActivity(){

private lateinit var viewModel:SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        viewModel = ViewModelProviders.of(this).get(SharedViewModel::class.java)
        viewModel.getPersonArray.observe(this, Observer { it:ArrayList<Person>
            Log.d("Array:",""+it)
        })
    }

}

Then the last thing I need to do is to set the value of that Array from whatever Fragment I want, and then the host Activity will be triggered if the value has changed, so, to do this, I simple do a viewModel.setValue(personArray) at my desired Fragment .

The Problem

When I try to instantiate the SharedViewModel inside any fragment I get this problem :

enter image description here

I tried to assert !! for the null, use let too, but is not working and I wonder from where it comes that FragmentActivity required

If I use viewLifecycleOwner instead of activity it says to me that it cant be called either with Fragment or FragmentActivity ?

Any clues?

Thanks .

Cochise answered 28/6, 2019 at 17:24 Comment(0)
A
26

Fragments offer a helper method for retrieving a non-null Activity - requireActivity(). Use that instead of activity:

viewModel = ViewModelProviders.of(requireActivity()).get(SharedViewModel::class.java)

As an alternative, you can include the fragment-ktx dependency in your app and use the by activityViewModels() Kotlin property extension instead of using ViewModelProviders.of() at all:

val viewModel: SharedViewModel by activityViewModels()
Afflict answered 28/6, 2019 at 17:46 Comment(6)
Using requireActivity() is more or less equivalent of using activity!!. The app will crash. Either on IllegalStateException or a NPE.Jackinthepulpit
@MarošŠeleng - from a lifecycle method such as onViewCreated() as it is in this case, it'll never crash, so whether you suppress the lint warning, use !! or use requireActivity() you'll get the same runtime behavior for all three.Afflict
That's true, but you do not mention any of that in your answer, which, in general, makes it a bad practice. I think we all know, how big hell can Fragment's lifecycle be, so why not write it safe all the time?Jackinthepulpit
@MarošŠeleng - requireActivity() is absolutely safe in onViewCreated() and that is why my answer points it out as the correct way of getting a non-null activity instance when you, as a developer, can guarantee the activity must be non-null.Afflict
Also, you can call this inside that method: viewModel = ViewModelProviders.of(this).get(SharedViewModel::class.java) inside fragment, this is pointer to owner.Overripe
@Overripe - that does something totally different by creating a ViewModel scoped to that individual fragment and not scoped to the entire Activity as passing requireActivity() as the owner does.Afflict
M
3

ViewModelProviders.of() is deprecated.So,we can initialize viewmodel like below.

 viewModel = ViewModelProvider(requireActivity()).get(SampleViewModel::class.java)
Mattie answered 18/1, 2021 at 12:10 Comment(0)
C
1

Ok, I was not seeing it

I was making an instance of the viewModel inside my Fragment, and another one, inside my MainActivity, so doing this, we prevent the observer to work.

To solve this, I just made a simple method that returns the instance that I made inside my MainActivity

 fun getViewModelInstance():SharedViewModel = viewModel

Then I just call that instance from any Fragment to work with it

(activity as MainActivity).getViewModelInstance().setPersonArray(personArray)
Cochise answered 28/6, 2019 at 17:41 Comment(3)
When you use ViewModelProvider.of() and pass in your activity, you are going to get the exact same instance back that was in the Activity, so you had the right idea. It is only if you pass in this from within the Fragment that you create a brand new ViewModel tied to the Fragment.Afflict
You don't want to do that, if you want to use the same one as in the activity, just use the activity scope.Leadership
Very helpful. Was stuck here as well. Thank you geeks!Paulettepauley
Q
0

Basically, issue is that inside your fragment class activity context would be nullable and ViewModelPorviders require Non-null context to work with.

One solution would be making your ViewModel object nullable (because your activity is nullable) inside your fragment like something below:

var viewModel: SharedViewModel? = null

then during initialization,

viewModel = activity?.let { ViewModelProviders.of(it).get(SharedViewModel::class.java) }
Questionary answered 28/6, 2019 at 17:31 Comment(0)
K
0

Put the thing in onActivityCreated() instead of onCreateView()... That will solve the issue of getActivity() nullable... Because according to the lifecycle of a fragment, onActivityCreated() is after onCreareView()... And the activity is created on the call of onActivityCreated()... So, the getActivity() will return the activity just created

Kowatch answered 27/10, 2019 at 1:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.