NullPointerException when i try to run fragment method from main activity - android

I am new to using fragments, I am trying to execute a fragment method from my main activity, the method compares a textview and then deletes cookies and loads a website.
This is what I have:
In MainActivity:
NavigatorFragment navigator = new NavigatorFragment();
navigator.refresh();
In Fragment:
public void refresh(){
if (!textView.getText().toString().equals("Click HERE")){
cookieManager.removeAllCookie();
cookieManager.removeSessionCookie();
myWebView.loadUrl(URL);
}
}
the result:
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.CharSequence android.widget.TextView.getText()' on a null object reference
EDIT
Sorry but forget to mention that although the fragment is started the same thing happens, I previously used this code to prevent my fragments from restarting: link So when I press item4 of my BottomNavigationView it opens the fragment, in onCreateView:
textView = root.findViewById(R.id.textView);
when I press it again (without having changed fragment) is when I want the method to be executed
I repeat, when I press item 4 BottomNavigationView for the first time, it opens Fragment 4 and loads its elements, when I press item 4 a second time (without having changed the fragment) that's when I want the method to run
Main Activity:
public class MainActivity extends AppCompatActivity {
/*
Fragment MyFragment;
private NavController navController;
*/
NavigatorFragment navigator = new NavigatorFragment();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.nav_view);
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
replace_fragment(new HomeFragment());
}
private void replace_fragment(Fragment fragment) {
String tag = fragment.getClass().getSimpleName();
FragmentTransaction tr = getSupportFragmentManager().beginTransaction();
Fragment curFrag = getSupportFragmentManager().getPrimaryNavigationFragment();
Fragment cacheFrag = getSupportFragmentManager().findFragmentByTag(tag);
if (curFrag != null)
tr.hide(curFrag);
if (cacheFrag == null) {
tr.add(R.id.nav_host_fragment, fragment, tag);
} else {
tr.show(cacheFrag);
fragment = cacheFrag;
}
tr.setPrimaryNavigationFragment(fragment);
tr.commit();
}
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.navigation_home:
replace_fragment(new HomeFragment());
if (!getSupportActionBar().isShowing()){
showBar();
}
return true;
case R.id.navigation_schedule:
replace_fragment(new ScheduleFragment());
if (!getSupportActionBar().isShowing()){
showBar();
}
return true;
case R.id.navigation_calculators:
replace_fragment(new CalculatorsFragment());
if (!getSupportActionBar().isShowing()){
showBar();
}
return true;
case R.id.navigation_navigator:
replace_fragment(new NavigatorFragment());
if (!getSupportActionBar().isShowing()){
navigator.refresh();//**************PROBLEM HERE******************************************
}
hideBar();
return true;
}
return false;
}
};
public void hideBar(){
if (Build.VERSION.SDK_INT >= 19) {
// Call some material design APIs here
Objects.requireNonNull(getSupportActionBar()).hide();
}
else {
// Implement this feature without material design
getSupportActionBar().hide();
}
}
public void showBar(){
if (Build.VERSION.SDK_INT >= 19) {
// Call some material design APIs here
Objects.requireNonNull(getSupportActionBar()).show();
}
else {
// Implement this feature without material design
getSupportActionBar().show();
}
}
}
**the refresh method works if run from the fragment code

When instantiating a fragment instance using new NavigatorFragment() that doesn't involve that the fragment life cycle starts away. Meaning that the fragment isn't created, started, resumed, and shown on the screen.. So, accessing fragment's views at this moment will return NullPointerException.
So, what you need to do is to have a placeholder for the fragment in the activity layout, and start fragment transaction by adding or replacing fragments in this placeholder, which makes the fragment passes by its all life cycle callback methods, and to give it a chance to create its view (layout) on the screen within the onCreateView() lifecycle callback, where you can just allowed to access the underlying TextView or any other view in this fragment, and use their methods like in your case textView.getText() subsequently without NullPointerException.
Here you can find how to use a fragment the right way.

Related

Show and hide fragment within an activity

