I'm creating a data entry application, which is currently divided into three Activities - a login activity, a 'main' activity (user information, summaries, etc), and a data entry activity for a single record. Each of these activities has several different 'screens' (i.e. full-page layouts containing a set of views), and each of those screens is a single fragment.
I have two questions:
Is this an appropriate overall architecture? The division of activities/fragments is somewhat arbitrary but these partitions make semantic sense to me.
What is the best way to manage switching between fragments within an activity? My main activity uses a ViewPager and FragmentPagerAdapter since it is based on a tabs + swipe navigation structure, but all activities will have fragments which are not represented as menu items. Should I use a FragmentPagerAdapter without a ViewPager for this (and if so, how)? Should I be writing my own abstraction layer to handle the transitions?
I've read a number of tutorials and examples but haven't seen this particular pattern used. As I'm not very experienced in Android development, I thought it best to ask whether there's a ready-made solution before I try to write my own. Any advice is appreciated. Thanks!
EDIT: Here's what I have at the moment, as a very simplified version to switch between two 'screens' - a title page and a login page. This shows most of the things I want it to handle (holding fragment instances, managing transactions, placing them into the layout). Still seems like it's replicating the functionality of the PagerAdapter and ViewPager in some ways but I don't want it to be tied to menus, tabs, swiping, etc. since the primary navigation will be through buttons within the app. I'll also need to pass some initial data to the fragment initialization for more complex fragments.
public class LoginFragmentSwitcher {
private int mCurFragIndex;
private ArrayList<Fragment> mFragList;
public LoginFragmentSwitcher() {
//set initial index
mCurFragIndex = 0;
//create fragments
mFragList = new ArrayList<Fragment>();
mFragList.add(new TitleFragment());
mFragList.add(new LoginFragment());
//TODO: more fragments will be added here
//display the first fragment
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.fragment_container, mFragList.get(0));
ft.commit();
}
// Perform a fragment transition to the specified index
public void showFragment(int newFragIndex) {
//only switch if you're not already showing the appropriate fragment
if (newFragIndex != mCurFragIndex) {
//start the fragment transaction
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
//TODO: apply transition style if desired
//switch content to the new fragment
ft.replace(R.id.fragment_container, mFragList.get(newFragIndex));
//register entry in the back stack and complete transaction
ft.addToBackStack(null);
ft.commit();
//update index
mCurFragIndex = newFragIndex;
}
}
}
Related
Fragments is something that I am still trying to understand, I get some of it but not all of it.
My question is, do i need a container to start a new fragment instance?
This is what I have been currently doing to launch a fragment from my current activity that i have a container in.
FragmentManager fm = getActivity().getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(container.getId(), new OtherFragment());
ft.commit();
So my main activity has a container where I can switch from 4 fragments. Now lets say I click on one of the list items in my 3rd tab. That launches a new activity that shows another listview. Then if i click on the item on that listview, i launch a new activity. Then, were it says "tap for more information", I will be launching a new activity (I haven't created this yet, and that is why I am asking this).
But I feel like it could just be launching fragments instead of activities. If so, how do I go about doing that, because I feel like I need some type of container to put it in since I have tried launching a newinstance of a dummy fragment class i created it but it doesn't launch. If not, how do i just create a new instance of it without a container, if possible.
Or do I only use fragments whenever they will be similar and will have a container to be put in??
And I could do fragmentActivity, but that is almost the same as Activity. The reason I ask is because we shouldn't have so many activities, right? or does having as many as activities as you want not affect the project performance? Because right now I usually create activities for everything, unless its like the first picture where I will have something similar that can be put into a container.
Thanks.
You're going to to pretty much the same thing that you did to show the first fragment.
FragmentManager fm = getActivity().getFragmentManager();
if (mDetailFragment == null)
{
mDetailFragment = new DetailFragment();
}
FragmentTransaction ft = fm.beginTransaction();
ft.replace(container.getId(), mDetailFragment);
ft.commit();
You're going to want to keep references to your fragments so you're not creating them new every time. To improve the user experience you can add animations to the transition and, if it makes sense, add the fragment to the backstack.
ft.addToBackstack("OtherFragment");
ft.setCustomAnimations(R.anim.enter, R.anim.exit, R.anim.popEnter, R.anim.popExit);
So I have enabled the setting to destroy actvities when you navigate away from an activity
Settings=>Developer Options=>Don't Keep activites
This should basically replicate an activity or fragment getting garbaged collected and then I have to restore the data via the bundle savedinstancestate.
So I understand how that works. But it seems when I navigate from fragment 1 to fragment 2 and then put the application in the background and then in the foreground(destroying the activity)
Both fragment 1 and fragment 2 show at the same time. In which only fragment 2 should be showing.
I do not know if this is something standard that I have to manage hiding and showing fragments onsavedinstance. Or if something in my code is breaking things. Below is how I push fragments which I hope is helpful:
public void pushFragmentWithAnimation(FragmentManager fm, int parentId, Fragment currentFrag, Fragment newFrag, int animEntry, int animExit) {
hideSoftKeyboard(currentFrag.getActivity());
FragmentTransaction ft = fm.beginTransaction();
// See: http://developer.android.com/reference/android/app/FragmentTransaction.html#setCustomAnimations(int, int, int, int)
ft.setCustomAnimations(animEntry, animExit, animEntry, animExit);
ft.add(parentId, newFrag, String.format("Entry%d", fm.getBackStackEntryCount())).hide(currentFrag).show(newFrag);
ft.addToBackStack(null);
ft.commit();
}
Fragment 1 is still in the backstack because when I press back I only see fragment 1. Let me know if you know why this is happening.
The lifecycle of XML added Fragments and programmatically added Fragments differ enough to make mixing them a bad idea, as explained in detail here.
The easiest way around this is to make all fragments programmatically added by replacing your XML inflated Fragment with a FrameLayout of the same ID, then in your onCreate add
FragmentManager fragMgr = getSupportFragmentManager();
if (null == fragMgr.findFragmentByTag(FRAG_TAG))
{
fragMgr.beginTransaction().
add(R.id.fragment, new Fragment1(), FRAG_TAG).commit();
}
Where FRAG_TAG is any unique string. This ensures that Fragment1 is only created if it is not already in the layout.
I am not entirely sure why this solution works. I assume its related to if the activity gets killed that it does not keep track of which fragment is currently shown and shows all of the fragments. So basically I needed to replace:
ft.add(parentId, newFrag, String.format("Entry%d", fm.getBackStackEntryCount())).hide(currentFrag).show(newFrag);
with
ft.replace(parentId, newFrag, tag);
Then when I create the initial fragment in the main activity. I only would do that when
if(savedInstanceState==null){
My updated code is below: https://github.com/CorradoDev/FragmentTest/tree/2c53f9f42e835da768f61b0233f3ab5b3adf2448
Here is my use case:
I need to create 3 tabs using ActionBar Navigation Tabs, and I am using ActionBarSherlock to accomplish this. Each of the 3 tabs is it's own Fragment. However, there is some common information that is shown in each of the tabs (in my case, product title, description). I have created another Fragment for this common information, and am referencing this Fragment in each of the main Fragment layouts, like this.
Here is my problem:
I want to reuse the Fragment instance that retrieves and displays the common info. I am using the code below, but it always seems to create a new instance of the common fragment in each of the main fragments.
FragmentManager fm = getFragmentManager();
f = (ProductDetailsInfoFragment) fm.findFragmentByTag("prodinfo");
if (f == null) {
Log.d(TAG, "fragment not found...creating new instance");
f = new ProductDetailsInfoFragment();
f.setTargetFragment(this, 0);
fm.beginTransaction().replace(R.id.prod_info_fragment, f, "prodinfo").commit();
}
You can share fragments if you want to. You will need to implement ActionBar.TabListener and in your onTabSelected just pick what fragment you want to use.
You could do something like this: https://gist.github.com/anonymous/5415274
A better option is to store the data that is needed by both of these fragments in a separate object that you can share between them. This will allow you to test the retrieval without having a UI attached to it if you wish. This also allows the two fragments to diverge as they need to, making them a single purpose thing vs. having to keep all the code needed for both actions in a single fragment.
I'm building an app that the interface is based on this http://code.google.com/p/android-playground/, and I need to have a fragment inside of each tab (that are fragments). The tabs are all inflated from the same xml, and in that xml I have a fragment tag.
The problem is that when the activity is created, as the id of each fragment in the tabs are equal, the contents that should go to the second tab, go in the first.
I'm using this code to replace the fragment in the tab
FragmentTransaction ft = x.getSupportFragmentManager().beginTransaction();
ft.replace(R.id.details, fragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
I don't know if is possible to correct this.
do you want all the tabs to have the exactly the same content?
Usually when putting fragments in tabs each tab would be showing differnt content, so a xml file per tab would not be uncommon. You can have a seperate xml layout for each tab that just declares your fragment with a different id each time. Without declaring a seperate (read unique) id for each fragment there is no efficent / simple operation i know to get a handle to a specific fragment (as the id is the unique handle).
You also may be able to use a FragmentPagerAdapter depending on your needs. You could then fade your current tab fragment out, then call public void notifyDataSetChanged () and provide a new fragment. This is not really the standard way of doing it though and will not be preserved on the back stack.
Optionally you could create each tab programatically in the PagerAdapter and set a tag for each fragment when calling FragmentTransaction.add(..) and then use this tab-unique tag in future fragment transactions Ignore this, it does not look like you can switch fragements with a tag, id only im afraid. Go with my first suggestion I would!
I've struggled with ths as well and the way I reference a Fragment from its Activity later on is by accessing the Adapter for the tabs.
Assuming you use an Adapter, you could do something like this
private mTabsAdapter;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// etc
this.mTabsAdapter = new TabsAdapter(this, this.mViewPager);
// Tab1
Tab tab = actionBar.newTab();
this.mTabsAdapter.addTab(tab, MyFragment1.class, null);
// Tab2
tab = actionBar.newTab();
this.mTabsAdapter.addTab(tab, MyFragment2.class, null);
}
public MyFragment1 getTab1() {
return (MyFragment1)this.mTabsAdapter.getItem(0);
}
public MyFragment2 getTab2() {
return (MyFragment2)this.mTabsAdapter.getItem(1);
}
Then inside your fragment, you would have a method to access
public class MyFragment1 {
// ...
public void reloadData() {
// reload data here
}
}
Now you can access the Fragment such as
this.getTab1.reloadData();
This feels like a sad way to access a fragment, but you can't rely on a Tag in every scenario. Also, you must take care to only reference this where the reference to the Fragment exists.
There may be times that this will be null. For example if you nave a bunch of tabs, they may become garbage collected at some point. you should communicate between Fragments via a callback. This example is for only a few scenarios.
The callback method is described at Communicating with the Activity and I'd suggest this method if it fits your application. This will prevent the need for directly accessing tabs in most cases.
This is a design question, rather than a technical one.
General case: I want an UI event in a Fragment to make Activity-wide changes.
Specific case: I have two fragments, hosted in the same activity. When the user clicks a button in one of those fragments, I want it to be replaced by the other.
I don't want, however, my Fragments touching my activity. I may want to change the behavior later (maybe, in a bigger screen, show both fragments instead of replacing the first), and I don't want my Fragment code to have that logic.
What I did was implement a Listener class in my fragments, that reports events back to the Activity. This way, if I want to use another Activity class with different display behavior, I can just change the listener and leave the Fragment code untouched.
Is this a good way to go about it? Is there a standard good practice, or a better design pattern?
Using listeners is the recommended way of communicating between Fragment and your activity.
See this Android documentatin section for infromation. Long story short they just implement a listener interface by the Activity class and cast getActivity() result in a fragment to a listener.
From my personal experience this is very convenient because lets you to:
Easilly switch underlying activity (e.g. you host entire fragment in a wrapper activity for compatibility in pre-3.0 and host this fragment along with others in 11+)
Easilly control if the wrapper activity supports callbacks or not. Just check is it does implement the listener and do your app specific actions if it doesn't.
You are right on about using a Listener. This is something I also had to deal with in a project at work. The best way to handle it is to make the Fragment stand-alone in nature. Anything wishing to interact with the Fragment should use its public API and/or set listeners for specific events. If you are familiar with Design Patterns, this is the Observer pattern. The events can be general or specific as well as contain data or no data.
As an example of my project, I had two Fragments. A ListFragment and an InfoFragment that displayed the selected ListItem. The ListFragment already has a Listener interface for my Activity to hook into, but the InfoFragment does not since its your basic Fragment. I added a Listener interface to the InfoFragment that would be notified when the Fragment wanted to close. For the Fragment, this could be by a button press, or specific action occured, but as far as my Activity is concerned, when the Event is triggered, it would close up the Fragment view.
Don't be afraid to use a lot of Listeners for Fragments, but also try to group them by a specific action using data parameters to individualize them. Hope this helps!
A technical answer for:
I have two fragments, hosted in the same activity. When the user clicks a button in one of those fragments, I want it to be replaced by the other.
FragmentTransaction ft = this.getFragmentManager().beginTransaction();
Fragment mFragment = Fragment.instantiate(this.Activity(), Fragment2.class.getName());
ft.replace(android.R.id.content, mFragment);
ft.commit();
public class Example_3_Mainfile extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.example_3_mainfile);
Fragment fr ;//make class that extend to thefragment
fr = new Act_2_1();
FragmentManager fm = getFragmentManager();
FragmentTransaction fragmentTransaction = fm.beginTransaction();
fragmentTransaction.replace(R.id.fragment_place, fr);
//id get of fragment tag from xml file there decelar
fragmentTransaction.commit();
}
}