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.
I am having an issue working with Xamarin.Android (please see code below)
MainActivity.cs:
using Android.App;
using Android.Widget;
using Android.OS;
namespace DialogFragmentLife
{
[Activity(Label = "DialogFragmentLife", MainLauncher = true)]
public class MainActivity : Activity
{
private Button _btnShowDF;
private DialogFragmentLife _dfLife;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.Main);
_btnShowDF = FindViewById<Button>(Resource.Id.btnShowDF);
_btnShowDF.Click += _btnShowDF_Click;
}
private void _btnShowDF_Click(object sender, System.EventArgs e)
{
_dfLife = new DialogFragmentLife(this);
_dfLife.Show(FragmentManager, "dfLife");
}
}
}
And the DialogFragmentLife.cs below
using Android.App;
using Android.Content;
using Android.OS;
using Android.Views;
namespace DialogFragmentLife
{
class DialogFragmentLife : DialogFragment
{
private Context _context;
public DialogFragmentLife(Context context)
{
_context = context;
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
base.OnCreateView(inflater, container, savedInstanceState);
RetainInstance = true;
View view = inflater.Inflate(Resource.Layout.DialogFragmentLife, container, false);
return view;
}
public override void OnDismiss(IDialogInterface dialog)
{
base.OnDismiss(dialog);
}
public override void OnPause()
{
base.OnPause();
}
public override void OnStop()
{
base.OnStop();
}
public override void OnHiddenChanged(bool hidden)
{
base.OnHiddenChanged(hidden);
}
public override void OnDestroyView()
{
Dialog dialog = Dialog;
if (dialog != null && RetainInstance)
dialog.SetDismissMessage(null);
base.OnDestroyView();
}
public override void OnDetach()
{
base.OnDetach();
}
public override void OnCancel(IDialogInterface dialog)
{
base.OnCancel(dialog);
}
}
}
Everything works as expected, except for the DialogFragment's lifecycle events when it is being dismissed, or when the devices are rotated (orientation change), none of the LifeCycle events get fired.
As you can see on the code,
OnDismiss
OnPause
OnStop
OnHiddenChanged
OnDestroyView
OnDetach
OnCancel
None of them gets fired. I wonder if anyone out there is having this same issue.
Orientation does not affect dialog fragment lifecycle.
DialogFragment does various things to keep the fragment's lifecycle driving it, instead of the Dialog. Note that dialogs are generally autonomous entities -- they are their own window, receiving their own input events, and often deciding on their own when to disappear (by receiving a back key event or the user clicking on a button).
DialogFragment needs to ensure that what is happening with the Fragment and Dialog states remains consistent. To do this, it watches for dismissing events from the dialog and takes care of removing its own state when they happen. This means you should use Show(FragmentManager, String) or Show(FragmentTransaction, String) to add an instance of DialogFragment to your UI, as these keep track of how DialogFragment should remove itself when the dialog is dismissed.
Also, know that there are certain lifecycle methods that a dependent on some other method to be called for eg: OnDestroy and OnDestroyView are dependent on Dismiss or closing of the by back button or closing of the fragment in any sense, What you are failing to notice here is that on orientation change the lifecycle events of the activity will be called and not the dialog fragment, One fragment does not affect in any way the lifecycle of the other.
For information related to working with lifecycles check this out.This guy has beautifully analyzed every aspect of Android( Activity& Fragment) lifecycle.
onActivityCreated:
Called when the fragment's activity has been created and this fragment's view hierarchy instantiated. It can be used to do final initialization once these pieces are in place, such as retrieving views or restoring state. It is also useful for fragments that use setRetainInstance(boolean) to retain their instance, as this callback tells the fragment when it is fully associated with the new activity instance. This is called after onCreateView(LayoutInflater, ViewGroup, Bundle) and before onViewStateRestored(Bundle).
onAttach: Called when a fragment is first attached to its context. onCreate(Bundle) will be called after this.
onCancel: This method will be invoked when the dialog is cancelled.
onCreate: Called to do the initial creation of a fragment. This is called after onAttach(Activity) and before onCreateView(LayoutInflater, ViewGroup, Bundle), but is not called if the fragment instance is retained across Activity re-creation (see setRetainInstance(boolean)).
Note that this can be called while the fragment's activity is still in the process of being created. As such, you can not rely on things like the activity's content view hierarchy being initialized at this point. If you want to do work once the activity itself is created, see onActivityCreated(Bundle).
If your app's targetSdkVersion is M or lower, child fragments being restored from the savedInstanceState are restored after onCreate returns. When targeting N or above and running on an N or newer platform version they are restored by Fragment.onCreate.
onCreateDialog: Override to build your own custom Dialog container. This is typically used to show an AlertDialog instead of a generic Dialog; when doing so, onCreateView(LayoutInflater, ViewGroup, Bundle) does not need to be implemented since the AlertDialog takes care of its own content.
This method will be called after onCreate(Bundle) and before onCreateView(LayoutInflater, ViewGroup, Bundle). The default implementation simply instantiates and returns a Dialog class.
Note: DialogFragment own the Dialog.setOnCancelListener and Dialog.setOnDismissListener callbacks. You must not set them yourself. To find out about these events, override onCancel(DialogInterface) and onDismiss(DialogInterface).
onDestroyView: Remove dialog.
onDetach: Called when the fragment is no longer attached to its activity. This is called after onDestroy(), except in the cases where the fragment instance is retained across Activity re-creation (see setRetainInstance(boolean)), in which case it is called after onStop().
onDismiss: This method will be invoked when the dialog is dismissed.
onSaveInstanceState: Called to ask the fragment to save its current dynamic state, so it can later be reconstructed in a new instance of its process is restarted. If a new instance of the fragment later needs to be created, the data you place in the Bundle here will be available in the Bundle given to onCreate(Bundle), onCreateView(LayoutInflater, ViewGroup, Bundle), and onActivityCreated(Bundle).
This corresponds to Activity.onSaveInstanceState(Bundle) and most of the discussion there applies here as well. Note, however: this method may be called at any time before onDestroy(). There are many situations where a fragment may be mostly torn down (such as when placed on the back stack with no UI showing), but its state will not be saved until its owning activity actually needs to save its state.
onStart: Called when the Fragment is visible to the user. This is generally tied to Activity.onStart of the containing Activity's lifecycle.
onStop: Called when the Fragment is no longer started. This is generally tied to Activity.onStop of the containing Activity's lifecycle.
Referred from here
After more scouring around the net, i manage to get some events fired now by doing the following:
On MainActivity.cs i changed
using Android.App;
to
using Android.Support.V7.App;
Then, on DialogFragmentLife.cs i changed
using Android.App;
to
using Android.Support.V4.App;
Lastly, back on MainActivity.cs, instead of showing the DialogFragment by using
_dfLife.Show(FragmentManager, "dfLife");
i changed it to use the SupportFragmentManager as follows
_dfLife.Show(SupportFragmentManager, "dfLife");
From there on, the OnDestroyView() is now fired when i rotate the screen, also when dismissing the Dialog Fragment
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;
}
I have an Activity called BaseActivity, hosting multiple fragments.
The BaseActivity has a public field Object owhich is accessed by the fragments by calling Object o = ((BaseActivity) getActivity()).o; This is initialized in the fragments onCreate.
This works but I have problems with runtime configuration changes. It seems that the Fragments onCreate is called before the BaseActivitys onCreate, so I cant retain the Object from the Bundle i saved in onSaveInstanceState.
Is there a way I can make sure the acitivty can retain its object from the saved Bundle before the Fragment tries to access it?
Try this ..
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Object o = ((BaseActivity) getActivity()).o;
}
This makes sure that the activity's onCreate has completed executing.