I'm currently creating functionality to change Fragments using bottom navigation. But instead of destroying them and recreating them, I want to simply hide and show the fragments to preserve the member variables.
I've tried replace(), hide() and show() but haven't managed the get it right, I'm getting animation errors which I'm not able to track down.
I also can't find an example of switching fragments within an AppCompatActivity.
public class MainActivity extends AppCompatActivity {
PassengerFragment passengerFragment;
DriverFragment driverFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BottomNavigationView bottomNav = findViewById(R.id.bottom_navigation);
bottomNav.setOnNavigationItemSelectedListener(navListener);
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container,
new PassengerFragment()).commit();
}
// this handles the bottom navigation so when you click an item it changes fragment
private BottomNavigationView.OnNavigationItemSelectedListener navListener =
new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem menuItem) {
Fragment selectedFragment = null;
switch (menuItem.getItemId()) {
case R.id.nav_passenger:
selectedFragment = passengerFragment;
break;
case R.id.nav_driver:
if (driverFragment==null) {
selectedFragment = new DriverFragment();
}
else {
selectedFragment = driverFragment;
}
break;
}
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container,
selectedFragment).commit(); <---- line 54
return true;
}
};
}
ERROR
Java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Class java.lang.Object.getClass()' on a null object reference
at android.support.v4.app.BackStackRecord.doAddOp(BackStackRecord.java:396)
at android.support.v4.app.BackStackRecord.replace(BackStackRecord.java:444)
at android.support.v4.app.BackStackRecord.replace(BackStackRecord.java:434)
at je.digital.kevin_pickmeup.MainActivity$1.onNavigationItemSelected(MainActivity.java:54)
That is because you are trying do replace with Fragment which are null. You have created passengerFragment and driverFragment as fields or global variables and than you are trying to use them but in some cases especially passengerFragment is null. If you are trying to keep the data in Fragments even they are replaced you can use addToBackStack but you will need to manage not to putting too much to back stack probably by checking getSupportFragmentManager().getBackStackEntryCount() and removing if there are more than 1 using popBackStack() or save the data in Bundle on onDestroyView and restore that data in onCreateView:
just needed better if statements to handle all the cases.
private BottomNavigationView.OnNavigationItemSelectedListener navListener =
new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem menuItem) {
Fragment passengerFragment = getSupportFragmentManager().findFragmentByTag("A");
Fragment driverFragment = getSupportFragmentManager().findFragmentByTag("B");
switch (menuItem.getItemId()) {
case R.id.nav_passenger:
if(passengerFragment.isHidden()) {
getSupportFragmentManager().beginTransaction().show(passengerFragment).commit();
getSupportFragmentManager().beginTransaction().hide(driverFragment).commit();
}
break;
case R.id.nav_driver:
if(driverFragment==null) {
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, new DriverFragment(),"B").commit();
getSupportFragmentManager().beginTransaction().hide(passengerFragment).commit();
}
else {
if(driverFragment.isHidden()) {
getSupportFragmentManager().beginTransaction().show(driverFragment).commit();
getSupportFragmentManager().beginTransaction().hide(passengerFragment).commit();
}
}
break;
}
return true;
}
};

Handling hidden and shown fragments onbackpressed

