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.
Related
What is the proper way of navigating back from nested fragments of ViewPager2?
Despite using app:defaultNavHost="true"with FragmentContainerView pressing back button while in a nested fragment of a page calls Activity's back press instead of navigating back to previous fragment.
As per the Create a NavHostFragment documentation, app:defaultNavHost="true" calls setPrimaryNavigationFragment() when the Fragment is first added - it is setPrimaryNavigationFragment() that routes back button press events to that fragment automatically.
In a ViewPager2 though, it is the ViewPager2 that is responsible for creating and adding the Fragment. Since every level of the Fragment hierarchy needs to be the primary navigation fragment, adding a child fragment via XML still doesn't solve the missing link: that the Fragment in the ViewPager2 needs to be the primary navigation fragment.
Therefore, you need to hook into the callbacks for when a Fragment is made the active Fragment and call setPrimaryNavigationFragment(). ViewPager2 1.1.0-alpha01 adds exactly this API in the FragmentTransactionCallback, specifically, the onFragmentMaxLifecyclePreUpdated(), which is called whenever the Lifecycle state of a Fragment is changed: when it is changed to RESUMED, that Fragment is now the active fragment and should become the primary navigation Fragment as part of the onPost callback.
private class Adapter(parentFragment: Fragment) : FragmentStateAdapter(parentFragment) {
init {
// Add a FragmentTransactionCallback to handle changing
// the primary navigation fragment
registerFragmentTransactionCallback(object : FragmentTransactionCallback() {
override fun onFragmentMaxLifecyclePreUpdated(
fragment: Fragment,
maxLifecycleState: Lifecycle.State
) = if (maxLifecycleState == Lifecycle.State.RESUMED) {
// This fragment is becoming the active Fragment - set it to
// the primary navigation fragment in the OnPostEventListener
OnPostEventListener {
fragment.parentFragmentManager.commitNow {
setPrimaryNavigationFragment(fragment)
}
}
} else {
super.onFragmentMaxLifecyclePreUpdated(fragment, maxLifecycleState)
}
})
}
// The rest of your FragmentStateAdapter...
}
You need to override your parent activity's onBackPressed logic, you need to use https://developer.android.com/reference/androidx/navigation/NavController#popBackStack() to navigate up in your nav graph of nested fragment.
Here is the Java version of #ianhanniballake answer (yes im a luddite for not using kotlin yet - i need to know java inside out first before i learn anything else).
I havent unit tested this yet, but it "works"...
public class ViewPagerAdapter extends FragmentStateAdapter {
public ViewPagerAdapter(#NonNull FragmentManager fragmentManager,
#NonNull Lifecycle lifecycle) {
super(fragmentManager, lifecycle);
registerFragmentTransactionCallback(new FragmentTransactionCallback() {
#NonNull
#Override
public OnPostEventListener onFragmentMaxLifecyclePreUpdated(#NonNull Fragment fragmentArg,
#NonNull Lifecycle.State maxLifecycleState) {
if (maxLifecycleState == Lifecycle.State.RESUMED) {
return () -> fragmentArg.getParentFragmentManager().beginTransaction()
.setPrimaryNavigationFragment(fragmentArg).commitNow();
} else {
return super.onFragmentMaxLifecyclePreUpdated(fragmentArg, maxLifecycleState);
}
}
});
}
// remainder of your FragmentStateAdapter here
}
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.
I tried the solution here:
Calling a Fragment method from a parent Activity
But it didn't work for me.
I have this method in my fragment
public void showbutton()
{
sup.setEnabled(true);
}
and I'm using this in the parent activity
Fragment fragment = (Fragment) getFragmentManager().findFragmentById(R.id.fragment);
fragment.showbutton();
I'm sure it's a silly mistake, I'm still new to Android so forgive me.
Use callback to communicate from fragment to Activity.
public interface UserAction {
void showButton();
}
In the Fragment implement the Interface
#Override
public void showButton() {
sup.setEnabled(true);
}
Then in your activity just call
UserAction mUserAction = getFragmentManager().findFragmentById(R.id.fragment);
mUserAction.showButton();
Firstly, make sure you are accessing the correct FragmentManager, verify that you need to call either getFragmentManager() or getSupportFragmentManager().
Secondly, you should cast the Fragment to your type. That is,
MyFragment fragment = (MyFragment) getFragmentManager().findFragmentById(R.id.fragment);
fragment.showbutton();
I think you should typecast using Fragment Name.
i.e
YourFragment fragment = (YourFragment) getFragmentManager().findFragmentById(R.id.fragment);
fragment.showbutton();
This issue is regarding to fragment life cycle. You are trying to invoke method of fragment which is closed by android OS. You need to create bool in your fragment and set that to false normally but when you need to show button just create that fragment and load that and update your bool to true.Later on check if that bool is true enable your button else disable it. Happy Coding!
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).
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();