Status Update
Comments
jb...@google.com <jb...@google.com> #2
Branch: androidx-main
commit 57ca221882695bd6a52549f4d9ea3b812e6fe87c
Author: Simon Schiller <simonschiller@users.noreply.github.com>
Date: Mon Mar 22 16:09:30 2021
[GH] [FragmentStrictMode] Detect <fragment> tag usage
## Proposed Changes
- Detect `<fragment>` tag usage inside XML layouts
## Testing
Test: See `FragmentStrictModeTest#detectFragmentTagUsage`
## Issues Fixed
Fixes: 153738235
This is an imported pull request from
Resolves #141
Github-Pr-Head-Sha: 4ea052596e4341b9f11bcf335e2bc38045a91f19
GitOrigin-RevId: 62e7487aa4874eef6bb556490e193717cf937251
Change-Id: Iae48578e85e4e4897f806d7ade2e2a660adf9479
M fragment/fragment/api/public_plus_experimental_current.txt
M fragment/fragment/api/restricted_current.txt
M fragment/fragment/src/androidTest/java/androidx/fragment/app/strictmode/FragmentStrictModeTest.kt
M fragment/fragment/src/main/java/androidx/fragment/app/FragmentLayoutInflaterFactory.java
M fragment/fragment/src/main/java/androidx/fragment/app/strictmode/FragmentStrictMode.java
A fragment/fragment/src/main/java/androidx/fragment/app/strictmode/FragmentTagUsageViolation.java
ka...@droidsonroids.pl <ka...@droidsonroids.pl> #3
ma...@marcardar.com <ma...@marcardar.com> #5
public static NavController findNavController(@NonNull Activity activity, @IdRes int viewId) {
if (activity instanceof FragmentActivity) {
NavHostFragment navHostFragment = (NavHostFragment)(((FragmentActivity)activity).getSupportFragmentManager().findFragmentById(viewId));
return navHostFragment.getNavController();
}
View view = ActivityCompat.requireViewById(activity, viewId);
NavController navController = findViewNavController(view);
if (navController == null) {
throw new IllegalStateException("Activity " + activity + " does not have a NavController set on " + viewId);
}
return navController;
}
ha...@gmail.com <ha...@gmail.com> #7
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public void onPostCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
super.onPostCreate(savedInstanceState, persistentState);
BottomNavigationView navView = findViewById(R.id.nav_view);
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_home, R.id.navigation_store, R.id.navigation_library)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(navView, navController);
}
Works perfectly fine :)
il...@google.com <il...@google.com> #8
Re #7 - please don't follow that bad advice. onPostCreate()
runs after onStart()
, which is most certainly later than you should be setting up your NavController
. Just use the approach in
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
.findFragmentById(R.id.my_nav_host_fragment);
NavController navController = navHostFragment.getNavController();
w....@futuremind.com <w....@futuremind.com> #9
Maybe you can change implementation of extension function from navigation-ui-ktx? In place of
fun Activity.findNavController(@IdRes viewId: Int): NavController =
Navigation.findNavController(this, viewId)
to
fun FragmentActivity.findNavController(@IdRes viewId: Int) =
(supportFragmentManager.findFragmentById(viewId) as? NavHostFragment)?.navController
?: Navigation.findNavController(this, viewId)
hu...@gmail.com <hu...@gmail.com> #10
I was wondering if it is actually a good idea to inflate NavHostFragment
with a FragmentContainerView
because if we do that what happen from my understanding is a NavHostFragment
instance would be created with its view attached to that FragmentContainerView
(named it FCV1
).
The view of the NavHostFragment
is a FragmentContainerView
too (FCV2
) according to the NavHostFragment
source code:
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
FragmentContainerView containerView = new FragmentContainerView(inflater.getContext());
containerView.setId(getContainerId());
return containerView;
}
Given NavHostFragment
is generally used to "host" other fragments which are destinations in its associated NavGraph
, those fragments' views would be attached to FCV2
, leaving FCV1
not having clear value, is it a waste?
On the other hand, if we use <fragment>
tag to attach NavHostFragment
to an Activity
, FCV1
would not be created and FCV2
is directly attached to the activity's view.
ka...@live.com <ka...@live.com> #11
I'm general fine with using <fragment>
for NavHostFragment
.
But the real problem here is Android Studio showing warning for this setup suggesting to replace <fragment>
with FragmentContainerView
.
In other words, Android Studio suggestion is breaking the code, leaving developer in dark. :-(
hu...@gmail.com <hu...@gmail.com> #12
Yes, I mean more like, if my understand above is correct, then Android Studio actually gives an incorrect warning because using FragmentContainerView
instead of <fragment>
would not make the app better, and can say even worse considering the extra nested layout.
ka...@live.com <ka...@live.com> #13
It's not only about performance. Using FragmentContainerView
is breaking the app.
Check the title. When you use FragmentContainerView
findNavController()
is not working anymore!
hu...@gmail.com <hu...@gmail.com> #14
Yeah sorry for confusion, I should have been clearer.
I mean I understand that it breaks the app if we address the warning. And what I wanted to say was even if it didn't break the app, I don't think it's a valid warning since using replacing <fragment> with
FragmentContainerView` would create extra nested layout while there is no benefit gained.
il...@google.com <il...@google.com> #15
Re #10 - yes, you should always use FragmentContainerView
. There are absolutely other fixes around window insets and layout issues that occur when a fragment's root layout is directly within other layouts such as ConstraintLayout
, besides the underlying issues with the <fragment>
tag where fragments added via that tag go through lifecycle states entirely differently from the other Fragments added to the FragmentManager
. The Lint check is there exactly because you absolutely should switch over to FragmentContainerView
in all cases. It is not a no-op or negative even for a single static fragment.
FragmentContainerView
uses a FragmentTransaction
to add the initial fragment. If you were to use that approach manually, you'd note that findNavController()
would not work in that case either. The whole point of FragmentContainerView
is to provide a consistent path and lifecycle transition points for all fragments - as mentioned in onCreate()
is not where you should be accessing the Fragment's views, which is why getting the NavController
from the NavHostFragment
itself is the recommended solution in
ka...@live.com <ka...@live.com> #16
Re #15 - that can be all well and true, however in the state it's now it's pure madness.
Look at this stack overflow question <fragment>
and I guarantee you most developers won't think twice about it. At the and all the good intensions are useless.
So until there is a lint check for findNavController()
in onCreate()
telling developer to call it later (in onPostCreate
) or at least documentation stating this fact, you can't expect people will actually switch to FragmentContainerView
.
Another option would be apply suggestion from #5 and incorporate searching fragment manager in findNavController
before throwing exception that NavController
is not present.
Long story short, as it is right now is bad and you shouldn't be turning a blind eye to it.
hu...@gmail.com <hu...@gmail.com> #17
Re #15 - thanks for the clarification. I wasn't aware of the issue with using <fragment>
in xml. Is it documented somewhere or is there any related bug report?
While I generally agree with #16, I think the original issue was the default generated code in Android Studio uses <fragment>
in XML and also having findNavController
in onCreate()
. If it could be updated by Android team to properly use FragmentContainerView
(for example can use what's recommended in #4), a lot of confusion can be avoided. At the end of the day, Android Studio's example code should reflect the most correct and recommended way of doing things.
ro...@gmail.com <ro...@gmail.com> #18
I'll stick to #4.
RG
ru...@gmail.com <ru...@gmail.com> #19
working in onCreate()
val navHostFragment by lazy { supportFragmentManager.findFragmentById(R.id.navHostFragment) as NavHostFragment }
val navController get() = navHostFragment.navController
override fun onCreate(savedInstanceState: Bundle?) {
navHostFragment.childFragmentManager...
}
lu...@lloydsbanking.com <lu...@lloydsbanking.com> #20
I'm curious about the use cases that lead to this exception. If the user has defined the nav graph and set it as the nav graph of the FragmentContainerView
, what other functionality do they require during onCreate()
?
ka...@droidsonroids.pl <ka...@droidsonroids.pl> #21
Ad #20 for example one may want to parametrize start destination depending on some condition.
da...@gmail.com <da...@gmail.com> #22
ma...@americium.org <ma...@americium.org> #23
For an activity, onPostCreate works. For example in the activity:
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
// Needs to be in the post create
// Refer issue https://issuetracker.google.com/issues/142847973
_binding.navHostFragment.findNavController().addOnDestinationChangedListener { _, destination, _ ->
// code goes here :)
}
}
}
mo...@gmail.com <mo...@gmail.com> #25
This is the updated answer.
[Deleted User] <[Deleted User]> #26
sp...@gmail.com <sp...@gmail.com> #27
Just ran into this today and I am sad that this is marked as "infeasible" when a good response to this problem, at minimum, is to update the boilerplate code for new projects so that it doesn't generate code that automatically gets complaints from lint and then auto-fixes to broken code.
na...@gmail.com <na...@gmail.com> #28
this seems working but handler is deprecated is showing
ps: Im using nav-frag 2.7+ and viewbinding so first method is also not working
Description
Version used: 1.2.0-beta02
Devices/Android versions reproduced on: all
When the NavHostFragment is inflated using FragmentContainerView, if you attempt to use findNavController in the onCreate() of the Activity, the nav controller cannot be found. This is because when the fragment is inflated in the constructor of FragmentContainerView, the fragmentManager is in the INITIALIZING state, and therefore the added fragment only goes up to initializing. For the nav controller to be properly set, the fragment view needs to be created and onViewCreated() needs to be dispatched, which does not happen until the ACTIVITY_CREATED state.
We should either force the fragment to create a view right after being added to the FragmentManager, or find a way to wait for the FragmentManager to reach a higher state before allowing the Navigation onCreate to be called.
The following options can be used as viable work arounds:
1. Retrieve the navController directly from the NavHostFragment.
val navHostFragment = supportFragmentManager.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
2. Post the call to the findNavController method on a handler and execute all actions that need it after that post is complete.
3. Continue to use the fragment tag (<fragment>) to inflate the NavHostFragment