I have an activity which hosts three Fragment's and I can switch between Fragment's using BottomNavigationView.The mechanism that I use to switch between Fragment's is using show and hide functions of FragmentTransaction instead of replace function of FragmentTransaction. I am doing so because I want some network operations to be done only once and also to inflate Layout only once.
The problem that I am facing using this mechanism is that when I start another Activity from any of the Fragment and then hit the back button the selectedItem of the BottomNavigationView and the Fragment shown are mismatching.
I was able to solve this problem though but I feel it has less efficiency. The procedure was that whenever I clicked a tab in BottomNavigation while switching Fragment's I gave it some predecided number and saved in a static variable(X) and whenever I clicked back button in the OnResume() method of the hosting activity I made a switch-case block using X to know which Fragment was visible before starting the new Activity and then finally making three FragmentTransaction's to show and hide required Fragment's.
protected void onResume() {
super.onResume();
if(selectedId!=63){
switch(selectedId){
case 0:if(bottomNavigationView.getSelectedItemId()==R.id.navigation_home){handleHomeFragmentVisibility();}
break;
case 1:if(bottomNavigationView.getSelectedItemId()==R.id.navigation_dashboard)
{handleDashboardFragmentVisibility();}
break;
case 2:if(bottomNavigationView.getSelectedItemId()==R.id.navigation_notifications)
{handleNotificationFragmentVisibility();}
break;
}
}
I feel using three FragmentTransaction's is costly and I was looking for some efficient way. Can you tell me one if you know ?
public void handleHomeFragmentVisibility(){
FragmentManager fragmentManager= getSupportFragmentManager();
if (fragmentManager.findFragmentByTag("home") != null) {
//if the fragment exists, show it.
fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag("home")).commit();
} else {
//if the fragment does not exist, add it to fragment manager.
Log.e(TAG,"homeFragmentAdded");
fragmentManager.beginTransaction().add(R.id.container, new HomeFragment(), "home").commit();
}
if (fragmentManager.findFragmentByTag("dashboard") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("dashboard")).commit();
}
if (fragmentManager.findFragmentByTag("requests") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("requests")).commit();
}
}
public void handleDashboardFragmentVisibility(){
FragmentManager fragmentManager= getSupportFragmentManager();
if (fragmentManager.findFragmentByTag("dashboard") != null) {
//if the fragment exists, show it.
fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag("dashboard")).commit();
} else {
//if the fragment does not exist, add it to fragment manager.
fragmentManager.beginTransaction().add(R.id.container, new DashboardFragment(), "dashboard").commit();
}
if (fragmentManager.findFragmentByTag("home") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("home")).commit();
}
if (fragmentManager.findFragmentByTag("requests") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("requests")).commit();
}
}
public void handleNotificationFragmentVisibility(){
FragmentManager fragmentManager= getSupportFragmentManager();
if (fragmentManager.findFragmentByTag("requests") != null) {
//if the fragment exists, show it.
fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag("requests")).commit();
} else {
//if the fragment does not exist, add it to fragment manager.
fragmentManager.beginTransaction().add(R.id.container, new NotificationFragment(), "requests").commit();
}
if (fragmentManager.findFragmentByTag("home") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("home")).commit();
}
if (fragmentManager.findFragmentByTag("dashboard") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("dashboard")).commit();
}
}
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
FragmentManager fragmentManager = getSupportFragmentManager();
switch (item.getItemId()) {
case R.id.navigation_home:
selectedId=0;
handleHomeFragmentVisibility();
break;
case R.id.navigation_dashboard:
selectedId=1;
handleDashboardFragmentVisibility();
break;
case R.id.navigation_notifications:
selectedId=2;
handleNotificationFragmentVisibility();
break;
}
return true;
}
});
A first note on your code: Avoid boilerplate! Write only one method instead of three and use a signature of the type handleFragmentVisibility(String show, String hide1, String hide2, int container). In case the fragment to be shown is null, instantiate it by testing for show, something like:
Fragment newFragment = (show == "home") ? new HomeFragment() : (show == "dashboard") ? new DashboardFragment() : new NotificationFragment();
However, none of your fragments should ever get null through hiding (please check for yourself), since you don't remove them from your activity or replace them with other fragments. Instead of using show and hide you could also use attach and detach, both sets of methods keep state. I don't see an efficiency problem and you do indeed need to call three FragmentTransactions. It only can be done with less code:
public void handleFragmentVisibility(String show, String hide1, String hide2){
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag(show)).commit();
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag(hide1)).commit();
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag(hide2)).commit();
}
Please note, that although this method keeps the state of the fragment while hiding or detaching them, other events like orientation change still make it necessary that you take care of saving state in onSaveInstanceState(Bundle savedInstanceState).

Back navigation in title bar - fragment

