Should an activity-scoped ViewModel in a fragment be retrieved in onCreateView or onActivityCreated?
viewModel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
viewModel.getText().observe(getViewLifecycleOwner(), new Observer<CharSequence>() {
#Override
public void onChanged(#Nullable CharSequence charSequence) {
editText.setText(charSequence);
}
});
I see both examples online. Some say getActivity() can return null in onCreateView, some say this never happens.
You should use onViewCreated() instead of onActivityCreated().
onActivityCreated() is called when the activity's onCreate() method returns.
Activity is attached as a host to your fragment in both onCreateView() and onViewCreated() already;
Using onViewCreated() makes more sense to subscribe to data source once the view hierarchy has been created.
Related
Is it safe to observe LiveData inside onActivityCreated or onViewCreated, isn't it adds new observer to the LifecycleOwner and multiple observers will be active in the same Fragment?
For example when we navigate from Fragment A to Fragment B then navigate back to Fragment A, onActivityCreated \ onViewCreated in Fragment A will be called twice and viewModel.liveData.observe() will be called twice.
It depends on which Lifecycle object are you going to pass to your Observer.
Let's say you subscribe your Observer in Fragment's onCreate method. This solve the problems with observe called twice, but if the user press back button the Fragment A's onCreate won't be called so no Observer subscribed. Or even in some cases your observe method can be called while your Fragment is now active and leads to crash.
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
viewModel.liveData.observe(this, Observer { myData -> {} });
}
The second option you have is to subscribe in onCreateView() or onViewCreated(). The problem with these two options is that it will get called everytime the Fragment A gets recreated and at the end you will end up with more than one observers.
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
viewModel.liveData.observe(this, Observer { myData -> {} });
}
So how we can solve those issues? The answer is simple: use viewLifecycleOwner.
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
viewModel.liveData.observe(viewLifecycleOwner, Observer { myData -> {} });
}
Because in this way you observe your LiveData bound to Fragment view's lifecycle.
So it is safe to observe LiveData in onCreateView or onViewCreated and in onActivityCreated since it'se called after onCreateView by documentation: Link (but deprecated in API Level 28 and not encouraged to be used anymore).
P.S. A really helpful talk on this problem in Google IO 2018: Youtube Link
When my activity containing viewpager is killed by system in background and then restores its state, fragments are correctly created and viewpager adapter can also point to them correctly.
But when I get a fragment reference and try to access its fields, they are all null (checked by using breakpoint).
I checked this by placing breakpoints in fragment onCreateView() and in my activity button's clickListener.
((WelcomeFragment)homeActivityFragmentPageAdapter.getItem(POSITION_HOME)).setdata(myData);
Now this method will through null pointer exception since setdata(data) is internally accessing arraylist field of fragment.
This creates a problem for me since, my activity has to continuously feed network data to the fragment by calling its public method (as suggested by documentation).
How to insure that after state restored; correct instance is pointed in my activity.
Try to use instantiateItem adapter method instead getItem.
((WelcomeFragment)homeActivityFragmentPageAdapter.instantiateItem(mViewPager, POSITION_HOME)).setdata(myData);
Method getItem is overrided method, and common use is creation of child fragments.
EDIT:
In case of the question's scenario, you also need to store the state of FragmentStatePagerAdapter manually:
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState
.putParcelable("pages",homeActivityFragmentPageAdapter.saveState());
super.onSaveInstanceState(savedInstanceState);
}
Then you can retrieve the state in oncreate:
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
homeActivityFragmentPageAdapter.restoreState(savedInstanceState.getParcelable("pages"),this.getClassLoader());
welcomeFragment = (WelcomeFragment) homeActivityFragmentPageAdapter.instantiateItem(mViewPager, POSITION_HOME);
}
else { //simply create a new instance here}
homeActivityFragmentPageAdapter.addFragmentToAdapter(welcomeFragment);
homeActivityFragmentPageAdapter.notifyDataSetChanged();
}
Hello so I am trying to pass an array list from my activity to the fragment and this is what I did :
FirstActivity :
AdminInterface instanceForInterface;
OnCreate
//
System.out.println(results.size) ; //works fine
instanceForInterface.onDataRecieved(results); // here I am getting the exception
//
public interface AdminInterface {
void onDataRecieved(ArrayList <Result> response);
}
public void setInterface(UserFragment anInterface) {
this.instanceForInterface = anInterface;
}
Fragment
OnActivityCreated
((FirstActivity) getActivity()).setInterface(this);
#Override
public void onDataRecieved(ArrayList<Result> response) {
processData(response);
}
Exception
Attempt to invoke interface method 'void **************.onDataRecieved(java.util.ArrayList)' on a null object reference
What I think :
I am calling this line
instanceForInterface.onDataRecieved(results); in OnCreate()
before the initialisation of
((FirstActivity) getActivity()).setInterface(this); in OnActivityCreated()
Solution Please ??
Thank You
The problem is that your fragment's onActivityCreated() method is invoked after your activity's onCreate() method.
The smallest change you can make to achieve the behavior you want is to use the onResumeFragments() method in your activity. That is, delete the line instanceForInterface.onDataRecieved(results); from your onCreate and add this code:
#Override
protected void onResumeFragments() {
super.onResumeFragments();
instanceForInterface.onDataRecieved(results);
}
onResumeFragments() will be invoked by the system after both your activity's onCreate() and your fragment's onActivityCreated() methods.
That being said, chances are quite good that you would be better off with a different approach entirely. For instance, you could have your activity expose a getter for results and have your fragment retrieve the results to work with (rather than have your activity store a reference to your fragment).
Further reading about the Activity and Fragment lifecycles:
https://developer.android.com/guide/components/activities/activity-lifecycle.html
https://developer.android.com/guide/components/fragments.html
In my activity I have a check for savedInstanceState, making sure I am not creating multiple fragments
But my question is should I have similar checks in Fragment's onCreate() and onCreateView()
Because when I rotate screen Fragment's onCreate() and onCreateView() are called everytime.
Question is, Is it OK for these 2 methods to re-do there job after everyscreen rotation or they should have a savedInstanceState check as well.
Right now my onCreate() makes a service call and onCreateView inflates a view (Recyclerview)
When an activity or Fragment is recreated, the onCreate() method is first fired, followed by the onRestoreInstanceState() method, which enables you to retrieve the state that you savedpreviously in the onSaveInstanceState() method through the Bundle object in its argument:
# Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
//---retrieve the information persisted earlier---
String ID = savedInstanceState.getString(“ID”);
}
I have a main fragment with a viewpager inside it. This viewpager has 2 pages (list fragments). When I start the activty, the main fragment is shown and I also show the first paged fragment. This paged fragment displays data from a db using AsyncTask.
In the main fragment I have:
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
onPageSelected(0);
}
#Override
public void onPageSelected(int position) {
Fragment fragment = (Fragment) pagerAdapter.instantiateItem(viewPager, position);
if (fragment instanceof IPagedFragment) {
((IPagedFragment) fragment).onShown(getActivity());
}
}
And the interface is:
public interface IPagedFragment {
void onShown(FragmentActivity activity);
}
The first issue I have is that I have to pass the activity as a parameter because when onShown gets called, the activity is still null.
Furthermore, the paged fragments use progressbar logic similar to the LoginActivity sample. I also get the following exception:
IllegalStateException: Fragment PagedFragment1{4201f758} not attached to Activity
at android.support.v4.app.Fragment.getResources(Fragment.java:620)
So what is the correct stage to start retrieving data from db once the paged fragment is fully available to the UI?
Issues like yours is the reason some developers are starting to question if fragments are really that good or useful.
Also "the correct" is debatable as you can do it in a variety of places and different developers will give you different answers, But let me try to supply you some useful info.
The attach/detach callbacks:
public void onAttach(Activity activity);
public void onDetach();
between those two methods any call to getActivity() will return the non-null activity the fragments is connected to. You can override them and use a private boolean isAttached to keep track of that call.
Also useful is the:
public void onActivityCreated (Bundle savedInstanceState)
this method is called AFTER the Activity.onCreate method. That is very important if you rely on some initialisation that happened there.
Also it's important to remember that on the moment the fragment transaction happens, the Fragment.onCreate happens after the Activity.onCreate and during rotation it happens before it.
As a general rule of thumb I use the Fragment.onStart() / Fragment.onStop() for getting/listening to data. On those calls, all the UI have been created, the fragment is attached to the activity and those callbacks don't get called if there's a dialog/popup (pause/resume does)
From the documentation:
public void onActivityCreated (Bundle savedInstanceState)
[...] tells the fragment when it is fully associated with the new activity instance.
source: http://developer.android.com/reference/android/app/Fragment.html#onActivityCreated(android.os.Bundle)
To get the reference of your activity, create a local object of fragmentActivity and get your activity reference as shown below.
private FragmentActivity fragmentActivity;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
fragmentActivity=activity;
}