Different backstack behavior between Nexus 6 device and emulator

360 views Asked by At

I am working on an app that has a support Fragment with a SearchView widget and RecyclerView to present search results which sends the user to a FragmentActivity to display the details of the selection. All of this works fine, but I'm seeing inconsistent behavior between the Nexus 6 emulator and the actual device in regards to the backstack. In the emulator, everything works as I would expect, with the user being taken back to the search results fragment if pressing the Back button while on the details FragmentActivity. On the actual Nexus 6 device, the user is taken all the way back to the AppCompatActivity which contains my app's menu (this activity uses the support FragmentManager, which adds the fragments it manages to the backstack):

private void replaceFragment(Fragment supportFragment) {
    String fragmentName = supportFragment.getClass().getSimpleName();
    supportFragmentManager.beginTransaction()
            .addToBackStack(fragmentName)
            .replace(R.id.frame_menu_container, supportFragment, fragmentName)
            .commit();
}

The code to send the user from the Fragment to the FragmentActivity is just an intent with extras:

Intent intent = new Intent(getActivity(), PlayerDetailsActivity.class);
intent.putExtra(TransientPlayer.BUNDLE_KEY, transientPlayer);
startActivity(intent);

I am not doing anything special with the backstack (yet) - this is with default behavior.

What could be the issue here? I've done some reading on the backstack and I haven't seen anything yet on manually adding something to the backstack when you're going back from a FragmentActivy to a Fragment where you're not using the FragmentManager.

EDIT 1: Here's the layout XML where frame_menu_container is defined:

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:gravity="center"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

    <FrameLayout
        android:id="@+id/frame_menu_container"
        android:layout_below="@id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:layout="@layout/fragment_home">
    </FrameLayout>

</RelativeLayout>

EDIT 2: OK, here is a textual description of my high-level activities and fragments:

NavigationMenuActivity, which extends AppCompatActivity. This is where all the fragments are swapped in and out as needed, and the SupportFragmentManager is used and where I have the replaceFragment() method.

I have the following fragments, all of which either display static information, make REST calls to retrieve and display data, or allow the user to send feedback. All extend android.support.v4.app.Fragment, and none of them have other fragments or activities the user can go to.

  • HomeFragment
  • CalendarFragment
  • StandingsFragment
  • PlayerSearchFragment (see below)
  • AboutFragment
  • FeedbackFragment

The only exception in functionality for these fragments is the PlayerSearchFragment, which also extends android.support.v4.app.Fragment. This fragment contains a RecyclerView that displays the results of a REST call when users want to search for players. When results are returned and the user selects an item from the list, they are sent to a PlayerDetailsActivity which extends FragmentActivity.

The PlayerDetailsActivity uses a FragmentTabHost view which contains different types of information about the player that can be viewed. This is the "end of the line" down this path - there are no other fragments or activities the user can go to. This is where the issue is. When I hit the Back button while on this activity, my intention is to have the user go back to the PlayerSearchFragment fragment (search results), which in this case, they do if I'm in a Nexus 6 emulator, but they go all the way back to the NavigationMenuActivity activity if I'm on my actual Nexus 6 device.

I have the following method in the PlayerDetailsActivity which is called when the Back button is pressed, but it always shows zero entries:

@Override
public void onBackPressed() {

    FragmentManager fragmentManager = getSupportFragmentManager();
    int count = fragmentManager.getBackStackEntryCount();
    Log.i(TAG, "Backstack : There are " + count + " entries");
    for (int i = 0; i > count; i++) {
        FragmentManager.BackStackEntry backStackEntry = fragmentManager.getBackStackEntryAt(i);
        Log.i(TAG, String.format("Backstack : id=%d, name=%s, shortTitle=%s, breadcrumbTitle=%s",
                backStackEntry.getId(),
                backStackEntry.getName(),
                backStackEntry.getBreadCrumbShortTitle(),
                backStackEntry.getBreadCrumbTitle()));
    }

    super.onBackPressed();
}

