AndroidViewModel is used to access Application context. I'm trying to access Activity's FragmentManager without passing it explicitly:
class FooViewModel(app: Application) : AndroidViewModel(app) {
private val fm = (app.applicationContext as Activity).fragmentManager
..
}
Getting error, unable to cast Context to Activity.
Question: is there any way to get FragmentManager inside AndroidViewModel without passing it explicitly?
I think short answer will be "no, there is no way", because Application context is in no aware of FragmentManager.
FragmentManager is an object that subclasses of FragmentActivity may have. Application is not a subclass of FragmentActivity.
Another question would be, why would you ever need a FragmentManager instance inside your ViewModel? Most possibly you should delegate view-related stuff to handle to other unit other than ViewModel (e.g. Activity, Fragment). Keep in mind, that this ViewModel would be retained over configuration change, thus if you keep a reference to the FragmentManager inside your ViewModel, you'd be leaking your Activity instance.
Related
I've creating a test activity that updates some text in my MyViewModel.
I'd like to observe these changes in a Fragment, but when I use
MyViewModel myViewModel = new ViewModelProvider(this).get(MyViewModel.class);
it gives me a different instance of MyViewModel than that used in the activity, which results in my onChanged() callback in the fragment not being called.
Only when I modify that same fragment code to
HomeViewModel homeViewModel = new ViewModelProvider(getActivity()).get(HomeViewModel.class);
does the fragment get the same instance of MyViewModel as the activity - so onChanged() is successfully called.
However, I'm not sure if using getActivity() as the ViewModelStoreOwner is the proper way of doing things as I haven't seen this in any examples anywhere. I'm wondering if there might be a better ViewModelStoreOwner I should be using in this instance?
I'm wondering if there might be a better ViewModelStoreOwner I should
be using in this instance?
You should use activity instance for sharing the same instance among fragments in the same activity.
Both Activity and Fragment implements their own ViewModelStoreOwner interface and implements the getViewModelStore() method. getViewModelStore() provide the ViewModelStore instance which is used to store the viewmodel objects, created by the ViewModelProvider.
Note: ComponentActivity implements the ViewModelStoreOwner interface and FragmentActivity (parent of AppCompatActivity) inherits the implementation.
So both Activity and Fragment have specific implementation for the ViewModelStoreOwner interface methods and store the viewmodel instance as per the lifecycle of the objects(including the configuration changes).
Since fragments belong to activity get the same activity instance so using the getActivity() will result in using the ViewModelStoreOwner object of the activity. To share the objects among fragments, simply use the activity instance for creating ViewModelProvider which will use the same ViewModelStoreOwner in all fragments hence will return the persisted object of viewmodel (if created before).
Having an Activity which does as little as possible has become a "best practice" for some time now, so the scenario of an Activity and a Fragment which need access to the same ViewModel instance may not be covered by many guides.
But "there is no rule without an exception", and your scenario is similar to the one where an Activity has two Fragments which need to share data.
In this case, one uses the Activity scope for the ViewModel to make sure every component will have access to the same instance. See also the section "Share data between fragments" in the View Model Overview at developer.android.com
Sorry for the stupid question but upon reading about ViewModel i came across
randomViewModel = ViewModelProviders.of(this).get(RandomViewModel::class.java)
I just want to know what the of() is in general. Is it just a function used by the providers? Or is it a special operator?
Thanks
#Deprecated
#NonNull
#MainThread
public static ViewModelProvider of(#NonNull Fragment fragment) {
return new ViewModelProvider(fragment);
}
As we can see by viewing the source code of ViewModelProviders, of() is basically an extension function of ViewModelProvider that returns a new NonNull ViewModelProvider object with the parameter fragment/activity and locks it on the MainThread. Basically it's a fancy way of writing ViewModelProvider(fragment) with extra steps.
But be aware that of() is deprecated, you now initialise a ViewModel like this:
ViewModelProvider(requireActivity(),ViewModelFactory(Database.getDatabase(requireActivity()))).get(ViewModelClass::class.java)
ViewModelProviders.of(this).get(RandomViewModel::class.java)
ViewModelProviders.of(this)
It is a static function that takes current context to retain the ViewModel scope. In this case Current activity is gonna be the context for which ViewModel scope will be retained.
.get(ViewModel::class)
It does two things
If the ViewModel is available it will return the ViewModel instance.
Otherwise, it will create and return the new instance.
The of() method here is a method inside the ViewModelProviders class which just creates a ViewModelProvider object, which retains ViewModels while the scope you have given eg Activity, Fragment is alive.
I Have Fragment A that calls Fragment B or Fragment C.
B and C uses a ViewModel provided by A.
For while, I'm passing the View as parameter on constructor:
FragmentB(val viewModel: ViewModel)
FragmentC(val viewModel: viewModel)
But this way, not is a good solution, because the app is crashing when try to reopen the fragment.
Could not instantiate the fragment
What the best way to do this?
I thought create a newInstance method and pass the viewModel as argument on Bundle, but how I would transform this viewModel in Parcelable or Serializable?
You are probably trying to create custom Fragment constructor which is not allowed in Android.
I assume your ViewModel was created inside of fragment A using method
ViewModelProviders.of(this)
ViewModel created this way should be used only inside of this fragment(It won't crash if you i.e. pass it through Object or Singleton but it's not supposed to be used that way).
Better solution is to put all of these fragments A, B, C inside one activity.
This way you can call
ViewModelProviders.of(requireActivity())
and get the same instance of the ViewModel in all of the fragments. So if you get the ViewModel using this method in your fragment A, put there some values and then replace fragment A with fragment B in the same activity, you can call ViewModelProviders.of(requireActivity()) again get access to the same instance of your ViewModel and retrieve the values.
In the documentation of the android.support.v4.app.Fragment class (not the framework's class) for the getActivity() method stated that the returned value may be null if the Fragment is associated with a Context.
How an Fragment can be associated with a Context? Is not the FragmentManager the only way to attach an Fragment to something? But the FragmentManager can be obtained only from Activity. Or not?
How an Fragment can be associated with a Context?
You must have heard of FragmentHostCallback. If you haven't check out the link.
In a simple way, it is an integration point with a Fragment Host. When I say Fragment Host, It is an object that can hold Fragments. For example an Activity. In order to host a fragment - one must implement FragmentHostCallback.
However, I haven't come up with any ideas about how Fragment can be implemented in non-activity objects. Will see in future may be...
So that way, getActivity() will return null on non-activity objects.
PS,
Always go for getContext() if you are requiring context rather than activity
As I understand, you need the context inside of a fragment.. If so have you checked the method getContext() method inside of a fragment?
Also getActivity() can be null if you are referencing it when the fragment is not attached to an activity. Have a check of the fragment lifecycle to learn more.
Hope I helped
I am developing an android application in which I need to launch an Fragment from the class which doesnot extend FragmentActivity . I am using support v4 package.
I will having the context in my current class. Can any one help me how to create FragmentManager object from current context?
public ToolBarGenerator(Context c)
{
context = c;
}
FragmentManager fm = (FragmentActivity)context.getSupportFragmentManager(); //getting error at this line.
//Called like the above
new ToolBarGenerator(getActivity())
If the Context you have is from the FragmentActivity, you can just cast it to FragmentActivity, though I don't know for sure if that's your case.
That said, it sounds like what you're doing is bad practice. I would keep all Fragment transactions within the FragmentActivity class. If another class needs to request a different Fragment shown, you should use callbacks or something similar.
Just answered this in another thread, but this may help others. I was in a situation perhaps similar to yours with no precise answers for how to work around having an Adapter class separate from the FragmentActivity class, while needing to access the getSupportFragmentManager() within the Adapter.
Passing the context didn't work because it doesn't allowing casting of the FragmentActivity (for reasons I still don't understand).
But I gave up and just saved access to the manager itself and call it directly:
public ViewPagerAdapter(Context context, FragmentManager fm, Fragment f) {
super(fm);
_fm = fm; // declared in the class "private FragmentManager _fm;"
_context=context;
}