BottomNavigationView back button behavior should work like the Youtube App but crashes


BottomNavigationView back button behavior should work like the Youtube App but crashes



Steps to reproduce:


BottomNavigationView



Replace MainActivity with this:


class MainActivity : AppCompatActivity() {

private var fragmentIds = ArrayList<Int>()

val fragmentA: FragmentA = FragmentA()
private val fragmentB = FragmentB()
private val fragmentC = FragmentC()

private fun getFragment(fragmentId: Int): Fragment {
when (fragmentId) {
R.id.navigation_home -> {
return fragmentA
}
R.id.navigation_dashboard -> {
return fragmentB
}
R.id.navigation_notifications -> {
return fragmentC
}
}
return fragmentA
}

private fun updateView(fragmentId: Int) {
var exists = false
fragmentIds
.filter { it == fragmentId }
.forEach { exists = true }

if (exists) {
fragmentIds.remove(fragmentId)
showTabWithoutAddingToBackStack(getFragment(fragmentId))
} else {
fragmentIds.add(fragmentId)
showTab(getFragment(fragmentId))
}
}

private val onNavigationItemClicked = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_home -> {
updateView(R.id.navigation_home)
return@OnNavigationItemSelectedListener true
}
R.id.navigation_dashboard -> {
updateView(R.id.navigation_dashboard)
return@OnNavigationItemSelectedListener true
}
R.id.navigation_notifications -> {
updateView(R.id.navigation_notifications)
return@OnNavigationItemSelectedListener true
}
}
false
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
showTabWithoutAddingToBackStack(fragmentA)

navigation.setOnNavigationItemSelectedListener(onNavigationItemClicked)

}

private fun showTab(fragment: Fragment) {
supportFragmentManager
.beginTransaction()
.replace(R.id.main_container, fragment, fragment::class.java.simpleName)
.addToBackStack(fragment::class.java.simpleName)
.commit()
}

fun showTabWithoutAddingToBackStack(fragment: Fragment) {
supportFragmentManager
.beginTransaction()
.replace(R.id.main_container, fragment, fragment::class.java.simpleName)
.commit()
}

fun setBottomTab(id: Int) {
navigation.setOnNavigationItemSelectedListener(null)
navigation.selectedItemId = id
// currentTab = id
navigation.setOnNavigationItemSelectedListener(onNavigationItemClicked)
}
}



Create 3 new classes, FragmentA, FragmentB and FragmentC:


class FragmentA : Fragment() {

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
setHasOptionsMenu(true)
return inflater.inflate(R.layout.fragment_a, container, false)
}

override fun onResume() {
super.onResume()
val act = activity as MainActivity
act.setBottomTab(R.id.navigation_home)
}
}



with this xml:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fragment A" />
</LinearLayout>



Here is a video that demonstrates above steps



Stacktrace:


12-06 12:58:35.899 25903-25903/com.example.jimclermonts.bottomnavigationview E/InputEventSender: Exception dispatching finished signal.
12-06 12:58:35.900 25903-25903/com.example.jimclermonts.bottomnavigationview E/MessageQueue-JNI: Exception in MessageQueue callback: handleReceiveCallback
12-06 12:58:35.912 25903-25903/com.example.jimclermonts.bottomnavigationview E/MessageQueue-JNI: java.lang.**IllegalStateException: Fragment already added**: FragmentB{3aac1d9 #1 id=0x7f080059 FragmentB}
at android.support.v4.app.FragmentManagerImpl.addFragment(FragmentManager.java:1882)
at android.support.v4.app.BackStackRecord.executePopOps(BackStackRecord.java:825)
at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2577)
at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2367)
at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2322)
at android.support.v4.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:851)
at android.support.v4.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:794)
at android.support.v4.app.FragmentActivity.onBackPressed(FragmentActivity.java:174)





"I want XXX behavior" Ok, what's wrong with your current implementation?
– azizbekian
Nov 22 '17 at 13:31






@azizbekian I want it to be the same as the Youtube App. But now the back button behavior is not the same.
– Jim Clermonts
Nov 22 '17 at 13:46





@JimClermonts what does exactly "is not the same" mean?
– Benjamin
Dec 5 '17 at 13:47





you means you want to like this one : github.com/pedrovgs/DraggablePanel
– Saif
Dec 5 '17 at 13:58





@saif I'm talking about the bottom navigation, the tabs. It has to be the same as in the youtube app
– Jim Clermonts
Dec 5 '17 at 14:34




4 Answers
4



I have implemented this concept using Bottombar library. I have uploaded to GitHub. Please check and comment here if any issues.



https://github.com/itvignes09/youtube-like-bttom-menu



Sample output



enter image description here





This solution is almost 100% correct. But when going A,B,C,D,E,D,C,B,A and then back 5 times, the last should always be the Home Fragment and then it should shut down.
– Jim Clermonts
Dec 13 '17 at 12:21





okay. I will update my solution
– Vigneswaran A
Dec 13 '17 at 12:23





I have updated the solution. Please check and let me know
– Vigneswaran A
Dec 13 '17 at 12:29





This uses a ViewPager, YouTube doesn't use a ViewPager...
– ClassA
Apr 27 at 14:11


ViewPager


ViewPager



This lets you have the "do not repeat (but reorder) my fragments on the backstack" behavior using a custom backstack (deque):


