Back Stack in fragments not working - java

I have a home activity in that I am replacing fragments as required.
In home activity I have main fragment, then from main fragment I am replacing a Transport fragment, from Transport fragment I am replacing TransportList Fragment.
Now as I press back from TransportList fragment I see the main fragment instead of Transport fragment.
I have added the fragments to backstack still its working like this.
Home activity
public class HomeActivity extends AppCompatActivity{
private boolean mBackPressCancelled = false;
private static final long BACK_PRESS_DELAY = 10000;
private long mBackPressTimestamp;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FragmentManager fragmentManager = HomeActivity.this.getFragmentManager();
MainFragment fragment = new MainFragment();
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
fragmentManager.beginTransaction().replace(R.id.mycontainer, fragment,"MAIN_FRAGMENT").commitAllowingStateLoss();
}
#Override
public void onBackPressed() {
// Do nothing if the back button is disabled.
if (!mBackPressCancelled) {
// Pop fragment if the back stack is not empty.
if (getFragmentManager().getBackStackEntryCount() > 0) {
mTxtTitle.setVisibility(View.GONE);
mLogo.setVisibility(View.VISIBLE);
super.onBackPressed();
}
else {
if (snackbar != null) {
snackbar.dismiss();
}
long currentTimestamp = System.currentTimeMillis();
if (currentTimestamp < mBackPressTimestamp + BACK_PRESS_DELAY) {
super.onBackPressed();
} else {
mBackPressTimestamp = currentTimestamp;
Toast.makeText(this,"press again",Toast.LENGTH_LONG).show();
}
}
}
}
}
Transport fragment :
mBtnSearch.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
FragmentManager fragmentManager = getFragmentManager();
TransportListFragment fragment1 = new TransportListFragment();
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
fragmentManager.beginTransaction().replace(R.id.mycontainer, fragment1).addToBackStack("G").commit();
}
});
Whats going wrong here please help.Thank you.

Just remove the following lines when you add new Fragment to the BackStack:
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);

Related

Multiple fragments saveInstance using bottom nav android problem

My app has bottom navigation view .Using bottom navigation I'm switching the fragments .I want to save the instance of every fragment .but I'm unable to do that because my code is missing something.Please help me to solve that issue. My code:
public class MainActivity extends AppCompatActivity {
private MyFragment fragment;
private YourFragment yourFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BottomNavigationView bottomNavigationView=findViewById(R.id.bottomNavigationView);
bottomNavigationView.setOnItemSelectedListener(item -> {
switch (item.getItemId()){
case R.id.frag_one:
if (savedInstanceState == null) {fragment = new MyFragment();
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.container, fragment, "myFragmentName")
.commit();
} else {
fragment = (MyFragment) getSupportFragmentManager()
.findFragmentByTag("myFragmentName");
}
break;
case R.id.frag_two:
if (savedInstanceState == null) {yourFragment = new YourFragment();
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.container,yourFragment,"myFragmentName")
.commit();
} else {
yourFragment = (YourFragment) getSupportFragmentManager()
.findFragmentByTag("myFragmentName");
}
break;
}
return true;
});
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
getSupportFragmentManager().putFragment(outState, "myFragmentName", fragment);
getSupportFragmentManager().putFragment(outState,"YourFragmentName",yourFragment);
}
}

How can I use the transaction backstack to pop fragments?

