I am having trouble figuring out the proper way to navigate through fragments without a pager and i am having problems during Configuration changes for screen orientation. I am using Show/Hide on the fragments to make them visible and functional but i am wondering if i should instead be using Detach/Attach. I am also having problems adding things to the back stack and i think it is also due to the use of show/hide. Is it better to use Attach/detatch or is there a way to override what the back button does to make it show/hide the last/current fragment.
The Behavior:
I have a map fragment and a List fragment along with a few others. everything starts up correctly and works initially with orientation changes. When i navigate to the list view it populates correctly but upon orientation change the list gets redrawn without the Data in it. The map view also gets redrawn and is visible behind my pager title indicator.
If anyone could please point me in right direction for solving this that would be awesome. I am suspecting that is is caused by the way that i am showing and hiding the fragments.
Here is where i create the Fragments and add them to the fragment manager. I have also shown where i show/hide fragments.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_map_frags);
mapViewContainer = LayoutInflater.from(this)
.inflate(R.layout.map, null);
setupFragments();
showFragment(0);
}
public void setListData(String name) {
bName = name;
showFragment(1);
}
private void setupFragments() {
final FragmentManager fm = getSupportFragmentManager();
final FragmentTransaction ft = fm.beginTransaction();
mFragment1 = fm.findFragmentByTag("f1");
if (mFragment1 == null) {
mFragment1 = new MenuFragment();
ft.add(mFragment1, "f1");
ft.hide(mFragment1);
}
mMapFragment = (MapFragment) getSupportFragmentManager()
.findFragmentByTag(MapFragment.TAG);
if (mMapFragment == null) {
mMapFragment = MapFragment.newInstance(0);
ft.add(R.id.fragment_container, mMapFragment, MapFragment.TAG);
}
ft.hide(mMapFragment);
myListFragment = (ListFrag) getSupportFragmentManager()
.findFragmentByTag(ListFrag.TAG);
if (myListFragment == null) {
myListFragment = new ListFrag();
ft.add(R.id.fragment_container, myListFragment, ListFrag.TAG);
}
ft.hide(myListFragment);
frag = (frag) getSupportFragmentManager().findFragmentByTag(
frag.TAG);
if (frag == null) {
bacFrag = new frag();
ft.add(R.id.fragment_container, frag, frag.TAG);
}
ft.hide(bacFrag);
ft.commit();
}
public void showFragment(int fragIn) {
final FragmentTransaction ft = getSupportFragmentManager()
.beginTransaction();
ft.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);
if (mVisible != null) {
if (mVisible == mListFragment) {
ft.remove(mListFragment);
} else {
ft.hide(mVisible);
}
}
switch (fragIn) {
case 0:
ft.show(mMapFragment);
ft.commit();
mVisible = mMapFragment;
break;
case 1:
mListFragment = (ListFragmentDisplay) getSupportFragmentManager()
.findFragmentByTag(ListFragmentDisplay.TAG);
Toast.makeText(this, "startListFrag", Toast.LENGTH_LONG).show();
if (mListFragment == null) {
mListFragment = new ListFragmentDisplay();
ft.add(R.id.fragment_container, mListFragment,
ListFragmentDisplay.TAG);
}
ft.show(mListFragment).commit();
mVisible = mListFragment;
break;
case 2:
ft.show(myfragment).commit();
mVisible = myfragment;
break;
case 3:
ft.show(frag).commit();
mVisible = frag;
break;
}
}
It's not your fault. The problem is that when the orientation changes all the Activity is Destroyed, even all the fragments added. So none of the data within it is retained.
It's not advised to use android:configChanges="orientation|keyboardHidden".
Rather, set for every fragment setRetainInstance(true) and it will work well with your current code.
If you want to have a better persistence (for example when the activity is temporarily destroyed for space issues) also remember to save the state of your fragments with onSaveInstanceState. setRetainInstance will work only when a configuration change is about to come.
Related
I'm working on an android application, that uses a navigation drawer to switch between two fragments. However, each time I switch, the fragment is completely recreated.
Here is the code from my main activity.
/* The click listener for ListView in the navigation drawer */
private class DrawerItemClickListener implements ListView.OnItemClickListener {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
selectItem(position);
}
}
private void selectItem(int position) {
android.support.v4.app.Fragment fragment;
String tag;
android.support.v4.app.FragmentManager; fragmentManager = getSupportFragmentManager();
switch(position) {
case 0:
if(fragmentManager.findFragmentByTag("one") != null) {
fragment = fragmentManager.findFragmentByTag("one");
} else {
fragment = new OneFragment();
}
tag = "one";
break;
case 1:
if(fragmentManager.findFragmentByTag("two") != null) {
fragment = fragmentManager.findFragmentByTag("two");
} else {
fragment = new TwoFragment();
}
tag = "two";
break;
}
fragment.setRetainInstance(true);
fragmentManager.beginTransaction().replace(R.id.container, fragment, tag).commit();
// update selected item and title, then close the drawer
mDrawerList.setItemChecked(position, true);
setTitle(mNavTitles[position]);
mDrawerLayout.closeDrawer(mDrawerList);
}
I've set up some debug logging, and every time selectItem is called, one fragment is destroyed, while the other is created.
Is there any way to prevent the fragments from being recreated, and just reuse them instead?
After #meredrica pointed out that replace() destroys the fragments, I went back through the FragmentManager documentation. This is the solution I've come up with, that seems to be working.
/* The click listener for ListView in the navigation drawer */
private class DrawerItemClickListener implements ListView.OnItemClickListener {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
selectItem(position);
}
}
private void selectItem(int position) {
android.support.v4.app.FragmentManager; fragmentManager = getSupportFragmentManager();
switch(position) {
case 0:
if(fragmentManager.findFragmentByTag("one") != null) {
//if the fragment exists, show it.
fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag("one")).commit();
} else {
//if the fragment does not exist, add it to fragment manager.
fragmentManager.beginTransaction().add(R.id.container, new OneFragment(), "one").commit();
}
if(fragmentManager.findFragmentByTag("two") != null){
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("two")).commit();
}
break;
case 1:
if(fragmentManager.findFragmentByTag("two") != null) {
//if the fragment exists, show it.
fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag("two")).commit();
} else {
//if the fragment does not exist, add it to fragment manager.
fragmentManager.beginTransaction().add(R.id.container, new TwoFragment(), "two").commit();
}
if(fragmentManager.findFragmentByTag("one") != null){
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("one")).commit();
}
break;
}
// update selected item and title, then close the drawer
mDrawerList.setItemChecked(position, true);
setTitle(mNavTitles[position]);
mDrawerLayout.closeDrawer(mDrawerList);
}
I also added this bit, but I'm not sure if it's necessary or not.
#Override
public void onDestroy() {
super.onDestroy();
FragmentManager fragmentManager = getSupportFragmentManager();
if(fragmentManager.findFragmentByTag("one") != null){
fragmentManager.beginTransaction().remove(fragmentManager.findFragmentByTag("one")).commit();
}
if(fragmentManager.findFragmentByTag("two") != null){
fragmentManager.beginTransaction().remove(fragmentManager.findFragmentByTag("two")).commit();
}
}
Use the attach/detach method with tags:
Detach will destroy the view hirachy but keeps the state, like if on the backstack; this will let the "not-visible" fragment have a smaller memory footprint. But mind you that you need to correctly implement the fragment lifecycle (which you should do in the first place)
Detach the given fragment from the UI. This is the same state as when it is put on the back stack: the fragment is removed from the UI, however its state is still being actively managed by the fragment manager. When going into this state its view hierarchy is destroyed.
The first time you add the fragment
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.add(android.R.id.content, new MyFragment(),MyFragment.class.getSimpleName());
t.commit();
then you detach it
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.detach(MyFragment.class.getSimpleName());
t.commit();
and attach it again if switched back, state will be kept
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.attach(getSupportFragmentManager().findFragmentByTag(MyFragment.class.getSimpleName()));
t.commit();
But you always have to check if the fragment was added yet, if not then add it, else just attach it:
if (getSupportFragmentManager().findFragmentByTag(MyFragment.class.getSimpleName()) == null) {
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.add(android.R.id.content, new MyFragment(), MyFragment.class.getSimpleName());
t.commit();
} else {
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.attach(getSupportFragmentManager().findFragmentByTag(MyFragment.class.getSimpleName()));
t.commit();
}
The replace method destroys your fragments. One workaround is to set them to Visibility.GONE, another (less easy) method is to hold them in a variable. If you do that, make sure you don't leak memory left and right.
I did this before like this:
if (mPrevFrag != fragment) {
// Change
FragmentTransaction ft = fragmentManager.beginTransaction();
if (mPrevFrag != null){
ft.hide(mPrevFrag);
}
ft.show(fragment);
ft.commit();
mPrevFrag = fragment;
}
(you will need to track your pervious fragment in this solution)
I guess you can not directly manipulate the lifecycle mechanisms of your Fragments. The very fact that you can findFragmentByTag is not very bad. It means that the Fragment object is not recreated fully, if it is already commited. The existing Fragment just passes all the lifecycle steps each Fragment has - that means that only UI is "recreated".
It is a very convenient and useful memory management strategy - and appropriate, in most cases. Fragment which is gone, has the resources which have to be utilized in order to de-allocate memory.
If you just cease using this strategy, the memory usage of your application could increase badly.
Nonetheless, there are retained fragments, which lifecycle is a bit different and do not correspond to the Activity they are attached to. Typically, they are used to retain some things you want to save, for example, to manage configuration changes
However, the fragment [re]creation strategy depends on the context - that is, what you would like to solve, and what are the trade-offs that you are willing to accept.
Just find the current fragment calling getFragmentById("id of your container") and then hide it and show needed fragment.
private void openFragment(Fragment fragment, String tag) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Fragment existingFragment = fragmentManager.findFragmentByTag(tag);
if (existingFragment != null) {
Fragment currentFragment = fragmentManager.findFragmentById(R.id.container);
fragmentTransaction.hide(currentFragment);
fragmentTransaction.show(existingFragment);
}
else {
fragmentTransaction.add(R.id.container, fragment, tag);
}
fragmentTransaction.commit();
}
Same idea as Tester101 but this is what I ended up using.
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Fragment oldFragment = fragmentManager.findFragmentByTag( "" + m_lastDrawerSelectPosition );
if ( oldFragment != null )
fragmentTransaction.hide( oldFragment );
Fragment newFragment = fragmentManager.findFragmentByTag( "" + position );
if ( newFragment == null )
{
newFragment = getFragment( position );
fragmentTransaction.add( R.id.home_content_frame, newFragment, "" + position );
}
fragmentTransaction.show( newFragment );
fragmentTransaction.commit();
Hide easily in kotlin using extensions:
fun FragmentManager.present(newFragment: Fragment, lastFragment: Fragment? = null, containerId: Int) {
if (lastFragment == newFragment) return
val transaction = beginTransaction()
if (lastFragment != null && findFragmentByTag(lastFragment.getTagg()) != null) {
transaction.hide(lastFragment)
}
val existingFragment = findFragmentByTag(newFragment.getTagg())
if (existingFragment != null) {
transaction.show(existingFragment).commit()
} else {
transaction.add(containerId, newFragment, newFragment.getTagg()).commit()
}
}
fun Fragment.getTagg(): String = this::class.java.simpleName
Usage
supportFragmentManager.present(fragment, lastFragment, R.id.fragmentPlaceHolder)
lastFragment = fragment
Here's what I'm using for a simple 2 fragment case in Kotlin:
private val advancedHome = HomeAdvancedFragment()
private val basicHome = HomeBasicFragment()
override fun onCreate(savedInstanceState: Bundle?) {
...
// Attach both fragments and hide one so we can swap out easily later
supportFragmentManager.commit {
setReorderingAllowed(true)
add(R.id.fragment_container_view, basicHome)
add(R.id.fragment_container_view, advancedHome)
hide(basicHome)
}
binding.displayModeToggle.onStateChanged {
when (it) {
0 -> swapFragments(advancedHome, basicHome)
1 -> swapFragments(basicHome, advancedHome)
}
}
...
}
With this FragmentActivity extension:
fun FragmentActivity.swapFragments(show: Fragment, hide: Fragment) {
supportFragmentManager.commit {
show(show)
hide(hide)
}
}
How about playing with the Visible attribute?
this is a little late response.
if you're using view pager for fragments, set the off screen page limit of the fragment to the number of fragments created.
mViewPager.setOffscreenPageLimit(3); // number of fragments here is 3
Ok, I've stated that fragments are removed without reason. It seems to me, like this, but sureley there is a reason I don't know about. Please help me find it.
I got the FragmentActivity which contains TabHost, with 5 tabs. TabHost has a onTabChangedListener like this (nothing complicated here, just a case with copied code):
new OnTabChangeListener() {
#Override
public void onTabChanged(String arg0) {
LiveTabFragment fragment = null;
int viewHolder = -1;
switch (tabHost.getCurrentTab()) {
case TAB_ABOUT:
fragment = (LiveAboutFragment) getSupportFragmentManager().findFragmentByTag(LiveAboutFragment.class.getSimpleName());
if (fragment == null) fragment = new LiveAboutFragment();
viewHolder = R.id.live_tab_about;
break;
case TAB_EVENTS:
fragment = (LiveEventsFragment) getSupportFragmentManager().findFragmentByTag(LiveEventsFragment.class.getSimpleName());
if (fragment == null) fragment = new LiveEventsFragment();
viewHolder = R.id.live_tab_events;
break;
case TAB_PLAYERS:
fragment = (LivePlayersFragment) getSupportFragmentManager().findFragmentByTag(LivePlayersFragment.class.getSimpleName());
if (fragment == null) fragment = new LivePlayersFragment();
viewHolder = R.id.live_tab_players;
break;
case TAB_LEGEND:
fragment = (LiveLegendFragment) getSupportFragmentManager().findFragmentByTag(LiveLegendFragment.class.getSimpleName());
if (fragment == null) fragment = new LiveLegendFragment();
viewHolder = R.id.live_tab_legend;
break;
case TAB_CHAT:
fragment = (LiveChatFragment) getSupportFragmentManager().findFragmentByTag(LiveChatFragment.class.getSimpleName());
if (fragment == null) fragment = new LiveChatFragment();
viewHolder = R.id.live_tab_chat;
break;
}
if (fragment != null) injectInnerFragment(fragment, viewHolder, false);
}
}
Here is an injectInnerFragment method:
public void injectInnerFragment(AaFragment fragment, int viewHolderId, boolean addToBackStack) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
if (fragment.isAdded()) {
transaction.replace(viewHolderId, fragment, fragment.getClass().getSimpleName());
if (addToBackStack) transaction.addToBackStack(null);
transaction.commit();
} else {
transaction.add(viewHolderId, fragment, fragment.getClass().getSimpleName());
if (addToBackStack) transaction.addToBackStack(null);
transaction.commit();
}
}
Now the problem:
When I click on tab for thr first time fragment is beeing created (onCreate is called) which is normal. In most cases second clicking on already created tab fragment doesn't call fragment'sonCreate which is what I wanted. Thats why I'm trying to find fragment if FragmentManager first.
There are two cases it doesn't work, and previously created fragment is created again, which is not efficient for me. The cases are:
if I click on any tab, then on TAB_ABOUT, then clicking again on any tab causes it's recreating ("any tab fragment" is not found in FragmentManager)
if I click on any tab, then on TAB_CHAT, then clicking again on any tab causes it's recreating ("any tab fragment" is not found in FragmentManager)
What kind of sorcery is this? Is it some automatic memory freeing, dependant on fragment "weight"? Maybe I should store all data I don't want reload each time fragment is created on Activity instead?
Look at your following code :
transaction.replace(viewHolderId, fragment, fragment.getClass().getSimpleName());
try with :
transaction.add(viewHolderId, fragment, fragment.getClass().getSimpleName());
Solved this by modyfing inject method:
public void injectInnerFragment(AaFragment fragment, int viewHolderId, boolean addToBackStack) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
if (fragment.isAdded()) {
transaction.attach(fragment);
transaction.commit();
} else {
transaction.add(viewHolderId, fragment, fragment.getClass().getSimpleName());
if (addToBackStack) transaction.addToBackStack(null);
transaction.commit();
}
}
I'm trying to create a dual pane/ single pane setup for an application. It was working fine a while ago before I put all the inards into the fragments, and I don't know what caused it not to work. The problem is happening when I'm doing an orientation change. At first, it was not recreating the view for some reason. It would call onCreateView, but I was not able to get a handle on the listview inside of the view and was getting null (I know I have the layouts correct for landscape and portrait, as it works in both portrait and landscape if it starts there). So, what I did is a add setRetainInstance to true, thinking that would fix my problem. Well, that brought up a whole other issue of now I'm getting No View Found For Id.
What I think is happening now is that its trying to reattach itself to the ID it had before the orientation change, and it doesn't exist as were on a different layout now. I tried just creating a new fragment and adding it, but that doesn't work either. I'm borderline pulling my hair out trying to work with Android's have thought fragment system that almost impossible to work with. Any help would be appreciated. Here is the relevant code
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.study_guide);
//Create The Ad Service
//Check to see if the view is landscape or portrait
if (this.findViewById(R.id.singlePaneStudyGuide) != null) {
mDualPane = false;
} else if (this.findViewById(R.id.doublePaneStudyGuide) != null) {
mDualPane = true;
}
LinearLayout layout = (LinearLayout)findViewById(R.id.addbox);
adView = new AdView(this,AdSize.SMART_BANNER,"a1511f0352b42bb");
layout.addView(adView);
AdRequest r = new AdRequest();
r.setTesting(true);
adView.loadAd(r);
//Inflate Fragments depending on which is selected
//if (savedInstanceState == null) {
FragmentManager fm = this.getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
SGListFragment newFrag = new SGListFragment();
if (mDualPane == true) {
ft.add(R.id.sgScrollContainer, newFrag, "SgListView").commit();
} else {
ft.add(R.id.singlePaneStudyGuide, newFrag, "SgListView").commit();
}
fm.executePendingTransactions();
//}
}
Ive tried looking for the fragment with the fragment manager and reassigning it to a different layout but for some reason its still looking for the old one.
You have to rewrite your code as follow:
//Inflate Fragments depending on which is selected
//if (savedInstanceState == null) {
FragmentManager fm = this.getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
// here the trick starts
int oldId = (mDualPane == true) ? R.id.singlePaneStudyGuide
: R.id.sgScrollContainer;
Fragment oldFragment = fm.findFragmentById(oldId);
if(null != oldFragment){
ft.remove(oldFragment);
ft.commit();
fm.executePendingTransactions();
ft = fragmentManager.beginTransaction();
}
// end of tricks
SGListFragment newFrag = new SGListFragment();
if (mDualPane == true) {
ft.add(R.id.sgScrollContainer, newFrag, "SgListView").commit();
} else {
ft.add(R.id.singlePaneStudyGuide, newFrag, "SgListView").commit();
}
I am developing application which contains 2 fragments and i want to show hide according to my need. Following code has simple example of my problem.
This simple Fragmentactivity contains 1 button and one listfragment.
This simple example works flawless. but i am not satisfied with show hide fragment. If you remove layout.setVisibility(View.GONE); from the code then ft.hide(f); will not hide fragment. In fact we are not hiding fragment we are hiding container.
My Question is, IS this a way to show hide fragments? If not then please explain with tested example How to hide and show Fragments because lots of people are facing this problem.
public class MainActivity extends FragmentActivity implements OnClickListener {
Fragment1 f;
Button b;
LinearLayout layout;
Fragment myf;
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
b = (Button) findViewById(R.id.button1);
layout = (LinearLayout) findViewById(R.id.ll);
f = new Fragment1();
}
#Override
public void onClick(View v) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);
if (f.isHidden()) {
ft.show(f);
layout.setVisibility(View.VISIBLE);
b.setText("Hide");
} else {
ft.hide(f);
b.setText("Show");
layout.setVisibility(View.GONE);
}
ft.commit();
// TODO Auto-generated method stub
}
Don't mess with the visibility flags of the container - FragmentTransaction.hide/show does that internally for you.
So the correct way to do this is:
FragmentManager fm = getFragmentManager();
fm.beginTransaction()
.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out)
.show(somefrag)
.commit();
OR if you are using android.support.v4.app.Fragment
FragmentManager fm = getSupportFragmentManager();
fm.beginTransaction()
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out)
.show(somefrag)
.commit();
In addittion, you can do in a Fragment (for example when getting server data failed):
getView().setVisibility(View.GONE);
Hi you do it by using this approach, all fragments will remain in the container once added initially and then we are simply revealing the desired fragment and hiding the others within the container.
// Within an activity
private FragmentA fragmentA;
private FragmentB fragmentB;
private FragmentC fragmentC;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
fragmentA = FragmentA.newInstance("foo");
fragmentB = FragmentB.newInstance("bar");
fragmentC = FragmentC.newInstance("baz");
}
}
// Replace the switch method
protected void displayFragmentA() {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (fragmentA.isAdded()) { // if the fragment is already in container
ft.show(fragmentA);
} else { // fragment needs to be added to frame container
ft.add(R.id.flContainer, fragmentA, "A");
}
// Hide fragment B
if (fragmentB.isAdded()) { ft.hide(fragmentB); }
// Hide fragment C
if (fragmentC.isAdded()) { ft.hide(fragmentC); }
// Commit changes
ft.commit();
}
Please see https://github.com/codepath/android_guides/wiki/Creating-and-Using-Fragments for more info. I hope I get to help anyone. Even if it this is an old question.
public void showHideFragment(final Fragment fragment){
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.setCustomAnimations(android.R.animator.fade_in,
android.R.animator.fade_out);
if (fragment.isHidden()) {
ft.show(fragment);
Log.d("hidden","Show");
} else {
ft.hide(fragment);
Log.d("Shown","Hide");
}
ft.commit();
}
Try this:
MapFragment mapFragment = (MapFragment)getFragmentManager().findFragmentById(R.id.mapview);
mapFragment.getView().setVisibility(View.GONE);
I may be way way too late but it could help someone in the future.
This answer is a modification to mangu23 answer
I only added a for loop to avoid repetition and to easily add more fragments without boilerplate code.
We first need a list of the fragments that should be displayed
public class MainActivity extends AppCompatActivity{
//...
List<Fragment> fragmentList = new ArrayList<>();
}
Then we need to fill it with our fragments
#Override
protected void onCreate(Bundle savedInstanceState) {
//...
HomeFragment homeFragment = new HomeFragment();
MessagesFragment messagesFragment = new MessagesFragment();
UserFragment userFragment = new UserFragment();
FavoriteFragment favoriteFragment = new FavoriteFragment();
MapFragment mapFragment = new MapFragment();
fragmentList.add(homeFragment);
fragmentList.add(messagesFragment);
fragmentList.add(userFragment);
fragmentList.add(favoriteFragment);
fragmentList.add(mapFragment);
}
And we need a way to know which fragment were selected from the list, so we need getFragmentIndex function
private int getFragmentIndex(Fragment fragment) {
int index = -1;
for (int i = 0; i < fragmentList.size(); i++) {
if (fragment.hashCode() == fragmentList.get(i).hashCode()){
return i;
}
}
return index;
}
And finally, the displayFragment method will like this:
private void displayFragment(Fragment fragment) {
int index = getFragmentIndex(fragment);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if (fragment.isAdded()) { // if the fragment is already in container
transaction.show(fragment);
} else { // fragment needs to be added to frame container
transaction.add(R.id.placeholder, fragment);
}
// hiding the other fragments
for (int i = 0; i < fragmentList.size(); i++) {
if (fragmentList.get(i).isAdded() && i != index) {
transaction.hide(fragmentList.get(i));
}
}
transaction.commit();
}
In this way, we can call displayFragment(homeFragment) for example.
This will automatically show the HomeFragment and hide any other fragment in the list.
This solution allows you to append more fragments to the fragmentList without having to repeat the if statements in the old displayFragment version.
I hope someone will find this useful.
From my code, comparing to above solution, the simplest way is to define a layout which contains the fragment, then you could hide or unhide the fragment by controlling the layout attribute which is align with the general way of view. No additional code needed in this case and the additional deployment attributes of the fragment could be moved to the outer layout.
<LinearLayout style="#style/StHorizontalLinearView"
>
<fragment
android:layout_width="match_parent"
android:layout_height="390dp"
android:layout_alignParentTop="true"
/>
</LinearLayout>
final Fragment fragment1 = new fragment1();
final Fragment fragment2 = new fragment2();
final FragmentManager fm = getSupportFragmentManager();
Fragment active = fragment1;
In onCreate, after setContentView, i hid two fragments and committed them to the fragment manager, but i didn't hide the first fragment that will serve as home.
fm.beginTransaction().add(R.id.main_container, fragment2, "2").hide(fragment2).commit();
fm.beginTransaction().add(R.id.main_container,fragment1, "1").commit();
#Override
public void onClick(View v) {
Fragment another = fragment1;
if(active==fragment1){
another = fragment2;
}
fm.beginTransaction().hide(active).show(another).commit();
active = another;
}
Ref : https://medium.com/#oluwabukunmi.aluko/bottom-navigation-view-with-fragments-a074bfd08711
This worked for me
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if(tag.equalsIgnoreCase("dashboard")){
DashboardFragment dashboardFragment = (DashboardFragment)
fragmentManager.findFragmentByTag("dashboard");
if(dashboardFragment!=null) ft.show(dashboardFragment);
ShowcaseFragment showcaseFragment = (ShowcaseFragment)
fragmentManager.findFragmentByTag("showcase");
if(showcaseFragment!=null) ft.hide(showcaseFragment);
} else if(tag.equalsIgnoreCase("showcase")){
DashboardFragment dashboardFragment = (DashboardFragment)
fragmentManager.findFragmentByTag("dashboard");
if(dashboardFragment!=null) ft.hide(dashboardFragment);
ShowcaseFragment showcaseFragment = (ShowcaseFragment)
fragmentManager.findFragmentByTag("showcase");
if(showcaseFragment!=null) ft.show(showcaseFragment);
}
ft.commit();
the answers here are correct and i liked #Jyo the Whiff idea of a show and hide fragment implementation except the way he has it currently would hide the fragment on the first run so i added a slight change in that i added the isAdded check and show the fragment if its not already
public void showHideCardPreview(int id) {
FragmentManager fm = getSupportFragmentManager();
Bundle b = new Bundle();
b.putInt(Constants.CARD, id);
cardPreviewFragment.setArguments(b);
FragmentTransaction ft = fm.beginTransaction()
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);
if (!cardPreviewFragment.isAdded()){
ft.add(R.id.full_screen_container, cardPreviewFragment);
ft.show(cardPreviewFragment);
} else {
if (cardPreviewFragment.isHidden()) {
Log.d(TAG,"++++++++++++++++++++ show");
ft.show(cardPreviewFragment);
} else {
Log.d(TAG,"++++++++++++++++++++ hide");
ft.hide(cardPreviewFragment);
}
}
ft.commit();
}
I have a layout that has an EditText and a Button. I <include> it in my main layout.
I'm having a weird issue with the layout and rotation. It seems to duplicate itself when the device (physical) is rotated, messing up the text and layout.
Here it is on first open, after I add some extra garble:
DSC_0013 is in the EditText on launch of the fragment.
Then, I rotate the phone and add some different garble:
And you can see the issue pretty clearly. At first, I thought it was just the EditText messing up. But if I add enough text to make a new line:
I can see that the button gets messed up too.
I do override onSaveInstanceState, but in it I don't touch the EditText or its value, it's strictly used for something else.
What's happening and how do I fix it?
Fixed it!
Turns out it wasn't the view duplicating itself, or the EditText, or the Button. It was the entire fragment.
In my Activity's onCreate, I add the fragment to an xml layout:
private FileDetails fileDetailsFragment;
public void onCreate(Bundle savedInstanceState) {
...
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
fileDetailsFragment = new FileDetails(fileData);
fragmentTransaction.add(R.id.DetailsHolder, fileDetailsFragment);
fragmentTransaction.commit();
And onCreate was being called every time I rotated the phone (as it's meant to). So I put in a check to see if the activity is being run for the first time, and it works great.
private FileDetails fileDetailsFragment;
public void onCreate(Bundle savedInstanceState) {
...
if (savedInstanceState == null) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
fileDetailsFragment = new FileDetails(fileData);
fragmentTransaction.add(R.id.DetailsHolder, fileDetailsFragment);
fragmentTransaction.commit();
} else {
fileDetailsFragment = (FileDetails) getSupportFragmentManager().findFragmentById(R.id.DetailsHolder);
}
You can also setRetainedInstance(true) on your fragment, then try to get the Fragment form de FragmentManager.findFragmentById(int) or FragmentManager.findFragmentByTag(String), and if it returns null it meant you had to create a new instance of your Fragment.
private FileDetails fileDetailsFragment;
public void onCreate(Bundle savedInstanceState) {
...
FragmentManager fragmentManager = getSupportFragmentManager();
fileDetailsFragment = (FileDetails) getSupportFragmentManager().findFragmentById(R.id.DetailsHolder);
if (fileDetailsFragment == null) {
fileDetailsFragment = new FileDetails(FileData);
}
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
fragmentTransaction.add(R.id.DetailsHolder, fileDetailsFragment);
fragmentTransaction.commit();
}
In some cases, the value of savedInstanceState may be null after rotation, so it is better to add another condition:
FragmentManager fragmentManager = getSupportFragmentManager();
if (savedInstanceState == null &&
fragmentManager.getFragments().size() == 0) {
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
fileDetailsFragment = new FileDetails(fileData);
fragmentTransaction.add(R.id.DetailsHolder, fileDetailsFragment);
fragmentTransaction.commit();
} else {
fileDetailsFragment = (FileDetails)
getSupportFragmentManager().findFragmentById(R.id.DetailsHolder);
}