Xamarin.Android DialogFragment some LifeCycle Events not firing - android

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

Related

Is it safe to observe LiveData inside onActivityCreated or onViewCreated

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

Android: on screen rotation what should Fragment's onCreate() and onCreateView() do

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”);
}

When is fragment finally attached to activity?

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;
}

Activity retain instance of Fragment on onSaveInstance

I'm writing an application where using onSaveInstance(..) to retain values on device config change(say device font, local).
Here application Activity using multiple Fragment to display. After chnage in config change when coming back to app then onCreate(..) execute and app checked if Bundle object is null. Now till this state app didn't set any Fragment child back but but still last set Fragment (before change in config) child started to execute it's life cycle method.
How can prevent it, one way to check Same Bundle object from Fragment child, same as Activity and return. But is there other way to remove child from Activity on device-config change!
Activity reference code to handle re-calling:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.reading_activity);
if(savedInstanceState == null) {
init(savedInstanceState);
} else {
// Don't do anything
}
}
Here init(..) responsible to set Fragment child.

Android: NullPointerException in dialog when rotating

Sorry if this been asked before but I couldn't find an answer to my specific case. Also sorry that I'm new and a little stupid.
Problem:
I'm showing a dialog from a fragment and passing along a context in my constructor method because I need a context in my dialog to register for broadcastrecievers etc.
DialogFragment fragmentDialog = MyDialog.myConstructor(getActivity());
fragmentDialog.show(getFragmentManager(), "dialog");
Then in MyDialog class I store the context in a instance variable.
The problem arises when rotating the device and I get a nullPointerException when I try to use the context again in the dialog.
Can this be solved in some easy way?
If the device is rotated the Activity will be destroyed and recreated. So the Context you passed to your Fragment points on the Activity which was destroyed.
You could use setRetainInstance(true) in your Fragment. This way your Fragment will survive the recreation of the Activity.
To solve the NPE you have to pass the Context to the Fragment, if the Activity is recreated. Then the Context belongs to the new Activity.
In fact, without this update every line of code which points on the Activity like getActivity() or getFragmentManager() will lead in a NPE.
You get the NullPointerException because activites are destroyed and recreated when rotating the screen.
The SO post below gives more info...
https://stackoverflow.com/a/1673374/
Please be careful with the order of events if you rotate a FragmentActivity, because this can also be a source of NullPointerExceptions.
This is not documentated:
When the FragmentActivity is created the first time,
public class MyActivity extends FragmentActivity implements
MyFragment.OnFragmentInteractionListener {
private int var1;
private int var2;
#Override
protected void onCreate(Bundle savedInstanceState) {
//before
var1 = 3;
super.onCreate(Bundle savedInstanceState)
//after
var2 = 5;
}
//Interface Methods
public int getVar1() { return var1; }
public int getVar2() { return var2; }
}
both of the [before] and [after] code will be run before the fragments are attached and created. So, if you get the vars in the onCreate() call of the Fragment you get both vars. But when you rotate your device, the Activity is recreated from the savedInstanceState in the super call. Now, the fragments are reattached and created anew in this call! That means, this time the Methods of the Listener Interface are called before your [after] code. So, if you pass the Context of the activity to the fragment and get Information through the Interface like it is shown in: https://developer.android.com/training/basics/fragments/communicating.html
you get a NullPointerException for var2 because the interface methods are called from the fragments onCreate() onAttach() ... functions before the [after] code in the Activity's onCreate() is executed! So, take care that you set your Information the InterfaceFunctions are accessing before the super call.
Depending on what you're doing in your initialization you could consider creating a new class that extends Application and moving your initialization code into an overwridden onCreate method within that class.
public class MyApplicationClass extends Application {
#Override
public void onCreate() {
super.onCreate();
// TODO Put your application initialization code here.
}
}
And you are not stupid, even experts need help from time to time.

Categories

Resources