There are many questions like this asked but everything I have tried seems to not work.
Essentially I have a main activity that calls different fragments depending on what the user clicks with the home fragment being default.
I would like to have a back button on the title bar, to go back to the previous fragment.
My fragment is called from the main activity like so:
Fragment fragment = null;
fragment = new nextFragment();
if (fragment != null) {
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.frame_container, fragment).addToBackStack(null);
fragmentTransaction.commit();
fragmentTransaction.addToBackStack(null);
} else {
// error in creating fragment
Log.e("MainActivity", "Error in creating fragment");
}
But since ActionBarActivity activity is deprecated I need to extend AppCompatActivity instead of FragmentActivity so I can use actionbar (I'm assuming this is what I need).
However then I am unable to switch to my fragment. So does anyone know how I could implement a back button in my fragment or how to use AppCompatActivity in this situation.
Thanks for any help.
Please try this if you extend AppCompatActivity:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Include these 2 lines ONLY if need to use Toolbar from layout xml as Action Bar
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
//Add back navigation in the title bar
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
//
//Other works to be done in onCreate.....
//
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
//Title bar back press triggers onBackPressed()
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
//Both navigation bar back press and title bar back press will trigger this method
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0 ) {
getFragmentManager().popBackStack();
}
else {
super.onBackPressed();
}
}
}
To add Back Button in title bar, you must add the following code to your Fragment.
Toolbar toolbar = (Toolbar)view.findViewById(R.id.app_bar);
AppCompatActivity AppCompatActivity = (AppCompatActivity)getActivity();
AppCompatActivity.setSupportActionBar(toolbar);
AppCompatActivity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
return view;
}
Don't forget to extend your MainActivity to AppCompatActivity.
You must then use this Java code in my Fragment class to react to the user tapping the back/up icon in the action bar.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
getActivity().onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
You've saved the last fragment used by calling
addToBacktack(null).commit()
So the next step forward to call it is by overriding onBackPressed() in the activity hosting the fragment.
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0 ){
getFragmentManager().popBackStack();
}
else {
super.onBackPressed();
}
}
So whenever you call the activity's onBackPressed() from the fragment, the fragment would go back to the last saved fragment.

Android activity with many fragments, the correct way to handle lifecycle changes?