Suppose I have a MainActivity with FragmentContainerView.
This FragmentContainerView's size matches the MainActivity.
Then I want to show one fragment at a time in this FragmentContainerView.
Fragment1, Fragment2, and Fragment3.
Each fragment will have one button. When button is pressed, next fragment is loaded.
Fragment1 (Press button)--> Fragment2 (Press button)--> Fragment3 (Press button)--> Fragment1 --> and so on.
So far, I was able to implement, but I am not sure how to make my fragments such that, when I press the back button,
Fragment1 should exit app
Fragment2 should load Fragment1
Fragment3 should load Fragment2
Here is my MainActivity
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FirstFragment fragment = new FirstFragment();
Bundle bundle = new Bundle();
bundle.putString("CLASS_NAME", " ");
fragment.setArguments(bundle);
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container_view, fragment, null)
.commit();
}
}
Here is my Fragment1
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding = FragmentFirstBinding.bind(getView());
String previous_class = getArguments().getString("CLASS_NAME");
if(!previous_class.equals(" ")){
binding.textViewFirstFragment.setText(previous_class);
}
//On button click, navigate to MainActivity
binding.buttonFirstFragment.setOnClickListener(this);
}
#Override
public void onClick(View v) {
//Log.i("BACKSTACK", "debug: current status of fragment back stack " + getFragmentManager().getBackStackEntryCount());
Fragment fragment = new SecondFragment();
putBundleArgument(fragment);
replaceFragment(fragment);
debug();
}
public void replaceFragment(Fragment fragment) {
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container_view, fragment);
transaction.addToBackStack(null);
transaction.commit();
}
Here is my Fragment2
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding = FragmentSecondBinding.bind(getView());
String previous_class = getArguments().getString("CLASS_NAME");
binding.textViewSecondFragment.setText(previous_class);
//On button click, navigate to MainActivity
binding.buttonSecondFragment.setOnClickListener(this);
}
#Override
public void onClick(View v) {
//Log.i("BACKSTACK", "debug: current status of fragment back stack " + getFragmentManager().getBackStackEntryCount());
Fragment fragment = new ThirdFragment();
putBundleArgument(fragment);
replaceFragment(fragment);
debug();
}
public void replaceFragment(Fragment fragment) {
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container_view, fragment);
transaction.addToBackStack(null);
transaction.commit();
}
Lastly, here is my Fragment3
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding = FragmentThirdBinding.bind(getView());
String previous_class = getArguments().getString("CLASS_NAME");
binding.textViewThirdFragment.setText(previous_class);
//On button click, navigate to MainActivity
binding.buttonThirdFragment.setOnClickListener(this);
}
#Override
public void onClick(View v) {
Fragment fragment = new FirstFragment();
putBundleArgument(fragment);
replaceFragment(fragment);
debug();
}
public void replaceFragment(Fragment fragment) {
FragmentTransaction transaction = getFragmentManager().beginTransaction();
/*
ThirdFragment fragment3 = new ThirdFragment();
transaction.remove(fragment3);
SecondFragment fragment2 = new SecondFragment();
transaction.remove(fragment2);
*/
getFragmentManager().popBackStack();
getFragmentManager().popBackStack();
getFragmentManager().popBackStack();
transaction.replace(R.id.fragment_container_view, fragment);
transaction.addToBackStack(null);
transaction.commit();
}
#Override // override this in MainActivity
public void onBackPressed(){
int count = getSupportFragmentManager().getBackStackEntryCount();
if(count == 1)//When there is only 1 fragment left in the stack ie Fragment 1.
finsh();
else
getSupportFragmentManager().popBackStack(); //Fragment 2 and 3 are popped here.
super.onBackPressed();
}

Set Text of EditeText of Fragment from Activity

