In my parent Fragment's onCreateView method, I invoke the Child Fragment Manager to insert a Fragment into the layout. This child fragment contains a few FloatingActionButtons.
After I commit the transaction, I then check the contents of my underlying list (powering a RecyclerView), and based on whether any values are present, I either hide/unhide some of the FloatingActionButtons.
However it's telling me that the buttons are null! Does committing a Fragment transaction not call all of its typical lifecycle events first? Is there a way to force it to wait? Is there a better practice for this?
The commit is not synchronous. There's no guarantee when the fragment's life cycle will execute.
If you need to know when the fragment is "ready", implement a callback from the fragment to the hosting activity letting it know.
class CustomB extends CustomA {
interface Listener {
void onViewCreated();
}
public View onCreateView() {
View v = super.onCreateView();
if (getActivity() instanceof Listener) {
((Listener)getActivity()).onViewCreated();
}
return v;
}
}
Generally speaking you should try to design your fragments to be self-contained. If the fragment is tightly bound to the activity, consider implementing the functionality as a custom View instead.
Related
I have a parent Fragment that has ViewPager which holds another Fragments(child). Some child Fragment can have list. If the user press a back button the list will scroll up if can scroll vertically, another press on back button will move the ViewPager to first item (Fragment).
While I can create an approach like this on child Fragment.
if (adapter.currentList.isNotEmpty() && recyclerView.canScrollVertically(-1))
recyclerView.smoothScrollToPosition(0)
else {
try {
val parentFragment: HomeFragment = parentFragment as HomeFragment
parentFragment.onBackPressed()
} catch (e: Exception) {
FirebaseCrashlytics.getInstance().recordException(e)
}
}
And a public method on parent Fragment like this
fun onBackPressed() {
if (viewPager.currentItem != 0)
viewPager.setCurrentItem(0, true)
else
requireActivity().onBackPressed()
}
I am not sure if this is the best thing to do since I read that Fragments should better not communicate directly with each other, instead communication should be handle by the host Activity or shared ViewModel. But doing so seems an overkill and I do not feel the use of LiveData just for this case.
I would go with the SharedViewModel approach, its kinda built for this. Your child Fragments are effecting the ViewPager that belongs to the ParentFragment. They are effectively part of the same View but the child Fragments don't have access to the View that contains them. So using a SharedViewModel can bridge the gap.
What I would do is make a well defined interface that manipulates the ViewPager and its Adapter as you see fit for your application. That interface should then be accessible through the SharedViewModel.
In the interface you could have a function called navigateToFragment(Class fragmentClass); In this function you would then need to find a fragment via its class inside the Adapter, and then use the ViewPager to go to it. Ie navigateToFragment(HomeFragment.class);
I am adding and removing Views to/from my Activity dynamically. Each of these Views is assigned an id and acts as a container for a particular Fragment. I add a Fragment to each one of these Views with conditional logic as follows:
if (supportFragmentManager.findFragmentById(R.id.someView) == null) {
supportFragmentManager.beginTransaction()
.add(R.id.someView, SomeFragment())
.commit()
}
This conditional logic ensures that a given View only has a Fragment added to it once during the lifetime of the Activity.
This logic works fine except when the Activity is recreated (due to a configuration change for example). When the Activity is recreated, the Views are not automatically recreated but the Fragments appear to survive the recreation. (I see that the Fragments have survived the recreation because the supportFragmentManager.findFragmentById(id:) calls return a non-null Fragment.)
I find that if I re-add Views to my Activity in the Activity.onCreate(savedInstanceState:) method, then the retained Fragments re-attach fine to the Views and everything is fine. However, if I delay adding the Views to a later point in the Activity lifecycle, then the Fragments do not re-attach to the Views (and the Views show up as blank).
Ultimately, this leads to confusing logic in my Activity.onCreate(savedInstanceState:) method when savedInstanceState is non-null to work around this. Either I have to re-add Views as they were at the point when the Activity was destroyed (I would prefer to do this elsewhere in the Activity) or I have to call FragmentTransaction.remove(fragment:) to remove each Fragment which survived the recreation.
Is there a way to add a Fragment to an Activity such that the Fragment does not survive Activity recreation? I see in the deprecation notice for the Fragment.setRetainInstance(retain:) method that the guidance is: "Instead of retaining the Fragment itself, use a non-retained Fragment and keep retained state in a ViewModel attached to that Fragment." However, this guidance does not give any instruction on how to define a non-retained Fragment.
There are a couple of dimensions to this answer.
Firstly, I could not find any documentation or any methods in the FragmentManager or FragmentTransaction classes which offer a means of creating a non-retained Fragment. The documentation in the deprecated Fragment.setRetainInstance(retain:) method says to use a "non-retained Fragment" but I could not find anywhere that explains what this means.
Secondly, the workaround for this problem is to remove the retained Fragment in the containing Activity's onCreate(savedInstanceState:) method so that the problematic Fragment can be recreated and attached to its containing view in a later lifecycle method, as follows:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.some_activity)
supportFragmentManager.findFragmentById(R.id.someView)?.let {
supportFragmentManager.beginTransaction().remove(it).commit()
}
}
Hello i create class that implement BottomSheetDialogFragment with dynamic content. The content is a Fragment. So when initialize the BottomSheet i passing fragment object, and attach it to specific Container ID inside this BottomSheetDialogFragment. Looks like this :
private fun attachContentFragment() {
val transaction = childFragmentManager.beginTransaction()
transaction.apply {
replace(R.id.flContent, state.layoutContent)
commit()
}
}
state.layoutContent is my attached Fragment
I need to dismiss the BottomSheet if every action called in that fragment.
As far as i know, i need to get the object of BottomSheet that hold me(Fragment) and dismiss it.
But how i can get that BottomSheet object?
Thanks
So, technically it is a fragment inside fragment situation. I think there is several solutions here:
Call Activity from your child fragment. BottomSheetDialogFragment will subscribe to Activity for such events and react on them.
Get the instance of a BottomSheetDialogFragment by calling proper FragmentManager (which possible is an Activity one). You can get an instance of a fragment byTag for example.
Or you can call getParentFragment from a child Fragment.
I have this code:
public class CrimeListFragment extends Fragment {
private RecyclerView mCrimeRecyclerView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_crime_list, container, false);
mCrimeRecyclerView = (RecyclerView) view
.findViewById(R.id.crime_recycler_view);
mCrimeRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
return view;
}
}
I know that when creating fragment, you separately implement onCreate() and onCreateView(). However, onCreate() is obviously missing here.
Why is that?
you separately call onCreate() and onCreateView()
No, you don't call either. The Fragment lifecycle calls them.
onCreate is not needed to be implemented on a simple Fragment class, only Activity classes
As for the title of your question - it should be called if you add that Fragment to an Activity.
Both onCreate() and onCreateView() can be overridden by you.
onCreate() is more optional and you can use to instantiate some variables (but you hardly need to override it).
onCreateView() is mandatory since you must inflate the view that you want and return it (like return view;).
In the docs:
onCreate()
The system calls this when creating the fragment. Within your implementation, you should initialize essential components of the fragment that you want to retain when the fragment is paused or stopped, then resumed.
onCreateView()
The system calls this when it's time for the fragment to draw its user interface for the first time. To draw a UI for your fragment, you must return a View from this method that is the root of your fragment's layout. You can return null if the fragment does not provide a UI.
You can also check the Fragment Life Cycle.
As you can see in picture below, in case of returning from back stack, only onCreateView() is called again... So, you can use onCreate() to run some code that can be executed only once (when Fragment is created... like configure some array or something like that)...
Then, you leave onCreateView() just to refresh/inflate the Views before displaying it to the user.
But again: onCreate() is not usually overriden and there's no problem with that... Is always up to you...
:
I have one activity and a couple of fragments. My activity has a drawer list that I use to switch fragments and roughly looks like this:
public class MainActivity extends AppCompatActivity
{
private PreferencesFragment preferencesFragment;
private HomeFragment homeFragment;
private void selectMenuItem(position)
{
Fragment fragment = null;
switch (position) {
case 0:
if (preferencesFragment == null) {
preferencesFragment = new PreferencesFragment();
}
fragment = preferencesFragment;
break;
case 1:
if (homeFragment == null) {
homeFragment = new HomeFragment();
}
fragment = homeFragment;
break;
}
getFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, fragment)
.commit();
}
}
So whenever I click on a drawer list item, selectMenuItem is called with the correct position. This loads an existing fragment or creates a new one when needed. The problem is that, even when a fragment already exists, the onCreateView method is triggered in the Fragments. I want to maintain the state of the view and only execute the code in onCreateView once. The docs say this about onCreateView:
The system calls this when it's time for the fragment to draw its user interface for the first time. To draw a UI for your fragment, you must return a View from this method that is the root of your fragment's layout. You can return null if the fragment does not provide a UI.
So why is it being called whenever selectMenuItem is executed? I verified that it's actually not executing the new Fragment() block and loading the properties value. How can I maintain the state of my Fragments when I'm using just 1 activity?
savedInstanceState is also null in the onCreateViewMethod in the Fragments. onSavedInstanceState is never triggered in the Fragments.
Yes, that's how the fragment lifecycle works. replace() removes any existing fragments, and removing a fragment destroys its view. Reusing the same Fragment instance in a later fragment transaction will trigger the lifecycle again from the beginning.
As Ivan mentions in his comments, onSaveInstanceState() only gets triggered when the hosting activity's onSaveInstanceState() is triggered, and merely replacing a fragment within an activity leaves the activity intact. You can save the state yourself, see e.g. android fragment- How to save states of views in a fragment when another fragment is pushed on top of it
To allow your fragments to get garbage collected, consider not keeping references to them.
If you replace fragment with another fragment -- views get destroyed, that's the correct behavior. If you really need to avoid view recreation, you can use FragmentTransaction.hide(), but imho your current approach is right, you don't know whether user will ever want to see PreferencesFragment so no point in bloating memory with unnecessary hidden views.