My app has one MainActivity with three tabs (A, B, C).
Tab A shows FragmentA1. When I click a list entry in this fragment then FragmentA2 is shown (still in tab A). The same applies to the other tabs, some hierarchies go even deeper (FragmentC4).
All the switching and replacing of all the fragments is handled in MainActivity by Listeners. (Edit: I don't define my fragment in XML layouts but in the code only).
My Question is:
Should I hold references to all fragments in MainActivity or should I create them new everytime I need them?
What are the (dis)advantages? Can I reuse fragments by using Alternative 1, instead of recreating them everytime?
Alternative 1:
class MainActivity
private Fragment fgmtA1;
private Fragment fgmtA2;
private Fragment fgmtA3;
...
public onClickItemInA1(int itemId) {
fgmtA2 = new FragmentA2();
// put args
// replace
}
...
}
Alternative 2:
class MainActivity
...
public onClickItemInA1(int itemId) {
FragmentA2 fgmtA2 = new FragmentA2();
// put args
// replace
}
...
}
Alternative 3:
Maybe the best solution is a completely different approach?
Should I hold references to all fragments in MainActivity or should I
create them new everytime I need them?
It depends...
The only two reasons which i can think of are performance and keeping the state of a Fragment.
If you always create a new Fragment the GC will have a lot to do, which could cause some performance issues if you use a lot of bitmaps or huge data. You can reuse a Fragment by holding a reference to it in the Activity or getting the Fragment by tag or id using the methods FragmentManager.findFragmentByTag(String) or FragmentManager.findFragmentById(int). With them you can reuse already created Fragments, which should be done by default.
Furthermore if your Fragments hold some data, you will lose them or you cache it somewhere else to reacreate it if the Fragment is destroyed. While reusing a Fragment you can use onSavedInstanceState() to reacreate your state.
Hence, yes you should reuse a Fragment because it could cause system performance or headaches using anti-patterns to save some data.
Related
I have a headless fragment to retain my data during config changes.
rFrag = new RetainedFragment();
getSupportFragmentManager()
.beginTransaction()
.add(rFrag, MODEL)
.commit();
What is the best way to access this fragment while inside of another activity or fragment besides the original activity that the headless fragment is attached to?
Using this doesn't work:
RetainedFragment rFrag = (RetainedFragment)getSupportFragmentManager
.findFragmentByTag(model);
I did a search and I believe this is because I don't have the retained fragment added onto the backstack, but adding a headless fragment onto the backstack isn't what I want to do.
Right now I just set the retained fragment to be public and static like this:
public static RetainedFragment rFrag;
But I feel like this isn't good practice to use static variables like that.
First of all, I don't know what you mean by "headless fragment".
Secondly, I don't know what RetainedFragment() class is but I will assume that it is just a class you created that extends Fragment.
Thirdly, you can't access a Fragment through other Activity. Every fragment is attached to one Activity and when that Activity is not visible its Fragments are not accessible.
Lastly, even if you force to access by using static methods and fields and such, you are right, it is not a good practice. You can, and should, use Intent extras and Fragment arguments to pass data from one to another. You mentioned you need to retain some data, so you actually don't really need the whole Fragment, right? You can just save, load and pass around the data you need.
In the latest versions of eclipse (ADT v22.6.2) the create android application now generates an activity_main.xml and a fragment_main.xml. It generates only a single activity class
but this now has an embedded inner static fragment class that is created by the activity in its onCreate method
#Override
protected void onCreate(Bundle savedInstanceState) {
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment()).commit();
}
....
public static class PlaceholderFragment extends Fragment
My confusion is how to port old code/examples where there was only 1 activity and the main application
logic is usually put in the Activity onCreate method i.e. stuff like findViewById. Listeners
etc
The way I have approached it is that I put all my user created views as members of the static PlaceHolderFragment class. Then I call this in the fragment onCreateView. I still have some logic in the activity and it stores a pointer to the fragment. It updates the views members by calling getters on the fragment. Is this correct or should all logic be moved to the fragment now ?
All the tutorials/examples use the old approach where logic is placed in the activity
so there is no reference documentation for how to use the new files that Eclipse generates for an Android application. Any help appreciated ?
Don't worry about the files that Eclipse generate automatically: You can do whatever that you want!!!
Fragment is a element between an Activity and a container.That's mean that you can put the logic of your code inside of one fragment with not problems.
In theory, fragments are used when you want to manage screens using different modules, containers. It's a fragment, a module, part of one screen (but also can be used in a full screen looking as an activity and with the same behaviour than one activity.) For example, imagine that you have for mobile phone screens, one list of news in one screen, and when you click, your app go to the next screen for show the content of the news, right? Ok, so you can use for these 2 screens: 2 activities for each one or one parent activity with 2 fragments...whatever that you want...
Imagine the same case, for a tablet version of your app, now the left part of the screen you should show the list of news and in the right part of the screen, the contain of each news clicked, right? In that case, would be completly necessary to use one activity parent with two fragments...in that case, we could reuse almost the same case for the mobile phones or tablet.
And now, focus in your question: if you don't want complicate the life (not too much, because work with fragment is easy too) I will recomend you to use only activities for your app.
But, like your question, you want to port, there isn't any problem. Now imagine that your activity is going to be only the class where you are going to manage the fragments. The logic for each screen has to be in each fragment. Use the activity only for replace fragments, or share information between fragments, etc. The activity will be like the orchestra director.
From each fragment, you can access to methods or public variables of your activity using ((NameOfActivity)getActivity()).
Is it clear for you?
One more stuff, in fragment, normally the method that we used for initialize stuffs is onCreateView (and not onCreate like activities).
I have 2 Tabs and 2 Corresponding Fragments. On calling the LAUNCH Activity both Tabs were added and then the first one added will be shown. Thus the first Fragments onCreateView is called the second Fragments ones not.
In my case this is an issue because the first Fragment has to call methods on the second Fragment. Inside the second Fragment there is an Objectreference which will be set by calling the onCreateView in the second Fragment.
Therefore I used following code snippet to solve this
actionBar.setSelectedNavigationItem(1);
actionBar.setSelectedNavigationItem(0);
It works but in my opinion there must be another possibility to solve this issue. Like calling the onCreateView of the second Fragment?
Here is the relevant code snippet. The listener is implemented as in android-dev Sample only with small changes not affecting my issue.
simplexFragment corresponds to the first Fragment
graphicFragment corresponds to the second Fragment
// adds two tabs
actionBar.addTab(actionBar.newTab().setText("Input").setTabListener(new TabListener(null, "input_fragment")));
graphicFragment = new GraphicFragment();
actionBar.addTab(actionBar.newTab().setText("Graphic").setTabListener(new TabListener(graphicFragment, "graphic_fragment")));
simplexFragment.setGraphics(graphicFragment); // sets the internal reference!
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
// selects the Tab so the GraphicFragments onCreateView will be called
actionBar.setSelectedNavigationItem(1);
actionBar.setSelectedNavigationItem(0);
Thanks for support.
In my case this is an issue because the first Fragment has to call methods on the second Fragment.
This is not how Fragments are meant to work. The idea is a Fragment should be self-contained and re-usable and one Fragment shouldn't know that another exists (or at least shouldn't rely the existence any other Fragment).
For example, suppose you have 3 Fragments and ActivityA uses FragmentA and FragmentB but you have another Activity (ActivityB) which uses FragmentA and FragmentC. In that case, FragmentA doesn't know what the other Fragment is (B or C) and shouldn't even expect there to be another Fragment at all.
The way to achieve what you want is to use callback methods on the Activity and have it perform the actions on any other Fragments (if they exist). It can do this by either calling public methods on the other Fragments or by passing data in the 'arguments' Bundle when it creates the other Fragments.
I initially create my fragments inside the Activity onCreate(). Than I go about creating the ViewPager and setting up the adapter. I keep a global reference to the fragments so I can update them as needed. Also, these fragments are accessed by the adapter.
My issue is that I notice once the screen is rotated the Fragments are recreated but the ViewPager still contains the original fragments created...??
How am I supposed to handle the life-cycle of my fragment? I need to be able to call directly to the fragment from the activity. Is a singleton for a fragment a good idea? Or just a memory leaker?
protected void onCreate (Bundle savedInstanceState)
{
...
...
// set up cards
mFrag1 = new Frag1();
mFrag1.setOnActionEventListener(mOnActionEvents);
mFrag2 = new Frag2();
mFrag3 = new Frag3();
mFragPager = (ViewPager) findViewById(R.id.vpPager);
mFragAdapter = new FragAdapter(getSupportFragmentManager());
mFragPager.setAdapter(mCardAdapter);
mFragPager.setOnPageChangeListener(mOnCardChange);
}
Global instances and static fragments are definitely a bad idea. Unless you call setRetainInstance() on a fragment, it will be serialized to a Bundle and recreated when an the parent activity is re-created (on screen rotate, etc.). That will, of course, produce new fragment instances, and your old references will be invalid. You should get references to fragments via FragmentManager.findFragmentById/Tag() as needed and definitely not store those in static variables.
You may need to show more of our code, but as long as you create the fragments in onCreate() the references should be valid for the lifetime of the activity. Check the compatibility library sample code for more on using fragments with ViewPager.
Consider the sample app from developers.android.com
This describes using Fragments like so:
On a Phone you can use Fragment 1 on Activity A and fragment 2 on Activity B.
On a tablet you have more real estate so you use Fragment 1 and Fragment 2 on Activity A.
Great! ... But... On the first example (the one with a phone) you create an Activity with an xml file containing a single <fragment> and that's all, in the activity you only call setContentView() on that xml? That seems like a lot of redundant code (Activity, XML & Fragment to display a Fragment): Can you set a Fragment as an Activity or is a Wrapper with XML always required?
Ah, found it here
public class MainMenuHolder extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// If not already added to the Fragment manager add it. If you don't do this a new Fragment will be added every time this method is called (Such as on orientation change)
if(savedInstanceState == null)
getSupportFragmentManager().beginTransaction().add(android.R.id.content, new MainMenuFragment()).commit();
}
}
FragmentActivity allow's you to set the Fragment as the content of android.R.id.content which I assume is the android internal ID of the trunk view.
With this method you still end up with an mostly redundant activity (If all you want is the Fragment acting as the Activity). But still, half as much fluff as having an activity and an XML file acting as a container.
Any other answers would be appreciated!
The online example doesn't fill in all the blanks. I'll try to answer your questions directly:
"On the first example (the one with a phone) should you create an Activity with an xml file containing a single and an activity which only calls setContentView() on that xml and that's all?"
You've started in the right place. But there's more to it than that. There's always more than one way to solve a problem in Android but a recommended way of generating the effect of having a dynamic number of fragments based on avail. real-estate is:
Create layout XML files in /layout for the primary (default) targeted orientation/device/form-factor/SDK
Create layout XML files for the smallest-width baseline for other targeted devices. You may also want to target other orientations, SDKs, etc.
Each layout XML file will have it's own set of defined fragments
In the Activity, check to see which fragments are present.
Clearly an analogous strategy can be adopted for programmatic layouts.
In your example in the original question (from Google's docs) you could have:
layout/main.xml :: this layout would only have Fragment 1
layout-sw600dp/main.xml :: this layout would have Fragments 1, 2
Then in MainActivity.java you would check for the existence of each fragment. To do that you could use FragmentManager#findFragmentById() to have a check like: if findFragmentById() returns null for Fragment-2 then MainActivity knows the device has loaded layout/main.xml and only supports one fragment.
Stepping 'back' from the example somewhat reveals that: prior to using Fragments you might have called Activity B from Activity A with startAcitityForResult(int). In the Fragment paradigm you probably only need to have a result from Fragment 2 cause something to happen in Fragment 1, so it's reasonable to have MainActivity be the gatekeeper for that. As you expand on the example you may see that in other apps, MainActivity may need to call other activities - for whatever reason. Perhaps you're targeting a large tablet with enough real estate for 3 fragments but on a handset that needs to be 3 activites. Things can get interesting but the Fragment API is fairly powerful.
"Can you set a Fragment as an Activity or is a Wrapper always required when using fragments?"
A Fragment is not an Activity. Indeed Fragments are loaded by Activities, so yes one might say a wrapper is always required. You're touching on aother subtle aspect of Fragments. Whereas Activities behave like MVC Controllers, Fragments could be called "mini-controllers" due to their lifecycle which both resembles and executes alongside an Activity. Again, the Fragment's lifecycle is contained inside ("wrapped by") the lifecycle of the Activity managing the Fragment. I recommend becoming familiar with the Fragment lifecycle documented at http://developer.android.com/guide/topics/fundamentals/fragments.html#Lifecycle.
More generically you could create a fragment container class:
public class SingleFragmentActivity extends Activity {
public static final String FRAGMENT_NAME = "fragmentName";
public static final String FRAGMENT_ARGUMENTS = "fragmentArguments";
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String fragmentName = getIntent().getStringExtra(FRAGMENT_NAME);
Fragment fragment = Fragment.instantiate(this, fragmentName);
Bundle fragmentArguments = getIntent().getBundleExtra(FRAGMENT_ARGUMENTS);
fragment.setArguments(fragmentArguments);
getSupportFragmentManager().beginTransaction().replace(android.R.id.content,fragment, "tag").commit();
}
}
now you use this class to instantiate any fragment as a standalone activity:
public void showFragmentAsActivity() {
Intent intent = new Intent(this, SingleFragmentActivity.class);
intent.putExtra(SingleFragmentActivity.FRAGMENT_NAME, MyFragment.class.getName());
intent.putExtra(SingleFragmentActivity.FRAGMENT_ARGUMENTS,MyFragment.getArgumentsBundle("a string argument"));
startActivity(intent);
}