My app is set up to have a unique toolbar for each Fragment. In one particular fragment, I want to override the Navigate Up or Back button in the toolbar to give a warning to the user to confirm their intention.
I originally asked this question here and found that by changing my original code to using setSupportActionBar to implement the toolbar, I was able to maintain my unique toolbar for the fragment and ovveride the Navigate Up button.
However, I just noticed that whenever I back out of that fragment that uses setSupportActionBar for the toolbar, I get a memory leak (same as the problem found by this user). I confirmed this by commenting out the line that sets up the actionbar and saw that the leak had disappeared.
How can I maintain my unique toolbar, override the Navigate Up button and avoid this memory leak?
However, I just noticed that whenever I back out of that fragment that uses setSupportActionBar for the toolbar, I get a memory leak
You can try to eleminate this by setSupportActionBar(null) when this particular fragment is destroyed:
override fun onDestroy() {
super.onDestroy()
(requireActivity() as AppCompatActivity).setSupportActionBar(null)
}
Java:
// In the fragment
#Override
public void onDestroy() {
super.onDestroy();
((AppCompatActivity) requireActivity()).setSupportActionBar(null);
}
Related
I've been trying to resolve this issue for some time now. My app uses a slidingPaneLayout to show fragments inside it. My issue is in lower API than 28, after some time loading fragments on the slidingPaneLayout, when instantiate new fragments, their content become partially or fully invisible but the fragment itself works correctly (sounds and timer works even with content invisible), if I go background with the app and comes back the content of the fragment re-appears without any issue.
I don't know exactly what is the causing the issue, if someone had this problem before and have some indications it would be a great help for me.
For the slidingPaneLayout I use this library: https://github.com/umano/AndroidSlidingUpPanel
For changing my fragments I use this:
fun showFragment(fragment: Fragment, containerId: Int, tag: String) {
val fragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.replace(containerId, fragment, tag)
fragmentTransaction.addToBackStack(null)
fragmentTransaction.commit()
}
What it should look like:
What happens when this issue first appears
After first issue all fragments load with content invisible (the orange arrow is from the activity view)
[3
PS: I corrected nearly all memory leaks with canaryleak on the app
PS: I'm using the last version of Fragments, androidx.fragment:fragment:1.2.4
Edit: When i force onStop in my fragment like below:
var firstTime = true
override fun onResume() {
super.onResume()
if(firstTime){
onStop()
firstTime = false
}
}
The fragment view appears correctly I don't know why
I'm not really sure what I'm doing wrong here. Simple setup, single activity with fragments controlled by a bottom bar inside, so far so good. The start fragment has a button inside which should navigate to another fragment.
Here's the onclicklistener for the button inside my fragment class:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
add_step_fab.setOnClickListener {
Navigation.findNavController(view).navigate(R.id.fragmentAtoB)
}
}
Here's my action in the navigation.xml:
<fragment android:id="#+id/navigation_main" android:name="com.test.test.fragments.AFragment"
android:label="Fragment A" tools:layout="#layout/fragment_a">
<action android:id="#+id/fragmentAtoB"
app:destination="#id/fragmentB" app:enterAnim="#anim/nav_default_enter_anim"
app:exitAnim="#anim/nav_default_exit_anim" app:popEnterAnim="#anim/nav_default_pop_enter_anim"
app:popExitAnim="#anim/nav_default_pop_exit_anim"/>
</fragment>
The navigation works, but I get the standard
java.lang.IllegalArgumentException: navigation destination com.test.test:id/fragmentAtoB is unknown to this NavController
error when I click on the button after rotating my screen, split screening the app, or sometimes seemingly randomly. Looks like it has something to do with the configuration change?
EDIT:
I also tried using
val clickListener: View.OnClickListener = Navigation.createNavigateOnClickListener(R.id.fragmentAtoB)
add_step_fab.setOnClickListener(clickListener)
with the same issue as above.
Checking if i'm actually in the same fragment, as some suggested for users who are having the same exception (but for unrelated reasons), such as:
if (controller.currentDestination?.id == R.id.navigation_main) {
controller.navigate(R.id.fragmentAtoB)
}
also didn't help.
I also experienced this recently after months of an app working correctly. I originally had added navigation to an app that was utilizing a "MainActivity" class and then had other activities that would be launched from a menu (as opposed to having the Navigation component navigate between fragments off of a single Activity).
So the app is organized such that the MainActivity is the "hub", and choices from menu options cause other activities to be launched, and when the user returns from them they are back at the MainActivity - so a pure "hub (MainActivity) and spoke (5 other activities)" model.
Within MainActivity.onCreate() is called a setupNavigation() method which originally looked like:
private void setupNavigation() {
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
drawerLayout = findViewById(R.id.drawer_layout);
// This class implements the listener interface for the NavigationView, handler is below.
NavigationView navigationView = findViewById(R.id.nav_view);
navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout);
NavigationUI.setupWithNavController(navigationView, navController);
// MainActivity implements the listener for navigation events
navigationView.setNavigationItemSelectedListener(this);
}
As I mentioned, this was working just fine even with configuration changes until recently. Even just changing the configuration from portrait to landscape on the MainActivity would cause an exception:
ComponentInfo{com.reddragon.intouch/com.reddragon.intouch.ui.MainActivity}: java.lang.IllegalStateException: You must call setGraph() before calling getGraph()
I found I had to add this line:
navController.setGraph(R.navigation.nav_host);
Right after the Naviation.findNavController() call, and then it handled configuration changes just fine.
After a configuration change, the fragment or activity goes out of scope temporarily and then executes an onResume() override. If you have anything which is initialized in onCreate(), or onViewCreated(), then those variables may have gone out of scope and are now null values. So, I usually have a method called RestoreAll() which is responsible for instantiating all of the objects used by the code, and I call this method from onResume() so that every time there is a configuration change, all the local objects are re-instantiated.
There is a problem with nav controller backstack after changed config. Try this:
https://stackoverflow.com/a/59987336/5433148
I am using ViewPager in my app. In each fragment there is a toolbar. On a single tap on the image the the toolbar is animated to the top out of the screen. But I have to notify all the remaining fragments to do the same thing. So that when the user scrolls to the next fragment he doesn't see the toolbar.
I tried adding setUserVisibleHint(), but it did not work as it was called only when the fragment was completely visible, thus showing the toolbar exiting to the user.
Then I tried it in onResume and setting pager.offscreenpagelimit=1, it worked fine for the fragment next to next but did not work for the next fragment.
Thanks!!
First Notify your activity from fragment using:
In Fragment on Animation End:
((YourActivity)getActivity()).hideToolbar();
In Activity:
public void hideToolbar() {
// Redraw view pager without toolbar (notify your adapter create pager without toolbar)
}
Well, why don't you get your toolbar out of your fragments and just create one in activity and change its state on page change (definitely it will not slide but may
You have 2 problems to face.
How to call existing fragments to hide/show their toolbars.
How to create another fragments with hidden toolbar.
First problem can be easly done by using Otto event library found here. Just paste this code in your viewpager fragments:
Bus bus = new Bus();
#Override
protected void onResume() {
super.onResume();
bus.register(this);
}
#Override
protected void onPause() {
super.onPause();
bus.unregister(this);
}
#Subscribe
public void onToolbarEvent(ToolbarEvent event) {
//do your toolbar logic
}
Then in your onClick event on image just put (of course creating bus object before)
bus.post(new ToolbarEvent());
ToolbarEvent can be just empty class. If you read about Otto events you will understand how it works.
Another problem is how to know that the toolbar should be hidden/shown, when viewpager instantiates new fragments? Simply add a boolean flag in your shared prefferences, so every time fragment is created, it can check if it can show toolbar or not e.g. in onViewCreated() method. The example how to use shared prefferences can be found here
Hope I helped a little bit.
I have v4.ViewPager inside Activity and use SlidingTabLayout from google's examples SlidingTabBasics. The problem I encounter is that each fragment retrieved from getItem(position) in v4.FragmentPagerAdapter has to refresh activity title. I have already learnt the hard way that FragmentPagerAdapter causes fragments to have really weird life callbacks so I can't probably use onResume or onStart. I noticed though that onCreateOptionsMenu(menu,inflater) gets called exactly when I want to refresh activity title. Is there a callback to supply actions when ViewPager has settled the fragment and it should change activity title?
Setting callback on ViewPager.onPageSelected(position) is inconvenient because I want this information to be propagated from fragment, not to fragment.
Currently I 'steal' onCreateOptionsMenu(menu,inflater) to do the work for me but it causes optimisation issues when no menu should be inflated but I still want the fragment to be able to affect activity title.
Have you tried this code in your fragment:
if(getActivity() != null){
getActivity().setTitle("new title");
}
Take into consideration that getActivity() will be null if the fragment is not yet attached to the activity.
Godspeed.
You can do this in different ways.
You can use interfaces in fragments that can be implemented by activity. But the drawback is, if you do have large number of fragments you must implement all of them.
I have in several places in my apps the need to hide or show the action bar, the problem is that I am still not sure where is the best place to hide or show the ActionBar on the fragments.
I have only one activity and several different fragments, on some of them I need to completely hide the ActionBar, I succeeded to do it but I am concerned of the fact that this ActionBar is actually attached to the parent activity (I got some NullPointerException in the past ...).
So my question is, where should I hide or show my ActionBar when a fragment is added, and where should I hide or show it when the same fragments are removed?
For now I am thinking about for example hiding it in OnActivityCreated, and then showing it back in onDetach but I am afraid that the ActionBar would remain hidden for good if anything wrong happens in the fragment (and therefore onDetach is not called).
UPDATE: I had to do this in my code to avoid a null pointer on my fragment's onStart method:
#Override
public void onStart(){
super.onStart();
//if the actionbar exists
if(getActivity().getActionBar() != null){
getActivity().getActionBar().hide();
}
}
UPDATE2:
I got actually this exception when trying to do it with onAttach: requestFeature() must be called before adding content. This error happened during screen rotations, I think the reason is that I am not using setRetainInstance and that the fragment is trying to add content to the main activity when this one is restarting due to the configuration change.
In order to fix that, I put my code in onActivityCreated, I guess I will only put this there for now on, and use on onDetach or onDestroy to display the ActionBar again.
I had a same requirement in one project and I was able to hide/show the ActionBar inside a Fragment without inconvenients by using onAttach and onDestroy methods. Then, I use this to hide the actionbar when the fragment is created:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
getActivity().getActionBar().hide();
}
And this, when it is removed:
#Override
public void onDestroy() {
super.onDestroy();
getActivity().getActionBar().show();
}
In the same methods, I was also able to addFlags and clearFlags to make the fragment in fullscreen mode. It worked well. Let me know if this helps.
Best approach is to Hide when the Fragment is attached to the Activity. Since this the actual place where the Fragment is associated with the Activity
public void onAttach(Activity activity) {}
And, Show it back when the Fragment is detached from the Activity
public void onDetach() {}
Note: onDestroy() will not be called when you retrain the fragment instance , by setting setRetainInstance.
if you use v4 support library this is the only way.
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
((ActionBarActivity) getActivity()).getSupportActionBar().hide();
}
#Override
public void onDestroy() {
super.onDestroy();
((ActionBarActivity) getActivity()).getSupportActionBar().show();
}