Where to put viewModel Observers in a dialogFragment? - android

For fragments it is advised to put liveData observers in the onActivityCreated method. This works fine for fragments, but when I apply this to a dialogFragment I get the following error:
java.lang.IllegalStateException: Can't access the Fragment View's LifecycleOwner when getView() is null i.e., before onCreateView() or after onDestroyView().
From this question I read the lifecycle of the dialogFragment at creation is:
onAttach
onCreate
onCreateDialog
onCreateView
onActivityCreated
onStart
onResume
So putting the observers in onActivityCreated should be fine as it is after onCreateView or onCreateDialog. I use the latter as I use a Alertdialog with my own layout.
This is the code for my observer:
mScheduleViewModel.getTeachers().observe(getViewLifecycleOwner(), new Observer<List<String>>() {
#Override
public void onChanged(#Nullable List<String> strings) {
mStringList = strings;
aclInputvalue.setThreshold(2);
aclAdapter.setList(strings);
aclAdapter.notifyDataSetChanged();
....
}
This code pattern works fine in a fragment but not in a dialogFragment. There I have to set the lifecycleOwner to 'this'.
So why do I get the error?

Update: See Farid's link below, this answer is probably not recommended.
You can use ViewModels in a DialogFragment when you are overriding onCreateDialog by doing this:
When you inflate your custom view in onCreateDialog, store a reference to it as a variable in your DialogFragment.
Override onCreateView and simply return your saved custom view.
Set the view to null in onDestroyView exactly like this (otherwise Leak Canary reported memory leaks)
override fun onDestroyView() {
myView = null
super.onDestroyView()
}
Then, the dialog behaves more like a normal fragment and you can observe your ViewModel in onCreateView, onViewCreated or onActivityCreated as you please.

Related

Lifecycle problem when use FragmentStatePagerAdapter, Fragments

I'm using FragmentStatePagerAdapter, ViewPager.
I'm going to use onSaveInstanceState by overriding to save some states like cursor position of EditText in every fragment.
But when I choose first fragment and next choose second fragment, the onSaveInstanceState of first fragment is not called. If I choose first and next choose third fragment, then the onSaveInstanceState of the first fragment is called.
In this case of choosing first fragment and next second fragment, even the onPause of the first fragment is not called.
What's the reason? How can I solve this problem? I have researched about this problem whole day. But I haven't found solution and correct reason yet.
onSaveInstanceState has cases that it can be called, but how about onPause? Why doens't onPause be called?
I found a solution. I used setUserVisibleHint.
In fragment, I wrote save and restore logic in setUserVisibleHint.
It works well.
This is called when fragment is shown or hidden.
Also I used onViewStateRestored, onSaveInstanceState together for being destroyed cases.
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser) {
if (mInputFrom != null) {
if(isToFocus) {
mInputFrom.requestFocus();
mInputFrom.setSelection(fromCursor);
} else {
mInputOut.requestFocus();
mInputOut.setSelection(toCursor);
}
}
} else {
if (mInputFrom != null) {
fromCursor = mInputFrom.getSelectionStart();
toCursor = mInputOut.getSelectionStart();
}
}
}
setUservisibleHint was deprecated.
So other option is that we can use constructor of FragmentStatePagerAdapter(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
If we call super's constructor like above in constructor of our customized Adapter that extends FragmentStatePagerAdapter, then onPause, onResume of fragment will be called for every case of being hidden or shown.
First option is suitable for my case I think. So I used first option and has solved perfectly.

onAttachFragment not being called when showing a dialog from fragment

I'm showing a BottomSheetDialogFragment from a Fragment. The idea is to get the callback of BottomSheetDialogFragment inside that Fragment instead of activity so I was hoping to receive the fragment inside
override fun onAttachFragment(childFragment: Fragment?) {
super.onAttachFragment(childFragment)
callback = childFragment as? Callback
}
This method is not being called. I tried using fragmentManager and childFragmentManager when showing the dialog to see if I can get onAttachFragment called but no luck.
AccountBottomSheetDialog dialog = AccountBottomSheetDialog.Companion.newFragment();
dialog.show(getChildFragmentManager(), AccountBottomSheetDialog.Companion.getTAG());
AccountBottomSheetDialog dialog = AccountBottomSheetDialog.Companion.newFragment();
dialog.show(getFragmentManager(), AccountBottomSheetDialog.Companion.getTAG());
Does anyone know how to get this called?
Thanks.
I think the reason it's not calling onAttachFragment is because DialogFragment control its life cycle and other method overrides differently than the normal ones, see documentation.
If you simply want to have a callback to the parent fragment, you can either override the onAttach method inside the DialogFragment and use the context parameter as the callback (cast it), or have a public method inside your DialogFragment that sets the callback, which you would call after creating that fragment.
onAttach method:
override fun onAttach(context: Context?) {
super.onAttach(context)
callback = context as? Callback
}
public callback setter method:
//parent fragment: after initializing it
childFragment.setCallback(this#ParentFragment)//or pass in other callbacks
//child fragment:
fun setCallback(callback: Callback) {
this.callback = callback
}
Maybe it a little bit late but now in 2019 with support library androidx.appcompat:appcompat:1.1.0 if using variant with child fragment manager then onAttachFragment called as I wait.
Here is correct fragment of code on Kotlin
ChildInfoDialog.getInstance(it.data).show(childFragmentManager, ChildInfoDialog.TAG)
Cannot answer your question directly but here is the workaround you could take:
Pass your parent fragment as newFragment's parameter
Set it on your child fragment as target fragment with any request code (setTargetFragment)
Use it in your code as getTargetFragment() and cast it to any interface you like (the interface your parent fragment implements of course).
PS.: for the above to work, fragment manager must be the same for parent and child.

How can I know that onCreateView has been called from an outer class?

I'm really curious about how to determine (from an outer class) if a fragment's onCreateView() has already been called. I have searched for similar questions but found none.
For instance, is fragment.isAdded() a good indicator?
My first thought was simply fragment.getView() != null, but I'm not 100% sure it would be reliable as it seems, and I'm also slightly reluctant to use it (for no particular reason, I just tend to avoid nullity checks). I would be happy to find a workaround. Suggestions I had:
isAdded()
Return true if the fragment is currently added to its activity.
This line is quite ambiguous in my opinion; added is not attached, but neither created. It might refer to FragmentTransaction.add() (which is semantically wrong because you can have <fragment>s stuck in your layout without having to call add or replace).
Still, FragmentTransaction.add() documentation gives no info nor makes you think added -> created. I'd say no.
isVisible()
Return true if the fragment is currently visible to the user. This means it: (1) has been added, (2) has its view attached to the window, and (3) is not hidden.
Looks good, in the sense that isVisible() -> isCreated, but the third option makes it isCreated != isVisible. I just think of fragments inside a view pager: not all are visible, but the fragments near the currently visible fragment are added, created and alive, you can call methods on them. But for them, isVisible() == false. This is kind of too strict.
isInLayout()
Return true if the layout is included as part of an activity view
hierarchy via the < fragment> tag. This will always be true when
fragments are created through the < fragment> tag, except in the case
where an old fragment is restored from a previous state and it does
not appear in the layout of the current state.
I don't think this applies here.
getView() != null
Returns
The fragment's root view, or null if it has no layout.
This still looks the one and only solution. I'd just like a confirmation about that.
Implement a callback
..to be called onCreateView() or, better, onViewCreated(). But:
I don't need to call something as soon as the fragment is created (why would you need that?), I need something to check at a given time;
One should define the opposite, say, onViewNotAvailableAnymore(), to make the check meaningful at all times;
I don't see how this would be different, or better, than getView != null.
Does Fragment.isAdded() imply that onCreateView has been called?
NO!! NO!! pause NOOOOOOO00000000000!!!!!
SIr
Fragment.isAdded() is a notification that your Fragment has been added to your Activity, end of story.
The add() method in FragmentTransaction has 3 different methods, all adds Fragment to an Activity ,and, two goes further to create your Fragments View and attach it to a Parent ViewGroup by the aid of LayoutInflater provided your first parameter is not 0 (id != 0)
To check if onCreateView() has been called you need to override onViewCreated().
getView() will always return null unless onCreateView() is done
your solution is check Fragment.isVisible()
FYI: There is nothing wrong that i see with the documentation. Its pretty clear sir.
Hope i am lucid Sir
Assuming
you're not interested in whether the Fragment is visible
you want to know only if the onCreateView(...) method has been called by the Android framework
you need to use existing Fragment API methods to find out
then use
getView() != null
provided that you inflate the layout and return the View inside onCreateView(...) .
A non-Fragment-API approach is to add a callback in onCreate(...), which you then call in onCreateView() or later (in lifecycle) method.
It can be done using interface. make an interface OnCreateViewListerner
public interface OnViewCreatedListener
{
void onCreateCalled();
}
create a static object of OnViewCreatedListener in your Fragment and initialize it within in your Outer class and Outer class implement this interface like
public class CustomClass implements OnViewCreatedListener{
#Override
public void onCreateCalled() {
}
public void initializeInterface()
{
FragmentTest.listerner = this;
}
.....
}
then override your onCreateView() method and write this
public class FragmentTest extends Fragment{
public static OnViewCreatedListener listerner;
View mView;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// if (container == null)
if(listerner!= null)
{
listerner.onCreateCalled();
}
mView = inflater.inflate(R.layout.fragment_product, container, false);
return mView;
}
}
Hope this will help you.
Please consider this approach as I did same like this:
Define an Interface with in your Fragment say:
public Interface IDoSomething{
void intimateAfterOnCreateView();
}
Now, call this method with in onStart() of a fragment as according to life cycle this method will be called after the onCreateView().
Outside from this fragment just implement IDoSomething and you will get an overrided method(intimateAfterOnCreateView()) automatically.
Now this method's execution will show that onCreateView() has been called.
I just want to share my knowledge, may be it helps.
If isAdded() on a Fragment returns true, it doesn't mean that the onCreateView() has been called. In fact, isAdded returns true even during the onAttach callback, that is called before the onCreate().
I would go with extending the Fragment class and adding a public method that you can use to reach from outer of your custom Fragment Class.
When the onCreateView() is called, you can set a boolean value to true and according to your architecture, you can set it to false back again when it's in onPause() or onStop() or onDestroyView() or onDestroy() or onDetach(), up to you.
I don't think the methods you mentioned in your question will provide you exactly what you need.
How can I know that onCreateView has been called from an outer class?
You need to create interface inside your fragment and implement it in the container activity (let's say MainActivity).
1. First create an interface inside your fragment:
// Container activity must implement this interface
public interface OnCreateViewCalledListener {
void OnCreateViewCalled(String someData);
}
2. Next implement the interface inside your container activity (lets say it is MainActivity) and call it's method:
public class MainActivity extends AppCompatActivity implements
YourFragment.OnCreateViewCalledListener {
...
#Override
public void OnCreateViewCalled(String someData) {
Toast.makeText(MainActivity.this, "OnCreateView was called and passed " + someData)
}
3. Then you need to check if MainActivity implements interface callbacks (this step is crucial to make it work properly):
//Using onAttach method to check that activity has implemented callbacks
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// Make sure that container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (OnCreateViewCalledListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnCreateViewCalledListener");
}
}
4. And finally you need to trigger the callback inside onCreateView:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mCallback.OnCreateViewCalled("Some useful data");
...
}
That's it!
EDIT:
To let other class know that onCreateView was called, please use the onCreateViewCalled() callback inside the MainActivity (e.g. use another interface to trigger callback in other class).
Also it is not mandatory to pass data into the OnCreateViewCalled()
Does Fragment.isAdded() imply that onCreateView has been called?
YES
isAdded() Return true if the fragment is currently added to its activity. (Implicitly onCreateView() has been called).

