How to saveInstanceState of two fragments committed in different transactions in MainActivity? - android

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.

Related

How can I get Fragment from View?

I added some Fragment into a TableLayout and I want to manage them from my container Activity, so I used this:
Fragment fragment = (Fragment) tableLayout.getChildAt(i);
but getChildAt(int) returns a View and a View could NOT cast to Fragment
I don't understand why people are down-voting your question. Fragments can be very confusing at times, especially for beginners. To understand your problem, you must learn what is a Fragment and how they are used.
To start with, a View is something that has an existence on the screen. Examples include: TextView, EditText, Button, etc. They are placed inside "layouts" written in Xml or Java/Kotlin. These layouts are shown using an Activity.
Now, a Fragment is not a View. It does not have any existence on the screen at all. Instead, it's a class that simply manages a "layout" — kinda similar to an Activity. If you need the View returned by your Fragment's onCreateView(), you can directly use findViewById() within your Activity.
If you need a reference to your Fragment, there are two possible ways of doing this:
1) If you added the Fragment programmatically like this
getFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container_viewgroup, myFragment, FRAGMENT_TAG)
.commit();
You can use:
MyFragment myFragment = (MyFragment) getFragmentManager().findFragmentByTag(FRAGMENT_TAG);
2) If you added the Fragment inside an XML layout like this:
<fragment android:name="com.example.android.fragments.HeadlinesFragment"
android:id="#+id/fragmentContainer"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
You can use this:
getFragmentManager().findFragmentById(R.id.fragmentContainer);
Basically, each Activity has a FragmentManager class that maintains all the active Fragments, and there are two ways of finding them: Using a unique TAG that you pass while showing a fragment, or passing the container view-ID where the fragment was added.
For people looking how to actually get a reference to the Fragment object from a View there is now a method in FragmentManager called findFragment(View) (reference)
//in Java
FragmentManager.findFragment(view)
//in Kotlin there is an extension function
view.findFragment()
Be careful - it will throw an IllegalStateException if the view was not added via a fragments onCreateView.
You can not get a fragment like this. You will have to add fragment with a tag and retrieve it by that tag.
to add a fragment do following:
getFragmentManager().beginTransaction().add(R.id.container, fragment, "tagTofindFragment");
to get fragment:
fragment = getFragmentManager().findFragmentByTag("tagTofindFragment");
Here tagTofindFragment is that tag that should be unique among your fragments.

How to replace a fragment and change TextView in the new one in the same procedure?

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.

Get Fragment dynamically attached to <FrameLayout>?

Well, i got a simple <FrameLayout>:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/FragmentContainer"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
Then in my code, i added a Fragment to it:
FragClass aFrag = new FragClass();
getSupportFragmentManager().beginTransaction()
.replace(R.id.FragmentContainer, aFrag).commit();
And somewhere else in my code, i want to get that FragClass (extends Fragment) object from the ID R.id.FragmentContainer.
i have tried
((ViewGroup) findViewById(R.id.FragmentContainer)).getChildAt(0)
or
((FrameLayout) findViewById(R.id.FragmentContainer)).getChildAt(0)
but they are returning the View, instead of the Fragment attached to it.
i know i can keep the variable aFrag somewhere, so i do not need to find it again. But i believe there should be a way to retieve it.
Let me wrap it up by a full answer :)
In this case, the dynamically added Fragment uses the ID of the container View (ViewGroup).
ref: http://developer.android.com/guide/components/fragments.html#Adding
Note: Each fragment requires a unique identifier that the system can use to restore the fragment if the activity is restarted (and which you can use to capture the fragment to perform transactions, such as remove it). There are three ways to provide an ID for a fragment:
Supply the android:id attribute with a unique ID.
Supply the android:tag attribute with a unique string.
If you provide neither of the previous two, the system uses the ID of the container view.
It is because it's a Fragment afterall. We have to use getSupportFragmentManager().findFragmentById() to retrieve it, which returns a Fragment, instead of findViewById() which returns a View.
So the answer to this problem would be:
((aFrag) getSupportFragmentManager().findFragmentById(R.id.FragmentContainer))
Thanks to #Luksprog.