I have an Android activity that holds and manages six fragments, is fragment is a step in a flow, some of the fragments are replaced and some of them are added.
The Activity just uses a Framelayout as the container for the fragments as follows:
<FrameLayout
android:id="#+id/content"
android:layout_below="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Then the flow of the fragments is like this:
//Activity starts, add first Fragment
fragmentManager.beginTransaction().replace(R.id.content, FirstFragment.newInstance(listOfItems)).commit();
then
//User pressed button, activity got callback from first fragment
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.content, fragment2);
transaction.addToBackStack("frag2");
transaction.commit();
then
//Another callback from Frag2, perform the add of frag 3
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(R.id.content, fragment3);
transaction.addToBackStack("frag3");
transaction.commit();
And so on....
I also manage the back stack from the Activity like this:
//Controlling the back stack when the user selects the soft back button in the toolbar
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
if (fragmentManager.getBackStackEntryCount() == 0) {
super.onBackPressed();
overridePendingTransition(R.anim.no_change, R.anim.slide_down);
} else {
if(!BaseFragment.handleBackPressed(getSupportFragmentManager())){
super.onBackPressed();
Fragment fragment = fragmentManager.getFragments()
.get(fragmentManager.getBackStackEntryCount());
fragment.onResume(); //Make sure the fragment that is currently at the top of the stack calls its onResume method
}
}
return true;
}
return super.onOptionsItemSelected(item);
}
//Controlling the back stack when the user selects the "hardware" back button
#Override
public void onBackPressed() {
if (fragmentManager.getBackStackEntryCount() == 0) {
super.onBackPressed();
overridePendingTransition(R.anim.no_change, R.anim.slide_down);
} else {
if(!BaseFragment.handleBackPressed(getSupportFragmentManager())){
super.onBackPressed();
Fragment fragment = fragmentManager.getFragments()
.get(fragmentManager.getBackStackEntryCount());
fragment.onResume(); //Make sure the fragment that is currently at the top of the stack calls its onResume method
}
}
}
My problem is that I open the app and go to this Activity which loads the fragments and then go through the flow to a certain stage ( I haven't narrowed it down yet) then I press the home button and blank my screen. Now after a certain amount of time when I open the app again it opens on the fragment I left but everything seems to be messed up, when I press back it seems to pop the wrong fragment and the UI becomes mixed up with the different fragments.
My guess is that when I open the app again the Activity onResume or the Fragment onResume or some lifecycle event is being called that I am not handling correctly?
So I was wondering is there best practices, guidelines or patterns that should be adhered to when using a Fragment pattern like I am doing so?
Since you have so many fragments in one activity, and they use the same container, that means all fragments are in the same place, and only one fragment will show at a time.
So why don't you use ViewPager and let FragmentPagerAdapter manager these fragments? In this way, you do not need to manager fragment lifecycle by yourself, you just need to override FragmentPagerAdapter methods:
to create fragment instance by getItem,
to update fragment by getItemPosition and Adapter.notifyDataSetChanged(),
to show selected fragment by mViewPager.setCurrentItem(i)
Code snippets, detail refer to https://github.com/li2/Update_Replace_Fragment_In_ViewPager/
private FragmentPagerAdapter mViewPagerAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
#Override
public int getCount() {
return PAGE_COUNT;
}
// Return the Fragment associated with a specified position.
#Override
public Fragment getItem(int position) {
Log.d(TAG, "getItem(" + position + ")");
if (position == 0) {
return Page0Fragment.newInstance(mDate);
} else if (position == 1) {
return Page1Fragment.newInstance(mContent);
}
return null;
}
#Override
// To update fragment in ViewPager, we should override getItemPosition() method,
// in this method, we call the fragment's public updating method.
public int getItemPosition(Object object) {
Log.d(TAG, "getItemPosition(" + object.getClass().getSimpleName() + ")");
if (object instanceof Page0Fragment) {
((Page0Fragment) object).updateDate(mDate);
} else if (object instanceof Page1Fragment) {
((Page1Fragment) object).updateContent(mContent);
}
return super.getItemPosition(object);
};
};

Handling ActionBar title with the fragment back stack?

