I have the necessity to replace one starting fragment (I'll call it A) of an activity with two other fragments (B and C, in the "usual" list+viewer configuration). Currently I have a relative layout with two frame layouts acting as a placeholder for B and C:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<RadioGroup
android:id="#+id/radiogroup_navigation"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<!-- Some radiobuttons (not displayed for the sake of brevity) -->
</RadioGroup>
<FrameLayout
android:id="#+id/frame_list"
android:layout_width="100dp"
android:layout_height="fill_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentBottom="true"
android:layout_below="#id/radiogroup_navigation">
</FrameLayout>
<FrameLayout
android:id="#+id/frame_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_below="#id/radiogroup_navigation"
android:layout_toRightOf="#id/frame_list">
</FrameLayout>
When I need to display A, I just hide frame_list and add A to frame_view, and when I need to display B and C I set frame_list visible again and add the two fragments to each frame, in the same fragment transaction.
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.remove(fragmentA);
t.add(R.id.frame_list, fragmentB);
t.add(R.id.frame_view, fragmentC);
t.addToBackStack(null);
t.commit();
In this way, when i press the back button, both C and B go away and I'm back to A fragment, but now frame_list is visible (and empty).
I am thinking to solve the problem in two possible ways:
overriding onBackPressed to hide the left frame if needed;
nesting B and C in another fragment;
But I also feel I'm probably looking at the problem in the wrong way, and maybe there's a cleaner design solution. Do you have any advice?
If I understand correctly, here is one solution:
Create two activities ActivityA and ActivityBC
Create another fragment with the radiogroup
Embed FragmentRadio into both ActivityA and ActivityBC
Have that fragment start new activities based on selection whilst finishing current activity
Make fields like this:
private static final String FRAGMENT_B_TAG = "fragmentB";
When you add the fragments, use the static Strings like tags:
t.add(R.id.frame_list, fragmentB, FRAGMENT_B_TAG);
t.add(R.id.frame_view, fragmentC, FRAGMENT_C_TAG);
In your activity, set up a listener, which will get triggered every time after you call addToBackStack(String). It will find out which fragment is currently visible and hide/show needed containers.
getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
FragmentA fa = getSupportFragmentManager().findFragmentByTag(FRAGMENT_A_TAG);
FragmentB fb = getSupportFragmentManager().findFragmentByTag(FRAGMENT_B_TAG);
if (fa != null && fa.isVisible()) {
// Fragment A is visible, so hide the second container which is now empty
}
if (fb != null && fb.isVisible()) {
// Fragment B is visible, so show the second container
}
}
});
Notice that checking whether Fragment C is visible or not is not needed since when Fragment B is visible, Fragment C is always visible too.
This is an untested code, but I think it should work. Also, if you need any explanation, don't hesitate to ask.
Hope it helps.
Related
There is one Activity, called MainActivity, with layout file below (only two FrameLayout as containers).
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/tabletux_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<FrameLayout
android:id="#+id/tabletux_container1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"></FrameLayout>
<FrameLayout
android:id="#+id/tabletux_container2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"></FrameLayout>
</LinearLayout>
And two fragments: A and B.
If I commit a transaction (say, transactionA) to "start" fragment A,
getFragmentManager().beginTransaction().
replace(R.id.tabletux_container1, displayFragment,
GeneralConstants.DISPLAY_FRAGMENT_FRAGMENTTRANSACTION_TAG).commit();
And then in fragment A (after transactionA), I commited another transaction (say, transactionB) to "start" fragment B.
// This is committed in DisplayFragment, not in MainActivity
getFragmentManager().beginTransaction()
.replace(R.id.tabletux_container2, detailFragment,
GeneralConstants.DETAIL_FRAGMENT_FRAGMENTTRANSACTION_TAG).commit();
How am I supposed to retrieve fragment B when trying to save the InstanceState of fragment B in MainActivity, especially after twice or more times of rotations of the tablet?
Further Questions:
1. What's the relationship between transactionA and transactionB?
2. Let me make a wild deduction: transactionA and transactionB are in a "transactions pool", so that all fragments committed by any of them can be retrieved from the "pool" by id or tag. If I am wrong, please help correct.
3. If transactions are separate and have no relations with each other, how am I able to find the fragment in the specific transaction? How can I locate the transaction?
What I have tried on method onSaveInstanceState(Bundle outState) in MainActivity:
detailFragment = (DetailFragment) getFragmentManager()
.findFragmentByTag(GeneralConstants.DETAIL_FRAGMENT_FRAGMENTTRANSACTION_TAG);
But this returns null. But the fragment "started" in the transaction committed in the MainActivity can be located via its tag.
For your questions, Please refer below link :
http://developer.android.com/guide/components/fragments.html
According to this guide :
Each fragment transaction is a set of changes that you want to perform at the same time. You can set up all the changes you want to perform for a given transaction using methods such as add(), remove(), and replace().
Then, to apply the transaction to the activity, you must call commit().
So you need to change your code as below :
getFragmentManager().beginTransaction().
replace(R.id.tabletux_container1, displayFragment,
GeneralConstants.DISPLAY_FRAGMENT_FRAGMENTTRANSACTION_TAG)
.replace(R.id.tabletux_container2, detailFragment,
GeneralConstants.DETAIL_FRAGMENT_FRAGMENTTRANSACTION_TAG).commit();
Thank you..!!
I found a tricky solution: in AndroidManifest.xml file, add the code below to the activity where the 2 fragments were committed
android:configChanges="orientation|screenSize"
The info provided by the 2 fragments will remain the same, but layout will be tuned according to screen size (For example: GridView will display more columns in horizontal view than in vertical view), which is exactly what I expect.
I could not explain how it works, nor do I know the limitation of such solution.
Hello you!
I am not a native english speaker, so hope you understand me anyway.
In my application i have a activity with a RelativeLayout wich i use as Fragment-container.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<RelativeLayout
android:id="#+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/white" />
</FrameLayout>
At the time i have two different Fragments, one will be shown at Startup and die other one will be called by click on a TextView in the first one. Both Fragments have its own class and the first one has a interface to the main activity to perform the replacement of the Fragment after onClick.
And now there is my Problem....
public void onCallForAGB() {
getFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, new LoginReaderFragment(), "agbreader")
.addToBackStack(null)
.commit();
LoginReaderFragment fragment = (LoginReaderFragment) getFragmentManager()
.findFragmentById(R.id.fragmentContainer);
if (fragment != null && fragment.isInLayout()) {
fragment.setText("Gruß an die Welt!");
} else {
Toast.makeText(mContext, "Fail", Toast.LENGTH_LONG).show();
}
}
just for explaining:
the container holds a Fragment named LoginLoginFrament. The user of my app can Login or click on a link (TextView) to show the AGB (dont know how to translate, maybe "Therms for use and Law dependencies" ??). Via the interfaceonCallForAGBwill be executed and replace the Fragment with a Fragment calledLoginReaderFragment` wich is a Fragment with a headline (TextView) and a textfield (TextView) to show filecontents. The function setText(string) should set the text after the Fragment was created.
But after Fragmenttransaction.commit() it seems the change will not be executed immediately. It seems it will be executed when leaving the whole procedure. So i can't access the new fragments Views (to change text) without the complete perfomed replace by commit.
So here is my question: How can i force the FragmentTransaction to be executed after commit() ? Or is there a workaround, so that i can change the headline and text of the new Fragment right after changing from the first to the second Fragment?
Here is the Errorreport (LogCat) which is why I have this assumption
05-29 20:59:18.748: E/AndroidRuntime(14334):
java.lang.ClassCastException:
de.example.myapp.fragments.LoginLoginFragment cannot be cast to
de.example.myapp.fragments.LoginReaderFragment
I think LoginReaderFragment fragment = .... create this error because the old Fragment (LoginLoginFragmet) is still active.
Hope you can help me.
Greets !
Do not try to set the Fragments UI data from the Activity itself after creating it.
Instead, give the fragment the information, which should be shown after the fragment is visible.
The best way to do this is to use the getInstance() pattern. see here.
You need to call the setText function from onViewCreated() in the fragment itself. this grants you that the data will be set, after the view is availible.
My android app has a registration flow setup with an Activity and I load the steps as fragments into the activity layout.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal"
tools:context="com.example.android.ui.RegisterActivity">
<LinearLayout
android:id="#+id/register_container"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"></LinearLayout>
</RelativeLayout>
Now each fragment class implements a public interface implemented in the RegisterActivity so that I know to load the next step fragment and I add the new fragment to the backstack
mFragmentTransaction.addToBackStack(mStepOne.TAG);
now it all works fine all the way through 4 steps and I can navigate back through the steps while retaining the inputted data in each fragment IF it stays in the same orientation (portait)
BUT once i change the orientation, the fragment views disappear
if I am up to step 3, I can still hit the back button and it will go back showing me the fragment that is meant to be there by viewing the backstack changed listener
mFragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
if(mFragmentManager.getBackStackEntryCount() > 0) {
Log.d("BACK STACK", "TAG (name): " + mFragmentManager.getBackStackEntryAt( (mFragmentManager.getBackStackEntryCount() - 1)).getName());
}
}
});
The count and tags of the fragments is retained but not the views. The RegisterActivity onCreateView loads the terms fragment into the view and it is what remains in view when navigating back through the steps.
mFragmentManager = getSupportFragmentManager();
mFragmentTransaction = mFragmentManager.beginTransaction();
mTerms = new RegisterFragmentTerms();
mFragmentTransaction.add(R.id.register_container, mTerms);
mFragmentTransaction.commit();
It seems to me the views are loaded into the RegisterActivity on top of each other and cleared on orientation change. Is it possible to solve what I'm trying to do? or have I implemented it wrong? It's my first android app :)
Cheers
It seems to be caused by Activity recreation. Have you ever add android:configChanges="orientation|keyboardHidden|screenSize" in your AndroidManifest.xml to your Activity? , if you add that flag to you activity, it will not be destroyed when orientation changes and the fragments stack will not disappear.
I have an Activity with a Button and a FrameLayout in its layout.
When I click the Button I add the fragment to the Activity's View.
If I add the fragment to the Back stack with addToBackStack() when I click the Back button it dissapears.
I want to achieve the same functionality by clicking again the Button.
My code is this :
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
AddRemoveFragment Frag_A = new AddRemoveFragment();
FragmentManager fm1 = getSupportFragmentManager();
FragmentTransaction transaction = fm1.beginTransaction();
if ( state == 0 ) {
Log.i(TAG, "inside IF");
state=1;
transaction.add(R.id.fragment_container_1, Frag_A);
transaction.addToBackStack(null);
transaction.commit();
} else {
state=0;
Log.i(TAG, "inside ELSE");
//transaction.replace(R.id.fragment_container_1, Frag_A);
transaction.remove(Frag_A);
transaction.commit();
}
}
});
Both remove() and hide() do nothing.
From the reference I don't understand something more specific. Just says it removes the fragment from the container. Isn't this what I want?Remove the fragment from FrameLayout?
Edit: hope it has nothing to do with the support library. I saw someone having some problems with that. Here
XML :
<?xml version="1.0" encoding="utf-8"?>
<Button
android:id="#+id/button_frag_1"
android:layout_width="124dp"
android:layout_height="wrap_content"
android:text="#string/button_text_1" />
<FrameLayout
android:id = "#+id/fragment_container_1"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:layout_alignBottom="#+id/button_frag_1"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_toRightOf="#+id/button_frag_1" >
</FrameLayout>
Edit 2: I changed the code inside the else statement from transaction.replace(R.id.fragment_container_1, Frag_A); to transaction.remove(Frag_A); but still got the same functionality.
For fragments, first of all you need to remember one thing:
If you added your fragment in your XML layout, then it can't be removed, it can only be shown using the .show() method and hidden using the .hide() method. If on the other hand you create an instance of your fragment in your code then you should add it using the .add() method or remove it using the .remove() method.
As regard to your question, I dont think you need to add your fragment to back stack if you want to remove your fragment using your button (unless you want to keep the functionality of removing it using the 'back' button).
In addition I don't think you need to use replace, from the documentation of replace:
Replace an existing fragment that was added to a container. This is essentially the same as calling remove(Fragment) for all currently added fragments that were added with the same containerViewId and then add(int, Fragment, String) with the same arguments given here.
It means that it replaces the content of the container with the new fragment, so all you do is remove your fragment and add it again.
You should .add() you fragment when you want to show it and .remove() it when you dont.
UPDATE:
Following you second question, when I say that you can add you fragment in your xml I mean that you can write this:
<fragment
xmlns:map="http://schemas.android.com/apk/res-auto"
android:id="#+id/listfragment"
android:name="com.eadesign.yamba.TimeLineListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
In your XML layout file inside your FrameLayout which is your fragment container, in this case you cant remove this fragment you can only hide it.
And just to clarify you will always have to provide some kind of layout which will be the container of your fragment/fragments.
as opposite to that you can do what your are doing in your code:
AddRemoveFragment Frag_A = new AddRemoveFragment();
transaction.add(R.id.fragment_container_1, Frag_A);
transaction.addToBackStack(null);
transaction.commit();
In this case the fragment can be removed.
UPDATE2:
Try to take this line: AddRemoveFragment Frag_A = new AddRemoveFragment(); outside of the setOnClickListener method scope. I think that your problem is the fact that you are creating a new instance of this fragment on every click of your button. In fact I would move this line FragmentManager fm1 = getSupportFragmentManager(); out side as well there is no need to get the instance of a SupportFragmentManager on each click of your button. You should do this once.
I have an activity with two fragments like this image :
status A
Fragment B is a ListFragment. When user clicks on one row in fragment B. I have show detail. And the UI should like this:
status B
How can I move and resize fragment B?
I tried this way but don't know whether it is a correct way or hacky way.
I set this layout for the activity.
<LinearLayout
android:id="#+id/linearLayoutMainPart"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#+id/scrollViewBottomMenu"
android:layout_toRightOf="#+id/scrollViewLeftMenu"
android:background="#android:color/white"
android:orientation="horizontal" >
<FrameLayout
android:id="#+id/leftContainer"
android:layout_width="wrap_content"
android:layout_height="match_parent" >
</FrameLayout>
<FrameLayout
android:id="#+id/rightContainer"
android:layout_width="0dip"
android:layout_height="match_parent">
</FrameLayout>
</LinearLayout>
I create 3 fragment A, B, C class. When the activity create I add two fragment A, B to leftContainer and rightContainer.
(fragment A layout width is fixed). When user click on one row in fragment B. I replace fragment A by another instance of fragment B (with different layout, and using the same data with the old fragment B instance). And replace fragment B in rightContainer by an instance of fragment C.
In fragment B class. I have flag to choose layout when onCreateView method is called, something like :
if(rightPosition)
return B_right_pos_layout;
else
return B_left_pos_layout;
I think I can use the same instance of Fragment B. By doing like this :
FragmentB fragmentB = (FragmentB) getSupportFragmentManager().findFragmentByTag(FRAGMENT_B_TAG);
fragmentB.setRightPos(false);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(rightContainer, fragmentC);
transaction.replace(leftContainer, fragmentB);
transaction.commit();
I think this way is more like a hack way. What is the best way?