How can I set the text of an EditText of an Fragment from the Activity?
Here es the Activity code:
public class MainActivity extends AppCompatActivity
{
FrameLayout simpleFrameLayout;
TabLayout tabLayout;
EditText editText;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
simpleFrameLayout = (FrameLayout) findViewById(R.id.simpleFrameLayout);
tabLayout = (TabLayout) findViewById(R.id.simpleTabLayout);
// Create a new Tab named "First"
TabLayout.Tab firstTab = tabLayout.newTab();
firstTab.setText("Personal"); // set the Text for the first Tab
tabLayout.addTab(firstTab); // add the tab at in the TabLayout
// Create a new Tab named "Second"
TabLayout.Tab secondTab = tabLayout.newTab();
secondTab.setText("Chat"); // set the Text for the second Tab
tabLayout.addTab(secondTab); // add the tab in the TabLayout
try {
Fragment fragment = null;
fragment = new FirstFragment();
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.simpleFrameLayout, fragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.commit();
}
catch (Exception e) {
}
tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
// get the current selected tab's position and replace the fragment accordingly
try {
Fragment fragment = null;
switch (tab.getPosition()) {
case 0:
fragment = new FirstFragment();
break;
case 1:
fragment = new SecondFragment();
break;
}
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.simpleFrameLayout, fragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.commit();
}
catch (Exception e) {
}
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
public void connect(){
//set text of EditText of SecondFragment
}
}
I am looking for a solution to this problem:
I want to create a chat client and for that I need to update the EditText of a tab from time to time without the need to switch to that tab. I also need the content of that EditText not to be lost when switching tabs.
Just create a variable inside the main activity that you can update from the fragments. Since the activity's lifecycle doesn't change from one fragment to the other, you can then get the same value from the second fragment.
I hope this helps.
Assuming you don't want to use a ViewPager and a PagerAdapter (you don't need swiping between your tabs), this is what it should look like:
public class MainActivity extends AppCompatActivity
{
FrameLayout simpleFrameLayout;
TabLayout tabLayout;
EditText editText;
FirstFragment firstFragment;
SecondFragment secondFragment;
private int selectedTabIndex = 0;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
simpleFrameLayout = findViewById(R.id.simpleFrameLayout);
tabLayout = findViewById(R.id.simpleTabLayout);
TabLayout.Tab firstTab = tabLayout.newTab();
firstTab.setText("Personal");
tabLayout.addTab(firstTab);
TabLayout.Tab secondTab = tabLayout.newTab();
secondTab.setText("Chat");
tabLayout.addTab(secondTab);
FragmentManager fm = getSupportFragmentManager();
if (savedInstanceState == null) {
firstFragment = new FirstFragment();
secondFragment = new SecondFragment();
fm.beginTransaction()
.add(R.id.simpleFrameLayout, firstFragment, "firstFragment")
.add(R.id.simpleFrameLayout, secondFragment, "secondFragment");
.detach(secondFragment)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
.commitNow();
} else {
firstFragment = (FirstFragment) fm.findFragmentByTag("firstFragment");
secondFragment = (SecondFragment) fm.findFragmentByTag("secondFragment");
this.selectedTabIndex = savedInstanceState.getInt("selectedTabIndex", 0);
// selectTab(selectedTabIndex); // you don't need this line
}
tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
this.selectedTabIndex = tab.getPosition();
selectTab(selectedTabIndex);
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
private void selectTab(int selectedTabIndex) {
FragmentManager fm = getSupportFragmentManager();
if (selectedTabIndex == 0) {
fm.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.detach(secondFragment)
.attach(firstFragment)
.commitAllowingStateLoss();
} else if(selectedTabIndex == 1) {
fm.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.detach(firstFragment)
.attach(secondFragment)
.commitAllowingStateLoss();
}
}
public void connect(){
secondFragment.updateText("someText"); // this can run when secondFragment has no view!
}
}
Note: if you need to use more tabs, then you could use an array, but you'd still need to initialize / reinitialize as it is shown in this snippet.
A general answer based on your general question Make your edittext public static public static EditText editText;
Assign a value to it then call it like MainActivity.editText.setText(""); from any other class
`
Use this approach only if its absolutely necessary as it may lead to memory leaks since you will have to manually destroy/nullify your object after use

Retain data in Fragment switching

Working on an app that included 3 fragments. I want to retain data in the FragmentA when the user decides to switch back to FragmentA from FragmentB or FragmentC via BottomNavigationView.
My code works great with changing screen orientation, but for some reason it wont retain data on fragment change.
For testing, I've used only one EditText that should retain data from string.
MainActivity
public class MainActivity extends AppCompatActivity implements
BottomNavigationView.OnNavigationItemSelectedListener {
Fragment Frag = new Fragment();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BottomNavigationView navigation = findViewById(R.id.navigation);
navigation.setOnNavigationItemSelectedListener(this); // mOnNavigationItemSelectedListener
if (savedInstanceState != null)
{
Frag = getSupportFragmentManager().getFragment(savedInstanceState, "Frag");
}
else
loadFragment(new FragmentA());
}
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem menuItem) {
Fragment fragment = null;
int i = menuItem.getItemId();
if (i == R.id.fragmentA) {
fragment = new FragmentA();
} else if (i == R.id.fragmentB) {
fragment = new FragmentB();
} else if (i == R.id.fragmentC) {
fragment = new FragmentC();
}
return loadFragment(fragment);
}
private boolean loadFragment(Fragment fragment)
{
if (fragment != null)
{
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment).commit();
Frag = fragment;
return true;
}
return false;
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
getSupportFragmentManager().putFragment(outState, "Frag", Frag);
}
}
FragmentA
public class FragmentA extends Fragment {
EditText editText;
String string;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null)
{
string = savedInstanceState.getString("Text", "");
}
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
// return super.onCreateView(inflater, container, savedInstanceState);
LayoutInflater layoutInflater = getActivity().getLayoutInflater();
View view = layoutInflater.inflate(R.layout.fragmentA, container, false);
editText = view.findViewById(R.id.centerText);
editText.setText(string);
return view;
}
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
string = editText.getText().toString();
outState.putString("Text", string);
}
}
I want that editText retains data when user switches back to FragmentA from FragmentB or FragmentC.
I've been struggling with this problem for some time now, trying different methods with no luck.
Either you can keep the references to your fragments, instead of creating new ones every time like you do here:
if (i == R.id.fragmentA) {
fragment = new FragmentA();
} else if (i == R.id.fragmentB) {
fragment = new FragmentB();
} else if (i == R.id.fragmentC) {
fragment = new FragmentC();
}
You could also use a ViewPager which allows you not only to swipe in-between the screens, but to set viewPager.setOffscreenPageLimit(i); which means that fragments will not be recreated unless they're too far off screen.
Here is your mistake
if (i == R.id.fragmentA) {
fragment = new FragmentA();
} else if (i == R.id.fragmentB) {
fragment = new FragmentB();
} else if (i == R.id.fragmentC) {
fragment = new FragmentC();
}
On click you create new fragment instance
Thank you for your replies.
This is what I changed. It is now working although I'm not sure if that's the elegant way to solve this problem. Data now retains in EditText while navigating back to FragmentA.
MainActivity
public class MainActivity extends AppCompatActivity implements
BottomNavigationView.OnNavigationItemSelectedListener {
Fragment Frag = new Fragment();
Fragment FragmentA;
Fragment FragmentB;
Fragment FragmentC;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BottomNavigationView navigation = findViewById(R.id.navigation);
navigation.setOnNavigationItemSelectedListener(this); // mOnNavigationItemSelectedListener
FragmentA = new FragmentA();
FragmentB = new FragmentB();
FragmentC = new FragmentC();
if (savedInstanceState != null)
{
Frag = getSupportFragmentManager().getFragment(savedInstanceState, "Frag");
}
else
loadFragment(FragmentA);
}
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem menuItem) {
Fragment fragment = null;
int i = menuItem.getItemId();
if (i == R.id.fragmentA) {
fragment = FragmentA;
} else if (i == R.id.fragmentB) {
fragment = FragmentB;
} else if (i == R.id.fragmentC) {
fragment = FragmentC;
}
return loadFragment(fragment);
}
private boolean loadFragment(Fragment fragment)
{
if (fragment != null)
{
getSupportFragmentManager().beginTransaction().hide(FragmentA).commit();
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment).commit();
getSupportFragmentManager().beginTransaction().show(fragment).commit();
Frag = fragment;
return true;
}
return false;
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
getSupportFragmentManager().putFragment(outState, "Frag", Frag);
}
}

BottomNavigationView - How to avoid recreation of Fragments and reuse them

I would like to make a bottom navigation bar in my project. Every view has it's own fragment. The problem is that every time i click on the button to change the view for example from recents to favorites it creates new fragment with completely new states(e.g. scroll position, text changed whatever my fragment contains). I know that in official Android documentation there was written that bottom navigation bar should reset the task states, but i think it is too uncomfortable for users.
I would like to have kind of similar functionality like instagram that you change from feed to explore and back to feed the scroll position the image caches everything remains saved. I tried almost every way to solve this problem the only thing that worked is by setting visibility GONE and setting visibility VISIBLE according to situation but i understand that it is not RIGHT way there should be better way of doing this and i am not talking about manually saving needed instances. I followed almost every tutorial about bottom nav fragments but the interesting thing is that no one is interested to make it use without calling new every time.
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.frameLayout, FirstFragment.newInstance());
fragmentTransaction.commit();
bottomNavigationView = (BottomNavigationView) findViewById(R.id.navigation);
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment fragment = null;
switch (item.getItemId()) {
case R.id.menu_dialer:
fragment = FirstFragment.newInstance();
break;
case R.id.menu_email:
fragment = SecondFragment.newInstance();
break;
case R.id.menu_map:
fragment = ThirdFragment.newInstance();
break;
}
if (fragment != null) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.frameLayout, fragment);
fragmentTransaction.commit();
}
return true;
}
});
I also tried the onAttach and Deattach solutions but again no success.
VIDEO LINK : new i tried Nino Handler version it only works when i tap on the same fragment button
Maybe it is connected that i am using canary version or something wrong in my gradle dependencies?
NEW UPDATES:
public class MainActivity extends AppCompatActivity {
private TextView mTextMessage;
private static final String TAG_FRAGMENT_ONE = "fragment_one";
private static final String TAG_FRAGMENT_TWO = "fragment_two";
private FragmentManager fragmentManager;
private Fragment currentFragment;
String TAG = "babken";
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
Fragment fragment = null;
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.navigation_home:
fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_ONE);
if (fragment == null) {
fragment = FragmentFirst.newInstance();
}
replaceFragment(fragment, TAG_FRAGMENT_ONE);
break;
case R.id.navigation_dashboard:
fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_TWO);
if (fragment == null) {
fragment = FragmentSecond.newInstance();
}
replaceFragment(fragment, TAG_FRAGMENT_TWO);
break;
}
return true;
}
};
private void replaceFragment(#NonNull Fragment fragment, #NonNull String tag) {
if (!fragment.equals(currentFragment)) {
fragmentManager
.beginTransaction()
.replace(R.id.armen, fragment, tag)
.commit();
currentFragment = fragment;
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_ONE);
if (fragment == null) {
fragment = FragmentFirst.newInstance();
}
replaceFragment(fragment, TAG_FRAGMENT_ONE);
BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
}
}
I had similar issue, but this code solved my problem.
public class MainActivity extends AppCompatActivity {
boolean doubleBackToExitPressedOnce = false;
final Fragment fragment1 = new HomeFragment();
final Fragment fragment2 = new DashboardFragment();
final Fragment fragment3 = new NotificationsFragment();
final FragmentManager fm = getSupportFragmentManager();
Fragment active = fragment1;
BottomNavigationView navigation;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
navigation = (BottomNavigationView) findViewById(R.id.navigation);
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
setFragment(fragment1, "1", 0);
}
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.navigation_home:
setFragment(fragment1, "1", 0);
return true;
case R.id.navigation_dashboard:
setFragment(fragment2, "2", 1);
return true;
case R.id.navigation_notifications:
setFragment(fragment3, "3", 2);
return true;
}
return false;
}
};
public void setFragment(Fragment fragment, String tag, int position) {
if (fragment.isAdded()) {
fm.beginTransaction().hide(active).show(fragment).commit();
} else {
fm.beginTransaction().add(R.id.main_container, fragment, tag).commit();
}
navigation.getMenu().getItem(position).setChecked(true);
active = fragment;
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_menu, menu);
return super.onCreateOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
startActivity(new Intent(MainActivity.this, SettingsActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public void onBackPressed() {
if (active == fragment1) {
if (doubleBackToExitPressedOnce) {
super.onBackPressed();
return;
}
this.doubleBackToExitPressedOnce = true;
Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT).show();
} else {
setFragment(fragment1, "1", 0);
}
}
}
I wouldn't keep the fragment instances globally.
Instead add a tag to the fragment when creating them
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.container, new PlaceholderFragment(), TAG_PLACEHOLDER)
.commit();
Then you can always retrieve it like this:
Fragment fragment = getSupportFragmentManager().findFragmentByTag(TAG_PLACEHOLDER);
if (fragment == null) {
fragment = new PlaceholderFragment();
}
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.container, fragment, TAG_PLACEHOLDER)
.commit();
UPDATE: I updated my answer and to provide a complete solution:
private static final String TAG_FRAGMENT_ONE = "fragment_one";
private static final String TAG_FRAGMENT_TWO = "fragment_two";
private static final String TAG_FRAGMENT_THREE = "fragment_three";
private FragmentManager fragmentManager;
private Fragment currentFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// instantiate the fragment manager
fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_ONE);
if (fragment == null) {
fragment = FirstFragment.newInstance();
}
replaceFragment(fragment, TAG_FRAGMENT_ONE);
bottomNavigationView = (BottomNavigationView) findViewById(R.id.navigation);
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment fragment = null;
switch (item.getItemId()) {
case R.id.menu_dialer:
// I'm aware that this code can be optimized by a method which accepts a class definition and returns the proper fragment
Fragment fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_ONE);
if (fragment == null) {
fragment = FirstFragment.newInstance();
}
replaceFragment(fragment, TAG_FRAGMENT_ONE);
break;
case R.id.menu_email:
Fragment fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_TWO);
if (fragment == null) {
fragment = SecondFragment.newInstance();
}
replaceFragment(fragment, TAG_FRAGMENT_TWO);
break;
case R.id.menu_map:
Fragment fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_THREE);
if (fragment == null) {
fragment = ThirdFragment.newInstance();
}
replaceFragment(fragment, TAG_FRAGMENT_THREE);
break;
}
return true;
}
});
}
private void replaceFragment(#NonNull Fragment fragment, #NonNull String tag) {
if (!fragment.equals(currentFragment)) {
fragmentManager
.beginTransaction()
.replace(R.id.frameLayout, fragment, tag)
.commit();
currentFragment = fragment;
}
}
ADDITIONAL INFO: If you want to be sure that the fragment states don't change and if you also want to be able to swipe the fragments you should consider using a ViewPager with a FragmentStatePagerAdapter and change the current fragment in the adapter with every click event
I wrote a Kotlin Extension function for FragmentManager class
fun FragmentManager.switch(containerId: Int, newFrag: Fragment, tag: String) {
var current = findFragmentByTag(tag)
beginTransaction()
.apply {
//Hide the current fragment
primaryNavigationFragment?.let { hide(it) }
//Check if current fragment exists in fragmentManager
if (current == null) {
current = newFrag
add(containerId, current!!, tag)
} else {
show(current!!)
}
}
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.setPrimaryNavigationFragment(current)
.setReorderingAllowed(true)
.commitNowAllowingStateLoss()
}
This can be called by supportFragmentManager.swtich(R.id.container,newFrag,newFrag.TAG) from onNavigationItemSelected
All the previous answers are using fragmentTransaction.replace(...). This will replace the current fragment by destroying it (which is causing the problem). Therefore all those solutions won't actually work.
This is the closest thing I could get to as a solution for this problem:
private void selectContentFragment(Fragment fragmentToSelect)
{
FragmentTransaction fragmentTransaction = this.getSupportFragmentManager().beginTransaction();
if (this.getSupportFragmentManager().getFragments().contains(fragmentToSelect)) {
// Iterate through all cached fragments.
for (Fragment cachedFragment : this.getSupportFragmentManager().getFragments()) {
if (cachedFragment != fragmentToSelect) {
// Hide the fragments that are not the one being selected.
fragmentTransaction.hide(cachedFragment);
}
}
// Show the fragment that we want to be selected.
fragmentTransaction.show(fragmentToSelect);
} else {
// The fragment to be selected does not (yet) exist in the fragment manager, add it.
fragmentTransaction.add(R.id.fragment_container, fragmentToSelect);
}
fragmentTransaction.commit();
}
To make this work, you should keep track of the fragments in an array (or in separate variables) in your Activity. I for reference pre-instantiated all fragments in a SparseArray.
You can use attach() and detach() methods:
private FirstFragment firstFragment = FirstFragment.newInstance();
private SecondFragment secondFragment= SecondFragment.newInstance();
private ThirdFragment thirdFragment = ThirdFragment.newInstance();
navigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_dialer:
changeFragment(firstFragment, "firstFragment");
return true;
case R.id.menu_email:
changeFragment(secondFragment, "secondFragment");
return true;
case R.id.menu_map:
changeFragment(thirdFragment, "thirdFragment");
return true;
}
return false;
}
});
public void changeFragment(Fragment fragment, String tagFragmentName) {
FragmentManager mFragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
Fragment currentFragment = mFragmentManager.getPrimaryNavigationFragment();
if (currentFragment != null) {
fragmentTransaction.detach(currentFragment);
}
Fragment fragmentTemp = mFragmentManager.findFragmentByTag(tagFragmentName);
if (fragmentTemp == null) {
fragmentTemp = fragment;
fragmentTransaction.add(R.id.content, fragmentTemp, tagFragmentName);
} else {
fragmentTransaction.attach(fragmentTemp);
}
fragmentTransaction.setPrimaryNavigationFragment(fragmentTemp);
fragmentTransaction.setReorderingAllowed(true);
fragmentTransaction.commitNowAllowingStateLoss();
}
So I have been researching for this for a long time and figured out that there is no way to reuse fragment with AUTOMATICALLY saved states you have to save your needed states manually then retrieve them whenever a new fragment is created but what about scroll position it is too hard and even in some cases it is impossible to save scroll view position state(e.g. recycler view). So i used the concept called VISIBILITY when i click on the button that fragment becomes visible others hide automatically.
How about that.
You declare the fragments in the class.
Fragment firstFragment,secondFragment,thirdFragment;
then in the switch-case you can code as that:
switch (item.getItemId()) {
case R.id.menu_dialer:
if(firstFragment != null) {
fragment = firstFragment;
}else{
fragment = FirstFragment.newInstance();
}
break;
case R.id.menu_email:
// the same ...
break;
case R.id.menu_map:
//the same ...
break;
}
Thomas's answer nearly helped me but had an issue that whenever I open new fragments the very first time, they overlapped but doesn't get overlapped once I open them again by pressing the menu buttons.
So I modified his code and obtained the solution using the following code:
private fun selectContentFragment(fragmentToSelect: Fragment) {
val fragmentTransaction = fragmentManager?.beginTransaction()
if (fragmentManager?.fragments?.contains(fragmentToSelect)!!) {
// Show the fragment that we want to be selected.
fragmentTransaction?.show(fragmentToSelect)
} else {
// The fragment to be selected does not (yet) exist in the fragment manager, add it.
fragmentTransaction?.add(R.id.container, fragmentToSelect)
}
// Iterate through all cached fragments.
for (cachedFragment in fragmentManager?.fragments!!) {
if (cachedFragment !== fragmentToSelect) {
// Hide the fragments that are not the one being selected.
// Uncomment following line and change the name of the fragment if your host isn't an activity and a fragment otherwise whole view will get hidden.
// if (!cachedFragment.toString().contains("HomeContainerFragment"))
fragmentTransaction?.hide(cachedFragment)
}
}
fragmentTransaction?.commit()
}
Make sure you're not passing the fragment's new instance every time.
This will work:
selectContentFragment(
when (item.itemId) {
R.id.home -> frag1
R.id.photoGallery -> frag2
else -> Home()
}
)
where frag1 and frag2 are global variables defined as:
val frag1 = Home()
val frag2 = PhotoGallery()
This will not work:
selectContentFragment(
when (item.itemId) {
R.id.home -> Home()
R.id.photoGallery -> PhotoGallery()
else -> Home()
}
)
It wasted my several hours. Hope it helps others!
**This code very helpful for me. Just like youtube. **
private Deque<Integer> fragmentIds = new ArrayDeque<>(3);
int itemId;
private HomeFragment homeFragment = new HomeFragment();
private FavouriteFragment favouriteFragment = new FavouriteFragment();
private NearmeFragment nearmeFragment = new NearmeFragment();
BottomNavigationView bottomNavigationView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragmentIds.push(R.id.action_home);
showTabWithoutAddingToBackStack(homeFragment);
bottomNavigationView = findViewById(R.id.bottom_navigation);
bottomNavigationView.setOnNavigationItemSelectedListener(onNavigationItemClicked);
}
private BottomNavigationView.OnNavigationItemSelectedListener onNavigationItemClicked = new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
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.action_home:
return homeFragment;
case R.id.action_favorites:
return favouriteFragment;
case R.id.action_nearme:
return nearmeFragment;
}
return homeFragment;
}
private void showTabWithoutAddingToBackStack(Fragment fragment) {
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment, fragment.getClass().getSimpleName()).commit();
}
#Override
public void onBackPressed() {
if(fragmentIds.getLast() != R.id.action_home){
fragmentIds.addLast(R.id.action_home);
}
fragmentIds.pop();
bottomNavigationView.getMenu().getItem(fragmentIds.size()-1).setChecked(true);
if (!fragmentIds.isEmpty()) {
showTabWithoutAddingToBackStack(getFragment(fragmentIds.peek()));
} else {
finish();
}
}
For Kotlin user may help this code :
First create a FragmentManager extension class
fun FragmentManager.replace(containerId: Int, fragment: Fragment, tag: String) {
var current = findFragmentByTag(tag)
beginTransaction()
.apply {
//Hide the current fragment
primaryNavigationFragment?.let { hide(it) }
//Check if current fragment exists in fragmentManager
if (current == null) {
current = fragment
add(containerId, current!!, tag)
} else {
show(current!!)
}
}
.setTransition(FragmentTransaction.TRANSIT_ENTER_MASK)
.setPrimaryNavigationFragment(current)
.setReorderingAllowed(true)
.commitNowAllowingStateLoss()
}
Now simply call it on your
onNavigationItemSelected
supportFragmentManager.replace(R.id.fragment_id,YourFragmentClass,yourFragment.TAG)
I had the same issue but I resolved it by creating multiple fragment hosts then I added fragments in fragmentHost. And when bottom nav itemSelected I just make the required host fragment visible and other host fragmentsHost to visibility gone.
This method may be wrong but it works perfectly for me.
And we don't have to manually handle the states of fragment only we need to handle backpress of activity.
But this method does not pause the fragments.
And I don't know how to handle the pauses and resumes of fragments so please reply to me.
Maybe This will help you.
This is my fragmentHosts Home Activity:
<androidx.fragment.app.FragmentContainerView
android:id="#+id/homeFragmentHost"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/bottom_NavBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:visibility="gone"/>
<androidx.fragment.app.FragmentContainerView
android:id="#+id/libraryFragmentHost"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/bottom_NavBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:visibility="gone"/>
<androidx.fragment.app.FragmentContainerView
android:id="#+id/myStuffFragmentHost"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/bottom_NavBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:visibility="gone"/>
<androidx.fragment.app.FragmentContainerView
android:id="#+id/moreFragmentHost"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/bottom_NavBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:visibility="gone"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_NavBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="#menu/app_bottom_nav_menu"
tools:ignore="BottomAppBar" />
Home Activity onCreate:
supportFragmentManager.beginTransaction().replace(R.id.homeFragmentHost,HomeFragment()).commitNow()
supportFragmentManager.beginTransaction().replace(R.id.libraryFragmentHost,LibraryFragment()).commitNow()
supportFragmentManager.beginTransaction().replace(R.id.myStuffFragmentHost,MyStuffFragment()).commitNow()
supportFragmentManager.beginTransaction().replace(R.id.moreFragmentHost,MoreFragment()).commitNow()
homeFragmentHost.visibility = View.VISIBLE
Bottom nav Item Selected listener
override fun onNavigationItemSelected(item: MenuItem): Boolean {
return when(item.itemId){
R.id.homeFragment -> loadFragmentHost(homeFragmentHost)
R.id.libraryFragment -> loadFragmentHost(libraryFragmentHost)
R.id.myStuffFragment -> loadFragmentHost(myStuffFragmentHost)
R.id.moreFragment -> loadFragmentHost(moreFragmentHost)
else -> false
}
loadFragmentHost function
private fun loadFragmentHost(view:FragmentContainerView): Boolean {
val list = arrayListOf(homeFragmentHost,libraryFragmentHost,myStuffFragmentHost,moreFragmentHost)
list.remove(view)
view.visibility = View.VISIBLE
list.forEach {
it.visibility = View.GONE
}
return true
}
public class MainActivity extends AppCompatActivity {
boolean doubleBackToExitPressedOnce = false;
final Fragment fragment1 = new HomeFragment();
final Fragment fragment2 = new DashboardFragment();
final Fragment fragment3 = new NotificationsFragment();
final FragmentManager fm = getSupportFragmentManager();
Fragment active = fragment1;
BottomNavigationView navigation;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
navigation = (BottomNavigationView) findViewById(R.id.navigation);
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
setFragment(fragment1, "1", 0);
}
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.navigation_home:
setFragment(fragment1, "1", 0);
return true;
case R.id.navigation_dashboard:
setFragment(fragment2, "2", 1);
return true;
case R.id.navigation_notifications:
setFragment(fragment3, "3", 2);
return true;
}
return false;
}
};
public void setFragment(Fragment fragment, String tag, int position) {
if (fragment.isAdded()) {
fm.beginTransaction().hide(active).show(fragment).commit();
} else {
fm.beginTransaction().add(R.id.main_container, fragment, tag).commit();
}
navigation.getMenu().getItem(position).setChecked(true);
active = fragment;
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_menu, menu);
return super.onCreateOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
startActivity(new Intent(MainActivity.this, SettingsActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public void onBackPressed() {
if (active == fragment1) {
if (doubleBackToExitPressedOnce) {
super.onBackPressed();
return;
}
this.doubleBackToExitPressedOnce = true;
Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT).show();
} else {
setFragment(fragment1, "1", 0);
}
}
}
Maintain Bottom sheet fragment Reusable
BackPress Maintain
double back press to exit
public class MainActivity extends AppCompatActivity {
BottomNavigationView bottomNavigationView;
Toaster toaster;
private final Fragment androidFragment = new AndroidFragment();
private final Fragment settingsFragment = new SettingsFragment();
Fragment active;
String TAG = MainActivity.class.getSimpleName();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toaster = new Toaster(this);
bottomNavigationView = findViewById(R.id.bottom_navigation);
renderFragment(androidFragment, "android");
active = androidFragment;
bottomNavigationView.setOnItemSelectedListener(
item -> {
Log.e(TAG, "onCreate: " + active );
if(item.getItemId() == R.id.action_android){
renderFragment(androidFragment, "android");
return true;
}
else if(item.getItemId() == R.id.action_settings){
renderFragment(settingsFragment, "settings");
return true;
}
return false;
}
);
}
private void renderFragment(Fragment fragment, String tag){
FragmentManager fragmentManager = getSupportFragmentManager();
if(fragment.isAdded()){
fragmentManager.beginTransaction().hide(active).show(fragment)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN).commit();
}
else {
if(active != null){
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container_view, fragment, tag)
.hide(active)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN).commit();
}
else {
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container_view, fragment, tag)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN).commit();
}
}
active = fragment;
}
}
This worked for me Guys.
Create the three fragments as members of the class and reuse them.
public class MainActivity extends AppCompatActivity {
private final Fragment mFirstFragment = new FirstFragment();
private final Fragment mSecondFragment = new SecondFragment();
private final Fragment mThirdFragment = new ThirdFragment();
#Override
protected void onCreate(Bundle savedInstanceState) {
......
......
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.frameLayout, mFirstFragment);
fragmentTransaction.commit();
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
if (bottomNavigationView.getSelectedItemId() != item.getItemId()) {
switch (item.getItemId()) {
R.id.menu_dialer:
replaceFragment(mFirstFragment);
break;
case R.id.menu_email:
replaceFragment(mSecondFragment);
break;
case R.id.menu_map:
replaceFragment(mThirdFragment);
break;
}
}
return true;
}
});
}
private void replaceFragment(Fragment fragment) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.frameLayout, fragment);
fragmentTransaction.commit();
}
}

Categories

Resources