class MainActivity extends AppCompatActivity {
private BottomNavigationView navigation;
// initialize with number of different fragments
private Deque<Integer> fragmentIds = new ArrayDeque<>(3);

private FragmentA fragmentA = new FragmentA();
private FragmentB fragmentB = new FragmentB();
private FragmentC fragmentC = new FragmentC();

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragmentIds.push(R.id.navigation_home);
showTabWithoutAddingToBackStack(fragmentA);
navigation = findViewById(R.id.navigation);
navigation.setOnNavigationItemSelectedListener(onNavigationItemClicked);
}

private BottomNavigationView.OnNavigationItemSelectedListener onNavigationItemClicked = new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
item.setChecked(true);
int itemId = item.getItemId();
if (fragmentIds.contains(itemId)) {
fragmentIds.remove(itemId);
}
fragmentIds.push(itemId);
showTabWithoutAddingToBackStack(getFragment(item.getItemId()));
return true;
}
};

private Fragment getFragment(int fragmentId) {
switch (fragmentId) {
case R.id.navigation_home:
return fragmentA;
case R.id.navigation_dashboard:
return fragmentB;
case R.id.navigation_notifications:
return fragmentC;
}
return fragmentA;
}

void showTabWithoutAddingToBackStack(Fragment fragment) {
getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment, fragment.getClass().getSimpleName()).commit();
}

void setBottomTab(int id) {
int itemIndex;
switch (id) {
case R.id.navigation_dashboard:
itemIndex = 1;
break;
case R.id.navigation_notifications:
itemIndex = 2;
break;
default:
case R.id.navigation_home:
itemIndex = 0;
}
navigation.getMenu().getItem(itemIndex).setChecked(true);
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
onBackPressed();
return true;
}
return super.onKeyDown(keyCode, event);
}

@Override
public void onBackPressed() {
fragmentIds.pop();
if (!fragmentIds.isEmpty()) {
showTabWithoutAddingToBackStack(getFragment(fragmentIds.peek()));
} else {
finish();
}
}
}





This implementation is not correct. When pressing rapidly between 2 tabs. I have to press back the same amount of times when going back. The backstack behaviour of the Youtube App is that it remembers the fragment for 1 back press.
– Jim Clermonts
Dec 11 '17 at 11:35






Secondly, When pressing "Home", "Dashboard", "Notifications", "Dashboard", "Home". The back button behaviour should be: "Dashboard", "Notifications", "Home", Close App. Right now this is not the case it memorizes the whole stack.
– Jim Clermonts
Dec 11 '17 at 11:36





Hm, shouldn't you describe this behavior in your question? In your own words and without referring mainly to an example or to "some app out there"? Something like: "Replacing a fragment should add the replaced fragment to the backstack, only if that fragment has not been added to the backstack before or has been popped from the backstack since." Instead we get an error message which I can't reproduce with your code?
– kalabalik
Dec 11 '17 at 11:51






@JimClermonts Please check my new implementation. In case it does not meet your requirements, please disclose them!
– kalabalik
Dec 11 '17 at 22:58



I have to change it a bit and got it working, and I have tested wit multiple devices and switching the tabs for more than 100 times in one test run.
Its working fine, change following code,


private fun updateView(fragmentId: Int) {
var exists = false
fragmentIds
.filter { it == fragmentId }
.forEach { exists = true }

if (exists) {
fragmentIds.remove(fragmentId)
showTabWithoutAddingToBackStack(getFragment(fragmentId))
} else {
fragmentIds.add(fragmentId)
showTab(getFragment(fragmentId))
}
}



To this new code,


private fun updateView(fragmentId: Int) {
var exists = false
fragmentIds
.filter { it == fragmentId }
.forEach { exists = true }

if (exists) {
showTab(getFragment(fragmentId))
setBottomTab(fragmentId)
} else {
fragmentIds.add(fragmentId)
showTab(getFragment(fragmentId))
}
}



That's it !!!



Hello Please check this I made this and working fine.
you can check this. https://github.com/sandeshsk/BackStackFragmentRedirectsToHome



Please update if there is any issue.



This is a method which assigns fragment


public void addFragment(FragmentManager fragmentManager,
Fragment fragment,
int containerId,boolean isFromHome){

fragmentManager.popBackStack(null,FragmentManager.POP_BACK_STACK_INCLUSIVE);

FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
if(isFromHome){
fragmentTransaction.replace(containerId,fragment);
}else{
fragmentTransaction.add(new HomeFragment(),"Home");
fragmentTransaction.addToBackStack("Home");
}
fragmentTransaction.replace(containerId,fragment).commit();

}



This is your navigation item listener


private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {

@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.navigation_home:
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
addFragment(getSupportFragmentManager(), new HomeFragment(), R.id.frame, true);
}else{
getSupportFragmentManager().popBackStack();
}
return true;
case R.id.navigation_dashboard:
addFragment(getSupportFragmentManager(),new DashboardFragment(),R.id.frame,false);
return true;
case R.id.navigation_notifications:
addFragment(getSupportFragmentManager(),new NotificationFragment(),R.id.frame,false);
return true;
case R.id.navigation_setting:
addFragment(getSupportFragmentManager(),new SettingFragment(),R.id.frame,false);
return true;
}
return false;
}
};



onBackPressed method


@Override
public void onBackPressed() {
if(getSupportFragmentManager().getBackStackEntryCount()>0){
navigation.setSelectedItemId(R.id.navigation_home);
}else {
super.onBackPressed();
}
}






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

List of Kim Possible characters

Audio Livestreaming with Python & Flask

NSwag: Generate C# Client from multiple Versions of an API