Fragments Replacing Fragments and Navigating Fragments usefully

So I have this ClientListView that works great, shows clients, I can click on a client and get their details on the right (in my second fragment). Defined by this layout here:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment class="com.viciousbytes.studiotab.subactivities.ClientListView"
android:id="#+id/client_list" android:layout_weight="1"
android:layout_width="0px" android:layout_height="match_parent" />
<FrameLayout android:id="#+id/client_details" android:layout_weight="1"
android:layout_width="0px" android:layout_height="match_parent"
android:background="?android:attr/detailsElementBackground" />
</LinearLayout>
This works great, but then I realized another activity I had (that was a fragmentactivity displaying a fragment) took up the whole screen, and would be better served being split into two.
So I went about changing some code that displayed this fragment activity originally
void showSessionEdit()
{
...
Intent intent = new Intent(getActivity(), EditSessionActivity.class);
// Send the recipe index to the new activity
intent.putExtra(EditSessionActivity.THE_SELECTED_CLIENT, (int)mClient.getID());
intent.putExtra(EditSessionActivity.SELECTED_SESSION, sessionId);
startActivityForResult(intent, 1899);
....
}
This worked great, brough up my session editor, i click back I get back to my clients and details. Though I realized I want my session editor to work more like my client list /details which has both on same screen. Through lots LOTS of trial of error I finally did replaced the above with this:
void showSessionEdit()
{
...
SessionEdit details = (SessionEdit) getFragmentManager().findFragmentById(R.id.session_edit);
// Make new fragment instance to show the recipe
details = SessionEdit.newInstance(mContext, mIsTablet, (int)mClient.getID(), sessionId);
// Replace the old fragment with the new one
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.client_list, details);
ft.addToBackStack("client_list");
// Use a fade animation. This makes it clear that this is not a new "layer"
// above the current, but a replacement
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
...
//now add another fragment to the right, just to test i dont have my invoice fragment done yet so I just added session again to see if it would display it does.
SessionEdit details2 = (SessionEdit) getFragmentManager().findFragmentById(R.id.session_edit);
// Make new fragment instance to show the recipe
details2 = SessionEdit.newInstance(mContext, mIsTablet, (int)mClient.getID(), sessionId);
// Replace the old fragment with the new one
FragmentTransaction ft2 = getFragmentManager().beginTransaction();
ft2.replace(R.id.client_details, details2);
ft.addToBackStack("client_details");
// Use a fade animation. This makes it clear that this is not a new "layer"
// above the current, but a replacement
ft2.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft2.commit();
...
}
This works well, though i realized that I was replacing not the "div" so to speak on the layout but the fragment itself, so my references to findFragmentById were no longer my client_details type or client_list type but now a SessionEdit type. So after more trial and error i learned to add the tag to addToBackStack(tag) and I could find fragmentsByTag now, this worked well, and back button sorta worked: Clicking back would replace my client_list on the left again so I could click clients and get their details on the right, the problem is, if my client_list was back again the details on the right would still show my session fragment. Also another issue is my clients list was a ListFragment, so when I did the replace, I could still see the list like underneath the new fragment, as if it was celluloid or something. Very strange.
So I made some headway on replacing my original fragments with new ones, but navigating using the back button no longer works "out of the box" like it did when I was just doing the two fragments, and adding new activities onto each other. So how does one go about navigating around multiple fragments? Ideally I would have a SessionInvoiceActivity that would replace both fragments at once (The client list on left, client details on right) with the session on left, invoices on right. And when the backbutton is clicked, id get back to my client list and client details? But this I am not sure how to do and still pass in the information I need. I am also still not clear as to why when I replace the fragment, the original fragment can be seen underneath? Is it a xml layout definition issue?
I am not sure if you have figured this out now, seems I am a summer behind.
I found fragments specified in layouts (in XML) and fragments added with the FragmentManager are difficult to mix.
I am having a similar issue being I add my "Master" Fragment in onCreate(), then when deemed necessary through size or user selection a second fragment the "Details" Fragment is either put where the "Master" Fragment was or is added to a second layout if it is in landscape mode. When the user hits the Back Key it will show a blank screen with no "Master" Fragment. I thought I may have had a work around for this by making the original call to display my "Master" Fragment in the onStart() of the Activity. Yet there seems to be one more gotcha to this. When adding the "Master" Fragment from the onStart() DO NOT add the transaction to the BackStack. That way the onResume() or whatever from your previous Activity will be called instead, thus not leaving an Activity with a blank screen.