My hope is to have the experience be the same for all devices, whether hardware or emulator.

4

There are 4 answers

0
TheIcemanCometh On BEST ANSWER

While I still don't have an answer as to why I couldn't get this to work the way I had hoped, I do have a workaround, and it does work well for what I need to do. Basically, I override the Up and Back navigation callbacks in PlayerDetailsActivity and use an intent (with data) to explicitly send the user back to NavigationMenuActivity. The data I send in the intent is basically just a boolean that tells me if I'm coming from the PlayerDetailsActivity and if so, show that fragment rather than the Home fragment, like so:

@Override
public void onBackPressed() {
    sendBackToMenu();
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case android.R.id.home:
            sendBackToMenu();
            finish();
            return true;
    }
    return false;
}

private void sendBackToMenu() {
    Intent backToMenuIntent = new Intent(this, NavigationMenuActivity.class);
    backToMenuIntent.putExtra("FROM_PLAYER_DETAILS", true);
    startActivity(backToMenuIntent);
}

The above changes were after I changed the class to extend from AppCompatActivity and added the following in PlayerDetailsActivity.onCreate() to activate the ActionBar for Up navigation:

ActionBar actionBar = getSupportActionBar();
assert actionBar != null;
actionBar.setTitle(R.string.player_details);
actionBar.setDisplayHomeAsUpEnabled(true);

Also, in NavigationMenuActivity.onCreate() I do this:

if (getIntent().getBooleanExtra("FROM_PLAYER_DETAILS", false))
    replaceFragment(new PlayerSearchFragment());
else           
    replaceFragment(new HomeFragment());

I don't know if this is the most 'appropriate' way to do this, but it work well and does what I need across a handful of hardware devices and Nexus 4/5/6 emulators.

3
The Original Android On

I think you want addToBackStack(fragmentName) called after replace(R.id.frame_menu_container, supportFragment, fragmentName).

Tell me what happens.

1
Ankur Chaudhary On

First thing according to your scenario you don't need to add transaction to BackStack as it's used only when you are navigating in between fragments in the same activity.

Now for your another point that when you back press you are not getting your fragment is you click two times. one click removes your second activity and another one is removing the fragment which is added in your backstack. As for your information Fragment dont have backpress callback, they rely on activity onbackPressed() method

I tried to run this code and it is running properly at my Nexus 5 device. Although I'm attaching code what i am using

public class MainActivity extends FragmentActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    replaceFragment(new FragmentA());


}
private void replaceFragment(Fragment supportFragment) {

    getSupportFragmentManager().beginTransaction()
            .replace(R.id.frame_menu_container, supportFragment
            .commit();
}

}

FragmentA is here:

 public class FragmentA extends Fragment {
    @Nullable
    @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_a, null);

    view.findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(getActivity(), AnotherActivity.class);
            startActivity(intent);
        }
    });
    return view;
}

}

And Another Activity is like:

public class AnotherActivity extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.another_act);

}

}

I don't think that you should face any problem.

In case you still face problem tell me your device name and attach a code here so that i can test and will let you know.

2
Elltz On

well try this, hopefully will get you out of this error-hole.

This answer gives you a workaround but not what is really happening

@Override
public void onBackPressed() {

FragmentManager fragmentManager = getSupportFragmentManager();
int count = fragmentManager.getBackStackEntryCount();
Log.i(TAG, "Backstack : There are " + count + " entries");
if(count > 0){
    fragmentManager.popBackStack(); //or  popBackStackImmediate();
    for (int i = 0; i > count; i++) {
         FragmentManager.BackStackEntry backStackEntry = fragmentManager.getBackStackEntryAt(i);
         Log.i(TAG, String.format("Backstack : id=%d, name=%s, shortTitle=%s, breadcrumbTitle=%s",
            backStackEntry.getId(),
            backStackEntry.getName(),
            backStackEntry.getBreadCrumbShortTitle(),
            backStackEntry.getBreadCrumbTitle()));
   }
}else{
super.onBackPressed();
}

}