Behavior of Fragments in Android

I cannot find anything on this... I am wondering when I have a fragment within an activity why I can't call certain things such as getPackageName(), and getContentResolver()?
Call from a non static function inside the fragment, after it attaches to the activity.
getActivity().getPackageName();
getActivity().getContentResolver();
As your commenter stated, these are not fragment functions, you have to get the contextWrapper instance.
Because as per this documentation, the methods you mentioned are methods of the Context class. Activity is a subclass of Context, therefore it has these methods available. Fragment, however, is not. There are 2 ways to get hold of these methods from a fragment:
First option: After your fragment has been attached (it's onAttach() method has been called) you can use the getActivity() method to get the activity that it has been attached to. Then you can use getPackageName() and getContentResolver(). Such as this: getActivity().getPackageName(). To be extra safe, you might want to do:
Activity myActivity = getActivity();
if (myActivity != null)
{
myActivity.getPackageName();
myActivity.getContentResolver();
}
else
{
//deal with the null problem
}
Second option: In the onCreateView() method, your fragment won't yet be attached so you can't use the above method. You may use the LayoutInflater to get a View. Then call getContext() on the view. Such as this:
View myView = inflater.inflate(R.layout.my_fragment_layout, container, false);
myView.getContext().getPackageName();
myView.getContext().getContentResolver();

Getting a reference to a child Fragment after the parent Fragment has been recreated

Starting Android 4.2, Android supports nested Fragments. The doc doesn't give a lot of explanations regarding nested Fragment lifecycles but from experience, it appears their lifecycle is really similar to "regular" Fragments.
It looks like there is one big difference though: child Fragments are not restored when the parent Fragment onCreate method is called. As a consequence, it seems impossible to save/restore a reference to a particular Fragment:
Using getChildFragmentManager.findFragmentByTag(String) always returns null in parent Fragment onCreate(Bundle) because mActive is null.
Using putFragment/getFragment results in a NullPointerException because getFragment looks for the size of a null mActive ArrayList.
So, my question is quite simple. Is there a correct way to retrieve a reference to a child Fragment in the parent Fragment onCreate method?
I don't think you can in onCreate as the view isn't constructed at that time. You can in onViewCreated() though. The logic I used is:
Check if there is saved state in onViewCreated(), if there is, try to get the child fragment
Then check if the child fragment is null, if it is, add it using the child fragment manager.
By "checking" I mean looking up the fragment by id. I guess by tag should work too.
AFAIK you can't get a child fragment before the view hierarchy is restored or created, but you could do the same at later time, for example in onActivityCreated()
What about setRetainInstanceState(true) on your fragment?
Could it solve your problem? It solved some problems when I have ChildFragments in a Fragment. I only have to keep a reference to the childfragment in the fragment.
But I allways did that in onCreateView(). Not sure if it will work in onCreate()
Or do you mean something completely different?
are u using FragmentPagerAdapter?
if not try FragmentPagerAdapter instead of FragmentStatePagerAdapter
I realised that have some bug when using FragmentStatePagerAdapter when i have 4 level nest.
Sorry my english is poor.
#Override
public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState != null) {
mFragment1 = getFragmentManager().getFragment(savedInstanceState, STATE_Fragment1);
mFragment2 = getFragmentManager().getFragment(savedInstanceState, STATE_Fragment2);
mFragment3 = getFragmentManager().getFragment(savedInstanceState, STATE_Fragment3);
} else {
mFragment1 = SomeFragment.newInstance("param1");
mFragment2 = SomeFragment.newInstance("param2");
mFragment3 = SomeFragment.newInstance("param3");
}
super.onCreate(savedInstanceState);
mMyPagerAdapter = new MyPagerAdapter(getChildFragmentManager(), mFragment1, mFragment2, mFragment3);
}
#Override
public void onSaveInstanceState(Bundle outState) {
if (mFragment1 != null) {
getFragmentManager().putFragment(outState, STATE_Fragment1,
mFragment1);
}
if (mFragment2 != null) {
getFragmentManager().putFragment(outState, STATE_Fragment2,
mFragment2);
}
if (mFragment3 != null) {
getFragmentManager().putFragment(outState, STATE_Fragment3,
mFragment3);
}
super.onSaveInstanceState(outState);
}

Categories

Resources