I have an Activity where I load in a ListFragment and, upon clicking, it drills down a level and a new type of ListFragment is shown, replacing the original one (using the showFragment method below). This is placed on the back stack.
At the beginning, the activity shows the default title in the action bar (i.e. it's set automatically based on the application's android:label).
When showing the list for the next level in the hierarchy, the name of the item clicked on should become the action bar's title.
However, when pressing Back, I would like the original default title to be restored. This isn't something FragmentTransaction knows about, so the title isn't restored.
I've vaguely read about FragmentBreadCrumbs, but this seems to require using a custom view. I'm using ActionBarSherlock and would prefer to not have my own custom title view.
What is the best way of doing this? Is it possible without a load of boilerplate code and having to keep track of the titles shown along the way?
protected void showFragment(Fragment f) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.fragment_container, f);
ft.addToBackStack(null);
ft.commit();
}
In every fragment and every activity I change the title like this. This way the active title will always be correct:
#Override
public void onResume() {
super.onResume();
// Set title
getActivity().getActionBar()
.setTitle(R.string.thetitle);
}
There is some cases where onResume isn't called inside fragments. In some of these cases we can use:
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser) {
// Set title
getActivity().getActionBar()
.setTitle(R.string.thetitle);
}
}
As the original answer is quite old, this might come of help as well. As the documentation states, one might want to register a listener to listen on the back stack changes in the hosting Activity:
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
// Update your UI here.
}
});
Then, identify the situation in the callback method and set a proper title, without accessing the ActionBar from the Fragment.
This is a more elegant solution as the Fragment doesn't have to know about the ActionBar existence and Activity is usually the place that is managing the backstack so having it handled over there seems to be more appropriate. Fragment should at all time be considered only by its own content, not the surroundings.
More on the topic in the documentation.
Let the controlling activity do all the work as follows:
Listen for backstack events (in onCreate() of activity):
// Change the title back when the fragment is changed
getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
Fragment fragment = getFragment();
setTitleFromFragment(fragment);
}
});
Get the current fragment from the container:
/**
* Returns the currently displayed fragment.
* #return
* Fragment or null.
*/
private Fragment getFragment() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.container);
return fragment;
}
Set the fragment inside the content view:
private void setFragment(Fragment fragment, boolean addToBackStack) {
// Set the activity title
setTitleFromFragment(fragment);
.
.
.
}
Warpzit is right. This also solves title problem when orientation of device is changed. Also if you use support v7 for action bar, you can get action bar from fragment like this :
#Override
public void onResume() {
super.onResume();
((ActionBarActivity)getActivity()).getSupportActionBar().setTitle("Home");
}
It is best to let the OS do as much of the work as possible.
Assuming each fragment is properly named using .addToBackStack("title") then
you can override onBackPressed something like this to achieve desired behavior:
// this example uses the AppCompat support library
// and works for dynamic fragment titles
#Override
public void onBackPressed() {
FragmentManager fragmentManager = getSupportFragmentManager();
int count = fragmentManager.getBackStackEntryCount();
if (count <= 1) {
finish();
}
else {
String title = fragmentManager.getBackStackEntryAt(count-2).getName();
if (count == 2) {
// here I am using a NavigationDrawer and open it when transitioning to the initial fragment
// a second back-press will result in finish() being called above.
mDrawerLayout.openDrawer(mNavigationDrawerFragment.getView());
}
super.onBackPressed();
Log.v(TAG, "onBackPressed - title="+title);
getSupportActionBar().setTitle(title);
}
}
I use a similar solution to Lee approach, but replacing onBackStackChanged() method instead.
First I set the fragment name when adding the transaction to the back stack.
getSupportFragmentManager().beginTransaction()
.replace(R.id.frame_content, fragment)
.addToBackStack(fragmentTitle)
.commit();
Then I override the onBackStackChanged() method and I call setTitle() with the last backstack entry name.
#Override
public void onBackStackChanged() {
int lastBackStackEntryCount = getSupportFragmentManager().getBackStackEntryCount() - 1;
FragmentManager.BackStackEntry lastBackStackEntry =
getSupportFragmentManager().getBackStackEntryAt(lastBackStackEntryCount);
setTitle(lastBackStackEntry.getName());
}
Use Fragments method:
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
It is called on every Fragment appearance, but onResume is not.
The best approach is to make use of the android provided Interface OnBackStackChangedListener method onBackStackChanged().
Lets say we have a navigation drawer with 4 options to which the user can navigate to. In that case we will have 4 fragments. Lets see the code first and then I will explain the working.
private int mPreviousBackStackCount = 0;
private String[] title_name = {"Frag1","Frag2","Frag3","Frag4"};
Stack<String> mFragPositionTitleDisplayed;
public class MainActivity extends ActionBarActivity implements FragmentManager.OnBackStackChangedListener
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
....
....
....
getSupportFragmentManager().addOnBackStackChangedListener(this);
mFragPositionTitleDisplayed = new Stack<>();
}
public void displayFragment() {
Fragment fragment = null;
String title = getResources().getString(R.string.app_name);
switch (position) {
case 0:
fragment = new Fragment1();
title = title_name[position];
break;
case 1:
fragment = new Fragment2();
title = title_name[position];
break;
case 2:
fragment = new Fragment3();
title = title_name[position];
break;
case 3:
fragment = new Fragment4();
title = title_name[position];
break;
default:
break;
}
if (fragment != null) {
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.container_body, fragment)
.addToBackStack(null)
.commit();
getSupportActionBar().setTitle(title);
}
}
#Override
public void onBackStackChanged() {
int backStackEntryCount = getSupportFragmentManager().getBackStackEntryCount();
if(mPreviousBackStackCount >= backStackEntryCount) {
mFragPositionTitleDisplayed.pop();
if (backStackEntryCount == 0)
getSupportActionBar().setTitle(R.string.app_name);
else if (backStackEntryCount > 0) {
getSupportActionBar().setTitle(mFragPositionTitleDisplayed.peek());
}
mPreviousBackStackCount--;
}
else{
mFragPositionTitleDisplayed.push(title_name[position]);
mPreviousBackStackCount++;
}
}
In the code shown we have the displayFragment() method. Here I display the fragment on the basis of option chosen from the navigation drawer.The variable position corresponds to the position of the item clicked from the ListView or RecyclerView in the navigation drawer. I set the actionbar title accordingly with getSupportActionBar.setTitle(title), where the title stores the appropriate title name.
Whenever we click the item from nav drawer a fragment is displayed depending on the item clicked to the user. But on the back end side this fragment is added to the backstack and the method onBackStachChanged(), gets hit. What I have done is that I have created a variable mPreviousBackStackCount and initialized it to 0. I have also created an additional stack which will store the action bar title names. Whenever I add a new fragment to the backstack, I add the corresponding title name to my created stack. On the opposite side whenever I press the back button onBackStackChanged() is called and I pop the last title name from my stack and set the title to the name derived by the peek() method of the stack.
Example:
Lets say our android backstack is empty:
Press Choice 1 from nav drawer:
onBackStachChanged() is called and the Fragment 1 is added to android backstack, backStackEntryCount is set to 1 and Frag1 is pushed to my stack and size of mFragPositionTitleDisplayed becomes 1.
Press Choice 2 from nav drawer:
onBackStachChanged() is called and the Fragment 2 is added to android backstack, backStackEntryCount is set to 2 and Frag2 is pushed to my stack and size of mFragPositionTitleDisplayed becomes 2.
Now we have 2 elements both in the android stack and my stack. When you press back button onBackStackChanged() is called and the value of backStackEntryCount is 1. The code enters the if part and pops out the last entry from my stack. So, the android backstack has only 1 fragment - "Fragment 1" and my stack has only 1 title - "Frag1". Now I just peek() the title from my stack and set the action bar to that title.
Remember: To set the action bat title use peek() and not pop() else your application will crash when you open more than 2 fragments and try to go back by pressing back button.
You can Solve with onKeyDown!
I have a bool
mainisopen=true <-- MainFragment is Visible
other Fragment mainisopen=false
and here is My Code:
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && mainisopen == false) {
mainisopen = true;
HomeFrag fragment = new HomeFrag();
FragmentTransaction fragmentTransaction =
getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragmet_cont, fragment);
fragmentTransaction.commit();
navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.getMenu().findItem(R.id.nav_home).setChecked(true);
navigationView.setNavigationItemSelectedListener(this);
this.setTitle("Digi - Home"); //Here set the Title back
return true;
} else {
if (keyCode == KeyEvent.KEYCODE_BACK && mainisopen == true) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Wollen sie die App schliessen!");
builder.setCancelable(true);
builder.setPositiveButton("Ja!", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
System.exit(1);
}
});
builder.setNegativeButton("Nein!", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(getApplicationContext(), "Applikation wird fortgesetzt", Toast.LENGTH_SHORT).show();
}
});
AlertDialog dialog = builder.create();
dialog.show();
return true;
}
return super.onKeyDown(keyCode, event);
}
}
As described here my solution is adding this code to MainActivity onCreate method(): and changing actionbar title
FragmentManager fragmentManager=getSupportFragmentManager();
fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
Fragment currentFragment = fragmentManager.findFragmentById(R.id.My_Container_1_ID);
currentFragment.onResume();
}
});
and changing actionbar title in fragment's onResume() method
#Override
public void onResume() {
super.onResume();
AppCompatActivity activity = (AppCompatActivity) getActivity();
ActionBar actionBar = activity.getSupportActionBar();
if(actionBar!=null) {
actionBar.setTitle("Fragment Title");
actionBar.setSubtitle("Subtitle");
}
}
To update the actionbar title on back press. Just simply put
getActivity.setTitle("title")
inside onCreateView method.

Categories

Resources