I have an activity with bottom navigation tabs that are changing the fragments in it. When I click back and forth on those tabs, at some point it stops working. Code executes just fine as I put some logs in it. But the fragments aren't being switched.
Code is in kotlin but it's rather straight forward
fun showTabFragment(tag: String) {
val currentFragment: Fragment? = supportFragmentManager.fragments?.lastOrNull()
var fragment = supportFragmentManager.findFragmentByTag(tag)
val fragmentExists = fragment != null
if (fragment == null) {
when (tag) {
TAG_LOGBOOK -> fragment = LogbookFragment()
TAG_RECIPES -> fragment = RecipesFragment()
TAG_PROFILE -> fragment = ProfileFragment()
else -> fragment = MeetingPlacesFragment()
}
}
val transaction = supportFragmentManager.beginTransaction()
if (currentFragment != null) {
Log.i("jacek", "hiding " + currentFragment.javaClass.simpleName)
transaction.hide(currentFragment)
}
if (fragmentExists) {
Log.i("jacek", "showing " + fragment.javaClass.simpleName)
transaction.show(fragment)
} else {
Log.i("jacek", "adding " + fragment.javaClass.simpleName)
transaction.add(R.id.container, fragment, tag)
}
transaction.commit()
}
The fragments are quite heavy. I will try with some lightweight ones, but still that shouldn't be a problem in my opinion. Is there anything else I could try?
I'm using the latest support library - 25.2.0
Also I'm not interested in replacing the fragments as the point is to add crossfade animation without recreating them
You need to reuse the same instance of a fragment that you wanted to hide or show.
private fun replaceFragment(fragment: Fragment) {
supportFragmentManager.beginTransaction().apply {
if (fragment.isAdded) {
show(fragment)
} else {
add(R.id.fmFragmentContainer, fragment)
}
supportFragmentManager.fragments.forEach {
if (it != fragment && it.isAdded) {
hide(it)
}
}
}.commit()
}
#Ali's answer is good, yet imagine if you have 5 fragments. This is another way to show/hide your fragments:
// in BaseFragment
public abstract String getTAG();
//in FragmentA, FragmentB and FragmentC
public String getTAG(){
return TAG;
}
//Activity containing the fragments
//android.support.v4.app.Fragment;
private FragmentA fragmentA; //inherited BaseFragment
private FragmentB fragmentB; //inherited BaseFragment
private FragmentC fragmentC; //inherited BaseFragment
private ConcurrentHashMap<String,BaseFragment> mapOfAddedFragments = new ConcurrentHashMap<>();
/**
* Displays fragment A
*/
private void displayFragmentA() {
displayFragment(fragmentA)
}
/**
* Displays fragment B
*/
private void displayFragmentB() {
displayFragment(fragmentB)
}
/**
* Displays fragment C
*/
private void displayFragmentC() {
displayFragment(fragmentC)
}
/**
* Loads a fragment using show a fragment
* #param fragment
*/
private void displayFragment(BaseFragment fragment){
if(!mapOfAddedFragments.containsKey(fragment.getTAG()))
mapOfAddedFragments.put(fragment.getTAG(), fragment);
showFragment(fragment.getTAG(), R.id.containerBody);
}
/**
* Displays a fragment and hides all the other ones
* #param fragmentTag is the tag of the fragment we want to display
*/
private void showFragment(String fragmentTag, #IdRes int containerViewId){
FragmentTransaction ft = this.getSupportFragmentManager().beginTransaction();
BaseFragment fragment = null;
fragment = mapOfAddedFragments.get(fragmentTag);
if(fragment != null) {
if (fragment.isAdded())
ft.show(fragment);
else { //fragment needs to be added to the frame container
ft.add(containerViewId, fragment, fragment.getTAG());
}
}
else //the chosen fragment doesn't exist
return;
//we hide the other fragments
for (ConcurrentHashMap.Entry<String, BaseFragment> entry : mapOfAddedFragments.entrySet()){
if(!entry.getKey().equals(fragmentTag)){
BaseFragment fragmentTemp = entry.getValue();
// Hide the other fragments
if(fragmentTemp != null)
if(fragmentTemp.isAdded())
ft.hide(fragmentTemp);
}
}
//commit changes
ft.commit();
}
And to instantiate them you can do this in the onCreate() method of your activity:
//don't forget to get the .TAG elsewhere before using them here
//never call them directly
private void instantiateFragments(Bundle inState) {
if (inState != null) {
fragmentA = inState.containsKey(FragmentA.TAG) ?
(FragmentA) getSupportFragmentManager().getFragment(inState, FragmentA.TAG):
FragmentA.newInstance(FragmentA.TAG,"0");
fragmentB = inState.containsKey(FragmentB.TAG) ?
(FragmentB) getSupportFragmentManager().getFragment(inState, FragmentB.TAG):
FragmentB.newInstance(FragmentB.TAG,"1");
fragmentc = inState.containsKey(FragmentC.TAG) ?
(FragmentC) getSupportFragmentManager().getFragment(inState, FragmentC.TAG):
FragmentC.newInstance(FragmentC.TAG,"2");
}
else{
fragmentA = FragmentA.newInstance(FragmentA.TAG,"0");
fragmentB = FragmentB.newInstance(FragmentB.TAG,"1");
fragmentc = FragmentC.newInstance(FragmentC.TAG,"2");
}
}
Edit according to Shujaat Ali Khan's question:
The BaseFragment extends support4 fragment:
public abstract class BaseFragment extends Fragment {
public abstract String getTAG();
//whatever we can add to be inherited
}
FragmentA for example:
public class FragmentA extends BaseFragment {
// Store instance variables
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
private String mParam1;
private String mParam2;
public static final String TAG = "FragmentA";
// newInstance constructor for creating fragment with arguments
public static FragmentA newInstance(String param1, String param2) {
FragmentA fragment = new FragmentA();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
// Store instance variables based on arguments passed
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
// Inflate the view for the fragment based on layout XML
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragmentA, container, false);
return view;
}
//other lifecycle methods
#Override
public String getTAG() {
return TAG;
}
}
Finally the R.id.containerBody is the id of a FrameLayout containing the fragments in the activity containing these fragments.
The problem here is even though you're hiding "current" fragment, there are other fragments loaded in the memory and that gives inconsistent behaviour.
You should be able to fix this by hiding all the fragment except the fragment you want to show.
Thanks to this answer. Show hide fragment in android
eg:
private FragmentA fragmentA;
private FragmentB fragmentB;
private FragmentC fragmentC;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragmentA = FragmentA.newInstance();
fragmentB = FragmentB.newInstance();
fragmentC = FragmentC.newInstance();
}
protected void displayFragmentA() {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (fragmentA.isAdded()) {
ft.show(fragmentA);
} else {
ft.add(R.id.fragement_container, fragmentA);
}
if (fragmentB.isAdded()) { ft.hide(fragmentB); }
if (fragmentC.isAdded()) { ft.hide(fragmentC); }
ft.commit();
}
Similarly you will have to write functions for displayFragmentB() and displayFragmentC()
Related
I am invoking a fragment(GetStartFragment) from an activity(DiscoverActivity) and from the same fragment (GetStartFragment), I am replacing it with another fragment (SelectFragment). Now I need to remove the old fragment (GetStartFragment) when I perform onClick() method as below. I was trying to remove, but the fragment ID returns null. How to remove the old fragment from the fragment itself.
DiscoverActivity.java :
public class DiscoverActivity implements AppCompatActivity {
.....
case R.id.start:
showGetStartFragment();
return true;
.....
private void showGetStartFragment() {
lGetStartFragment = GetStartFragment.newInstance();
lGetStartFragment.show(getSupportFragmentManager(), lGetStartFragment.getTag());
}
}
**FragmentDiscover.java :**
public class GetStartFragment extends BaseBottomSheetDialogFragment {
public static GetStartFragment newInstance() {
return new GetStartFragment();
}
.....
#OnClick(R2.id.getstart_button)
void onGetStartButtonClick() {
boolean isStart = true;
/* Show Fragment 2 */
SelectFragment lSelectFragment = SelectFragment.newInstance();
SelectFragment.show(requireActivity().getSupportFragmentManager(), SelectFragment.getTag());
/*Remove previous fragment - Fragment 1*/
FragmentManager fm = requireActivity().getSupportFragmentManager();
Fragment fragment_ID = fm.findFragmentById(com.misc.exam.R.id.design_getstart);
FragmentTransaction fmt = fm.beginTransaction();
if (fragment_ID != null) {
fmt.remove(fragment_ID).commitAllowingStateLoss();
}
}
#Override
public void onDestroy() {
super.onDestroy();
FragmentManager fm = requireActivity().getSupportFragmentManager();
Fragment fragID = fm.findFragmentById(com.misc.exam.R.id.design_getting_started);
FragmentTransaction fmt = fm.beginTransaction();
if (fragID != null) {
fmt.remove(fragID).commitAllowingStateLoss();
}
}
...
}
Have you tried saving the current fragment in a separate field and replacing it?
Fragment currentFragment;
currentFragment = new NewFragment()
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container,currentFragment)
This question already has answers here:
How to save and reuse same instance of fragments?
(2 answers)
Closed 5 years ago.
Lets look on this picture:
In Android tutorial every time when FragmentA is replaced with FragmentB new instance of FragmentB is created.
Lets look next case FragmentA to FragmentB then back (press) to FragmentA and again go to FragmentB. Maybe if old instance of FragmentB still alive would be better use it (only updating data) instead of create new instance? Is there any simple way to do that?
Try using fragmentTransaction.show method to re-use existing fragment.
Use following method to switch fragment, it will show fragment if it is already added.
public void switchFragment (Fragment oldFragment, Fragment newFragment, int frameId) {
boolean addFragment = true;
FragmentManager fragmentManager = getFragmentManager ();
String tag = newFragment.getArguments ().getString (BaseFragment.TAG);
Fragment fragment = fragmentManager.findFragmentByTag (tag);
// Check if fragment is already added
if (fragment != null && fragment.isAdded ()) {
addFragment = false;
}
// Hide previous fragment
String oldFragmentTag = oldFragment.getArguments ().getString (BaseFragment.TAG);
if (!tag.equals (oldFragmentTag)) {
FragmentTransaction hideTransaction = fragmentManager.beginTransaction ();
Fragment fragment1 = fragmentManager.findFragmentByTag (oldFragmentTag);
hideTransaction.hide (fragment1);
hideTransaction.commit ();
}
// Add new fragment and show it
FragmentTransaction addTransaction = fragmentManager.beginTransaction ();
if (addFragment) {
addTransaction.add (frameId, newFragment, tag);
addTransaction.addToBackStack (tag);
}
else {
newFragment = fragmentManager.findFragmentByTag (tag);
}
addTransaction.show (newFragment);
addTransaction.commit ();
}
You can use simple Singleton for your fragment,
public class FragmentGoogleMap extends Fragment implements OnMapReadyCallback {
MapView mapView;
GoogleMap googleMap;
private static FragmentGoogleMap instance = null;
private FragmentGoogleMap() {
}
public static FragmentGoogleMap getInstance(){
if(instance == null){
instance = new FragmentGoogleMap();
}
return instance;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
frome activity you can get instance
private FragmentGoogleMap fragmentGoogleMap;
private FragmentManager fragmentManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragmentManager = getSupportFragmentManager();
fragmentGoogleMap = FragmentGoogleMap.getInstance();
fragmentManager.beginTransaction().replace(R.id.fragment_map_container,fragmentGoogleMap).commit();
}
I have two fragments that are in the main activity and i want to refresh them when something occurs.
Now the code works for second fragment, but won't work for the first, and i am not sure why.
I have been looking at the code for about an hour, and i can't seem to find a reason.
Here is the code
public class MainActivity extends FragmentActivity {
Fragment frag,frag2;
FragmentManager fm;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String screen = getString(R.string.screen_type);
/*
* Get a reference to fragment manager
* Wire the container to represent fragment
*/
fm = getSupportFragmentManager();
frag = fm.findFragmentById(R.id.container);
if(screen.equals("large")){
frag2 = fm.findFragmentById(R.id.containerDetails);
loadFragments(frag,frag2,fm);
}
/*Loads the fragment into the activity*/
else
loadFragment(frag,fm);
}
private void loadFragments(Fragment frag, Fragment frag2, FragmentManager fm) {
if(frag == null && frag2 == null){
frag = new DisplayFragment();
frag2 = new DetailsFragment();
fm.beginTransaction().add(R.id.container,frag).add(R.id.containerDetails, frag2).commit();
}
}
private void loadFragment(Fragment frag, FragmentManager fm) {
if(frag == null){
frag = new DisplayFragment();
fm.beginTransaction().add(R.id.container,frag).commit();
}
}
public void updateDetails(int position) {
// Reload current fragment
if(frag2!=null)fm.beginTransaction().remove(frag2).commit();
frag2 = new DetailsFragment();
Bundle b = new Bundle();
b.putInt("Id",position);
frag2.setArguments(b);
fm.beginTransaction().add(R.id.containerDetails, frag2).commit();
}
public void updateDisplay() {
// Reload current fragment
if(frag!=null)fm.beginTransaction().remove(frag).commit(); //THIS IS ALWAYS NULL FOR SOME REASON
frag = new DisplayFragment();
fm.beginTransaction().add(R.id.container, frag).commit();
}
public void refreshDetails() {
// Reload current fragment
if(frag2!=null)fm.beginTransaction().remove(frag2).commit();
frag2 = new DetailsFragment();
fm.beginTransaction().add(R.id.containerDetails, frag2).commit();
}
}
The first fragment is always null, and it doesn't get removed, instead another fragment is pasted over that, and creates a mess.
Try using replace() method rather than add()
I have an Android Tab Layout with swipeable views.
The file structure is as follows:
There is an activity class: TabMainActivity.java
Under this activity, there is a fragment class: bookLockerFragment.java
This fragment class is linked to an XML file which contains various buttons.
public class bookLockerFragment extends Fragment {
Button btnSis;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.fragment_booklocker, container, false);
btnSis = (Button) rootView.findViewById(R.id.btnSis);
}
.......
}
I have read up on vogella activity testing tutorials, but there is minimal information on unit testing for fragment.
I attempted to write some code but got stuck at this line:
transaction.add(R.layout.fragment_booklocker, fragment, "tag");
I get the following error:
The method add(int, Fragment, String) in the type FragmentTransaction is not applicable for the arguments (int, booklockertest, String)
Code:
public class booklockertest extends
ActivityInstrumentationTestCase2 < TabMainActivity > {
private TabMainActivity mActivity;
Fragment fragment;
FragmentManager mFragmentManager;
public booklockertest() {
super(TabMainActivity.class);
// TODO Auto-generated constructor stub
}
protected void setUp() throws Exception {
super.setUp();
mActivity = getActivity();
}
private Fragment startFragment(booklockertest fragment) {
FragmentTransaction transaction =
mActivity.getSupportFragmentManager().beginTransaction();
transaction.add(R.layout.fragment_booklocker, fragment, "tag");
transaction.commit();
getInstrumentation().waitForIdleSync();
Fragment frag =
mActivity.getSupportFragmentManager().findFragmentByTag("tag");
return frag;
}
public void testFragment() {
booklockertest fragment = new booklockertest() {
//Override methods and add assertations here.
};
Fragment frag = startFragment(fragment);
}
}
stumble upon this, and you probably have figured it out already, but
private Fragment startFragment(booklockertest fragment) { ... }
...
public void testFragment() {
booklockertest fragment = new booklockertest() {
//Override methods and add assertations here.
};
Fragment frag = startFragment(fragment);
}
should be
private Fragment startFragment(bookLockerFragment fragment) { ... }
...
public void testFragment() {
bookLockerFragment fragment = new bookLockerFragment() {
//Override methods and add assertations here.
};
Fragment frag = startFragment(fragment);
}
An SSCCE for this issue is available on GitHub.
For future readers, the original example is on a branch of the same project, and the fix is available in this diff.
This SSCCE has a ListView and a row of buttons. The buttons are supposed to change the data in the ListView, and the listView rows (when clicked) are supposed to open a new fragment and advance the backstack while staying in the same activity.
If do the following things, it produces the following result:
Open the app.
Tap the ListView. - FragmentTransaction.replace(...) with addToBackStack(true)
Tap any of the buttons. - FragmentTransaction.replace(...) with addToBackStack(false)
Tap the back button.
Result:
Both fragments become visible, but I only want the first loaded fragment (ListTwoFragment in code) to display. Is this how fragments are supposed to work? If so, how can I get the desired effect?
MainActivity.java:
public class MainActivity extends FragmentActivity implements ListTwoFragment.Callbacks,
ListThreeFragment.Callbacks {
public static final String KEY_ARGS = "args";
private String curUri = "";
private String curArgs = "";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
selectContent(false);
}
private void selectContent(boolean addToBackStack) {
Fragment fragment;
if (curUri.isEmpty()) {
// Use default fragment
fragment = new ListTwoFragment();
curUri = ListTwoFragment.class.getName();
}
else {
try {
Class<Fragment> fragmentClass = (Class<Fragment>) Class.forName(curUri);
fragment = fragmentClass.newInstance();
}
catch (Exception e) { // ClassNotFound, IllegalAccess, etc.
return;
}
}
// Configure fragment
Bundle args = new Bundle();
args.putString(KEY_ARGS, curArgs);
fragment.setArguments(args);
attachFragment(fragment, addToBackStack, curUri + ";" + curArgs, R.id.fragment_container);
}
protected void attachFragment(Fragment fragment, boolean addToBackStack, String tag, int replaceId) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(replaceId, fragment, tag);
if (addToBackStack) transaction.addToBackStack(tag);
transaction.commit();
}
#Override
public void onTwoButtonClick(String title) {
curUri = ListTwoFragment.class.getName();
curArgs = title;
selectContent(false);
}
#Override
public void onTwoListClick() {
curUri = ListThreeFragment.class.getName();
curArgs = "";
selectContent(true);
}
#Override
public void onThreeButtonClick(String title) {
curUri = ListThreeFragment.class.getName();
curArgs = title;
selectContent(false);
}
}
I'm working with Fragments to, and the way I'm doing it:
to go forward (add to stack), and backwords (remove from stack) are two different functions
to Add to Stack and change Fragment:
public void changeFragmentAddToStack(Fragment myNewFragment) {
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.add(R.id.main_fragment, myNewFragment);
t.addToBackStack(null);
t.commit();
}
To go back Stack:
public void goBackStackMain() {
FragmentManager man = getSupportFragmentManager();
if(man.getBackStackEntryCount()>0){
man.popBackStack(man.getBackStackEntryAt(0).getName(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}
And if you want to do both: To go back stack and change fragment:
public void goBackStackAndReplaceFragment(Fragment myNewFragment) {
FragmentManager man = getSupportFragmentManager();
if(man.getBackStackEntryCount()>0){
int n = man.getBackStackEntryCount();
man.popBackStack(man.getBackStackEntryAt(n-1).getName(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.replace(R.id.main_fragment, myNewFragment);
t.commit();
}
I hope to help you !