layout with fragment and framelayout replaced by another fragment and framelayout

EDIT:
So after the comments below, I revisted and realized what was hanging me up.
Imagine my client list and client details activity be started by :
public class ClientsMainActivity extends FragmentActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//StudioTabOpenHelper db;
setContentView(R.layout.main_client_activity);
}
}
So this works great, starts up my main_client_Activity (defined in a layout below, and i call this activity when a button on my main screen is clicked):
Intent intent = new Intent(getActivity(), ClientsMainActivity.class);
startActivity(intent);
Easy the issue is, the ClientsMainActivity does not call a OnCreateView or anything, just sets the layout to the layout that defines my Fragment, and my ListFragment. This is fine cause I am not trying to pass anything into the ClientsMainActivity, but if I have a hypothetical activity like:
SessionMainsActivity that is called when they click on the session edit of a client, then I would not be calling the SessionsMainActivity the same way (starts activity that just sets to alayout), i would want that layout set as it defines how my fragments are split up. But I would also want to pass in data to that and then to the subsequent fragments (like which session they clicked on to be editing/mucking with.
So I wonder if the above makes sense, I am sure its a simple thing I just cannot wrap my brain around. I have no issues calling FragmentActivities from other fragments, they take up the whole screen but it works. So I guess the big issue is that ClientsMainActivity is from some example I found online for doing recipes that shows you how to make multiple fragments to a screen. The thing that gets me all that FragmentActivity does is sets the content view, to a layout that does all the work it seems, so that's why I cannot figure out how I would code it to do the same thing but let me pass in values to the fragments the layout defines etc...
END EDIT
So I am using this nice little tutorial here:
http://developer.android.com/guide/topics/fundamentals/fragments.html
It has gotten me a long way and utilizing what they say to do for the main activity, and the fragment_layout.xml, I got a nice client list on the left (Thats a listfragment) and a details fragment on the right.
Then i added the ability to edit session information on a client (or edit client details) both of which were full screen fragments. This worked great.
Now I decided my Session edit ui would best be served splitting the information up into two panes again.
This is not working as I thought, like I said I have a main_Activity that does this in the onCreate:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_client_activity);
}
with the main_client_activity.xml being defined in two layouts but the one for landscape tablets is here:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment class="com.viciousbytes.studiotab.subactivities.ClientListView"
android:id="#+id/client_list" android:layout_weight="1"
android:layout_width="0px" android:layout_height="match_parent" />
<FrameLayout android:id="#+id/client_details" android:layout_weight="1"
android:layout_width="0px" android:layout_height="match_parent"
android:background="?android:attr/detailsElementBackground"/>
</LinearLayout>
This all works great! In which case I handled everything else as a full screen activity that started its own fragment:
EditSessionActivity
EditClientActiivyt both of which use getSupportFragmentManager().beginTransaction and I could pass information into it from the .newInstance call.
I had my session_edit.xml layout defined with buttons, textviews etc..and that was working great. Thats what i loaded in my SessionEdit fragment "launched" by my EditSessionActivity But now since I want to split it apart I ran into a snag. Above I defined a client_list and a client_details id, are these placeholders on my screen? do I reference those when I wanna replace whats there with totally different fragments?
or do i build another fragment layout called something like fragment_session_layout which defines something like:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment class="com.viciousbytes.studiotab.subactivities.SessionEdit"
android:id="#+id/session_edit" android:layout_weight="1"
android:layout_width="0px" android:layout_height="match_parent" />
<FrameLayout android:id="#+id/invoice_details" android:layout_weight="1"
android:layout_width="0px" android:layout_height="match_parent"
android:background="?android:attr/detailsElementBackground" />
</LinearLayout>
Sorry don't know what to title this on the tip of my tongue of what I am asking, basically how to get two panes of fragments twice over. THe demo online shows how to do one (and a simple ListFragment at that).
I have done all the above and I just cannot figure out how to pass into the fragment the data I need, I was using this in my EditSessionActivity:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int clientID = getIntent().getIntExtra(THE_SELECTED_CLIENT, -1);
int sessionID = getIntent().getIntExtra(SELECTED_SESSION,-1);
SessionEdit edits = SessionEdit.newInstance(this.getBaseContext(), false, clientID, sessionID);
mUIListener = (OnUpdateUI)edits;
getSupportFragmentManager().beginTransaction().add(android.R.id.content, edits).commit();
}
that worked, but to try to adhere to the earlier fragment example, i assumed my EditSessionActivity was sorta like making another MainActivity (cause it has two panels in it like the main one). so I recoded the onCreate in EditSessionActivity with this:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.edit_session_fullview);
}
Which after fighting in my EditSession fragment dealing with the onCreateView, I got this to finally work but couldn't pass in the data cause there is no instantiation of the object using like the previous onCreate which had the edits=SessionEdit.newInstance(...)
So is it good practice to be doing the other fragment layout that has two pains in it and starting that up when the right action is triggered. Or is one supposed to replace the two already created fragments?? from my main_client_activity.xml somehow?
I assume editing clients and editing sessions are two distinct activities. When you switch from "editing clients" to "editing sessions" mode, both the "list" and "details" panes would change?
I would go with two layout files, instead of trying to reuse the same layout and reload fragments in it.
If you tried to reuse the same layout, you would have to:
Change #+id/invoice_details to something like #+id/right_pane. Otherwise it would look confusing to load something related to sessions into "invoice_details" placeholder.
replace fragment definition with another FrameLayout and load either ClientListView or SessionListView (or however it's called) fragment there at runtime.
This would add more complexity than having another layout xml file in my opinion.
So
Take your existing code that works with client list and client details
Duplicate all involved parts, and change what needs to be changed so it's now session list and session details
Remove duplication where it's easy to do (common functions go to utility classes, common layout elements to layout includes). Leave the things that are hard to de-duplicate as-is.
Re-evaluate later, when you have more fragments, more layouts and more experience.
UPDATE, about two different approaches fragments can be embedded in activity
As the Android documentation states, there are two main ways you can get a fragment to show up in your activity:
declare the fragment in layout's XML file (<fragment class=.... />)
put a placeholder FrameLayout in layout's XML file and load fragment at runtime
First approach is fine when fragment doesn't need to receive any arguments. Like, for example, if the logic to retrieve single and only list of clients is hardcoded in fragment's code.
Second approach lets you pass arguments to the fragment, and therefore is appropriate for "details drilldown" type of fragments.
From updated question I understand that,
each client has a separate list of sessions
the components in play are: EditSessionActivity that hosts two fragments, one for displaying list of sessions, another for displaying session details
If that's correct, then indeed you'd need to load both fragments programmatically because both needs parameters to be passed to. So your layout would have two FrameLayouts. The EditSessionActivity would start with getting some parameters from intent ("which list of sessions are we working with?"), and load "list of sessions" fragment with these parameters. When user selects list item in that fragment, the other fragment would be loaded with session details.

Categories

Resources