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()
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 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()
Imagine one activity with 3 fragments: starts showing the first one, select a menu option and go to the second one, select another option and go to the 3rd fragment and select again the first option an return to the second one.
f1 -> f2 -> f3 -> f2
When I press back I want the app returns to fragment 3 and when I press back again it should return to fragment 1 and if press back again, close the app.
Something like if the fragment exists, move it to top of the stack and if not, create it.
Thank you!
Here is solution I came up over time.
The idea is following, you need to keep a stack data structure and whenever you add a fragment add it to stack as well, then override onBackPress method and check if stack is not empty then replace your fragment container with new fragment from top of the stack when it is empty do super.onbackpress
So here is a parent class for all kind of fragment based navigation.
public abstract class FragmentsStackActivity extends BaseActivity {
public static final String TAG_BUNDLE = "bundle_tag";
protected final Bundle fragmentArgs = new Bundle();
protected Stack<Fragment> fragments = new Stack<>();
abstract protected void setupFragments();
public void setFragmentArguments(Fragment fragment, Bundle arguments){
if(!fragments.isEmpty() && fragments.peek()!=fragment){
fragment.setArguments(arguments);
}
}
public void setFragmentFromStack() {
if(!fragments.isEmpty()) {
Fragment fragment = fragments.peek();
final Fragment oldFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (oldFragment == null || oldFragment != fragment) {
getFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
final FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
//transaction.setCustomAnimations(R.anim.animator_left_right_in, R.anim.animator_left_right_in);
transaction.replace(R.id.fragment_container, fragment).commit();
}
}else {
finish();
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//TODO need to save fragment stack
}
}
example of an activity that extends this class
public class LoginActivity extends FragmentsStackActivity{
private final MyFragment1 fragment1 = new MyFragment1();
private final MyFragment2 fragment2 = new MyFragment2();
private final User mUser = new User();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
setupFragments();
setFragmentFromStack();
}
#Override
protected void setupFragments() {
fragments.add(fragment2);
//fragment2.setNotifier(this); // I use notifiers listener but you can choose whatever convenient for you
Bundle fragmentArgs = new Bundle();
fragmentArgs.putBoolean(Constants.TAG_LOGIN, true);
fragmentArgs.putParcelable(User.TAG, mUser);
fragmentArgs.putInt(Constants.TYPE, getIntent().getIntExtra(Constants.TYPE, 0));
fragment2.setArguments(fragmentArgs);
//fragment1.setNotifier(this); // I use notifiers listener but you can choose whatever convenient for you
}
// this method teals with handling messages from fragments in order to provide navigation
// when some actions taken inside the fragment, you can implement your own version
public void onReceiveMessage(String tag, Bundle bundle) {
switch (tag) {
case MyFragment2.TAG_BACK:
case MyFragment1.TAG_BACK:
fragments.pop();
setFragmentFromStack();
break;
case MyFragment2.TAG_NEXT:
fragment1.setArguments(bundle);
fragments.add(fragment1);
setFragmentFromStack();
break;
case MyFragment1.TAG_NEXT:
goToWelcomeScreen(bundle);
finish();
break;
}
}
private void goToWelcomeScreen(Bundle bundle){
}
}
You can implement this with the help of the following code:
// transaction.replace(R.id.detailFragment, frag1);
Transaction.remove(null).add(frag1) // frag1 on view
// transaction.replace(R.id.detailFragment, frag2).addToBackStack(null);
Transaction.remove(frag1).add(frag2).addToBackStack(null) // frag2 on view
// transaction.replace(R.id.detailFragment, frag3);
Transaction.remove(frag2).add(frag3) // frag3 on view
And for better understanding, have a ook at the following snippet:
// Works with either the framework FragmentManager or the
// support package FragmentManager (getSupportFragmentManager).
getSupportFragmentManager().beginTransaction()
.add(detailFragment, "detail")
// Add this transaction to the back stack
.addToBackStack()
.commit();
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
// Update your UI here.
}
});
have a look here http://developer.android.com/training/implementing-navigation/temporal.html
I have a few fragments in my app, but my code opens a new fragment every time I click the button.
I want to know how can I change this, and make the fragment return to the exact same state I left it in.
The code im using right now:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragments);
MainActivity fragment = new MainActivity();
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
transaction.add(R.id.fragment_place, fragment);
transaction.commit();
turnGPSOn();
}
public void onSelectFragment(View view) {
if (view == findViewById(R.id.add))
{
newFragment = new Add();
}
else if (view == findViewById(R.id.map))
{
newFragment = new MainActivity();
}
else
{
newFragment = new Add();
}
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
transaction.replace(R.id.fragment_place, newFragment);
transaction.addToBackStack(null);
transaction.commit();
}
Thanks!
You are getting a new fragment each time because you are calling to new XXX() each time.
I think you could use findFragmentByTag in order to solve this problem. As you can see here the replace function can accept a third parameter that is a String, this String can be used as an id to identify different fragments you have used previously.
So to sum up you can:
Call Fragment f = getSupportFragmentManager().findFragmentByTag("FragAdd"); for example in order to retrieve the first fragment.
If f is null, that means that you haven't used that fragment yet, so you have to call to new Add() if not, use that fragment to replace the old one. For example like this:
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
transaction.replace(R.id.fragment_place, newFragment, "FragAdd"); //or whatever other string you want to use
transaction.addToBackStack(null);
transaction.commit();
Hope it helps :)
I faced this issue a time ago, and managed to solve it for applications with one visible fragment at a time; for activities with several visible fragments, you'll need to make some adjustments. This is what I did.-
Create a custom ParentActivity, so that all my activities extend it. ParentActivity knows about which is the current Fragment that is showed, and how to show a new one.
public String currentFragmentTag;
public ParentFragment getCurrentFragment(int fragmentWrapperResId) {
ParentFragment res = null;
FragmentManager fragmentManager = getSupportFragmentManager();
res = (ParentFragment) fragmentManager.findFragmentById(fragmentWrapperResId);
if (res != null && res.isHidden()) {
if (currentFragmentTag != null) {
res = (ParentFragment) fragmentManager.findFragmentByTag(currentFragmentTag);
}
}
return res;
}
public void openFragment(ParentFragment fragment, int fragmentWrapperResId, int enterAnim, int exitAnim, int popEnterAnim, int popExitAnim, boolean addToBackStack) {
FragmentManager fragmentManager = getSupportFragmentManager();
ParentFragment currentFragment = getCurrentFragment(fragmentWrapperResId);
if (currentFragment != null && currentFragment.getTagName().equals(fragment.getTagName())) {
return;
}
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
if (currentFragment != null) {
transaction.hide(currentFragment);
}
if (fragment.isAdded()) {
transaction.show(fragment);
} else {
transaction.add(fragmentWrapperResId, fragment, fragment.getTagName()).setBreadCrumbShortTitle(fragment.getTagName());
}
if (addToBackStack) {
transaction.addToBackStack(fragment.getTagName());
} else {
currentFragmentTag = fragment.getTagName();
}
transaction.commit();
}
Create a ParentFragment, to be extended by the rest of Fragments, with a tag getter
public String getTagName() {
return getClass().getSimpleName() + System.identityHashCode(this);
}
As you can see, the main idea is not replacing visible fragments, but just adding them and show/hide whenever it's needed. This way, the fragments will keep their states, as they're not destroyed until you remove them from the bakstack.