I don't understand the Fragment lifecycle in Android, and what happens during screen orientation changes.
I started with the Master-Detail example in the Android SDK, and I have added the following lines of code:
in MyItemListActivity I modified onCreate()
public class MyItemListActivity extends FragmentActivity implements
MyItemListFragment.Callbacks {
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
*/
private boolean mTwoPane;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FragmentManager fragMgr = getSupportFragmentManager();
MyItemListFragment oldFragment = (MyItemListFragment)fragMgr.findFragmentByTag("booFragment");
if (null == oldFragment) {
FragmentTransaction xact = fragMgr.beginTransaction();
MyItemListFragment newFragment = MyItemListFragment.createInstance("boo");
xact.add(
R.id.myitem_list,
newFragment,
"booFragment");
xact.commit();
}
setContentView(R.layout.activity_myitem_list);
if (findViewById(R.id.myitem_detail_container) != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-large and
// res/values-sw600dp). If this view is present, then the
// activity should be in two-pane mode.
mTwoPane = true;
// In two-pane mode, list items should be given the
// 'activated' state when touched.
((MyItemListFragment) getSupportFragmentManager().findFragmentById(
R.id.myitem_list)).setActivateOnItemClick(true);
}
// TODO: If exposing deep links into your app, handle intents here.
}
/**
* Callback method from {#link MyItemListFragment.Callbacks} indicating that
* the item with the given ID was selected.
*/
#Override
public void onItemSelected(String id) {
if (mTwoPane) {
// In two-pane mode, show the detail view in this activity by
// adding or replacing the detail fragment using a
// fragment transaction.
Bundle arguments = new Bundle();
arguments.putString(MyItemDetailFragment.ARG_ITEM_ID, id);
MyItemDetailFragment fragment = new MyItemDetailFragment();
fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction()
.replace(R.id.myitem_detail_container, fragment).commit();
} else {
// In single-pane mode, simply start the detail activity
// for the selected item ID.
Intent detailIntent = new Intent(this, MyItemDetailActivity.class);
detailIntent.putExtra(MyItemDetailFragment.ARG_ITEM_ID, id);
startActivity(detailIntent);
}
}
}
in MyItemListFragment I created createInstance()
public class MyItemListFragment extends ListFragment {
/**
* The serialization (saved instance state) Bundle key representing the
* activated item position. Only used on tablets.
*/
private static final String STATE_ACTIVATED_POSITION = "activated_position";
/**
* The fragment's current callback object, which is notified of list item
* clicks.
*/
private Callbacks mCallbacks = sDummyCallbacks;
/**
* The current activated item position. Only used on tablets.
*/
private int mActivatedPosition = ListView.INVALID_POSITION;
/**
* A callback interface that all activities containing this fragment must
* implement. This mechanism allows activities to be notified of item
* selections.
*/
public interface Callbacks {
/**
* Callback for when an item has been selected.
*/
public void onItemSelected(String id);
}
/**
* A dummy implementation of the {#link Callbacks} interface that does
* nothing. Used only when this fragment is not attached to an activity.
*/
private static Callbacks sDummyCallbacks = new Callbacks() {
#Override
public void onItemSelected(String id) {
}
};
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public MyItemListFragment() {
}
public static MyItemListFragment createInstance(String boo) {
Bundle init = new Bundle();
init.putString(
"booboo",
boo);
MyItemListFragment frag = new MyItemListFragment();
frag.setArguments(init);
return frag;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// TODO: replace with a real list adapter.
setListAdapter(new ArrayAdapter<DummyContent.DummyItem>(getActivity(),
android.R.layout.simple_list_item_activated_1,
android.R.id.text1, DummyContent.ITEMS));
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Restore the previously serialized activated item position.
if (savedInstanceState != null
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
setActivatedPosition(savedInstanceState
.getInt(STATE_ACTIVATED_POSITION));
}
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// Activities containing this fragment must implement its callbacks.
if (!(activity instanceof Callbacks)) {
throw new IllegalStateException(
"Activity must implement fragment's callbacks.");
}
mCallbacks = (Callbacks) activity;
}
#Override
public void onDetach() {
super.onDetach();
// Reset the active callbacks interface to the dummy implementation.
mCallbacks = sDummyCallbacks;
}
#Override
public void onListItemClick(ListView listView, View view, int position,
long id) {
super.onListItemClick(listView, view, position, id);
// Notify the active callbacks interface (the activity, if the
// fragment is attached to one) that an item has been selected.
mCallbacks.onItemSelected(DummyContent.ITEMS.get(position).id);
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mActivatedPosition != ListView.INVALID_POSITION) {
// Serialize and persist the activated item position.
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
}
}
/**
* Turns on activate-on-click mode. When this mode is on, list items will be
* given the 'activated' state when touched.
*/
public void setActivateOnItemClick(boolean activateOnItemClick) {
// When setting CHOICE_MODE_SINGLE, ListView will automatically
// give items the 'activated' state when touched.
getListView().setChoiceMode(
activateOnItemClick ? ListView.CHOICE_MODE_SINGLE
: ListView.CHOICE_MODE_NONE);
}
private void setActivatedPosition(int position) {
if (position == ListView.INVALID_POSITION) {
getListView().setItemChecked(mActivatedPosition, false);
} else {
getListView().setItemChecked(position, true);
}
mActivatedPosition = position;
}
}
The app runs fine when started, but when I rotate the screen the app crashes and the following is in the logs:
12-12 13:41:23.930: E/AndroidRuntime(31051): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.mymasterdetail/com.example.mymasterdetail.MyItemListActivity}: android.view.InflateException: Binary XML file line #24: Error inflating class fragment
...
12-12 13:41:23.930: E/AndroidRuntime(31051): Caused by: android.view.InflateException: Binary XML file line #24: Error inflating class fragment
12-12 13:41:23.930: E/AndroidRuntime(31051): at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:713)
...
12-12 13:41:23.930: E/AndroidRuntime(31051): at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:290)
12-12 13:41:23.930: E/AndroidRuntime(31051): at android.app.Activity.setContentView(Activity.java:1928)
12-12 13:41:23.930: E/AndroidRuntime(31051): at com.example.mymasterdetail.MyItemListActivity.onCreate(MyItemListActivity.java:52)
...
(Line 52 is the call to setContentView() in the Activity.)
If I remove xact.add() then the app runs just fine. (But no data is passed to the Fragment.)
I know that the FragmentTransaction approach is correct to pass data to my Fragment, but I don't see what else I need to do to prepare the Fragment to handle the lifecycle events associated with changes in screen orientation, and I don't know how to inflate the Fragment (either implicitly or explicitly.)
(I'm using a tablet, so I have the twoPane display, in case that makes a difference.)
Try moving your setContentView() to BEFORE you start instantiating your fragments.
If you setup your fragments first and try to attach them to views that don't yet exist then errors are likely.
Also ensure that the view you attach your fragments to exists in both your portrait and landscape layouts.
As you are adding your fragments programmatically, ensure you do not add them in your XML layouts as well. You should only have a FrameLayout in your layout to attach your fragment to.
What happens during an orientation change is that the current activity is destroyed and recreated - this is useful, because alternate layouts can be inflated (if you have another one for a different orientation in your resources folder).
Likely what's happening here is that you're trying to access a fragment before it's been inflated (as Kuffs said).
Your fragment needs a public empty constructor.
From http://developer.android.com/reference/android/app/Fragment.html:
All subclasses of Fragment must include a public empty constructor. The framework will often re-instantiate a fragment class when needed, in particular during state restore, and needs to be able to find this constructor to instantiate it. If the empty constructor is not available, a runtime exception will occur in some cases during state restore.
Related
Short and simple version: I am trying to achieve a backstack just like the app Instagram.
How their navigation work
In instagram they use navigation from a bottombar. Without knowing how the code looks like. They create some kind of 4 lane backstack.
So lets say i click on Home (lane 1). I then manage to click on a post -> click on a user -> click on another post -> and lastly click on a hashtag
I have then added 4 pages to the backstack for that lane.
I then do something similar to another lane (lets say the account page).
I know have 2 lanes with 4 pages in both. If i would hit back button at this point. I would just traverse back at the same pages as i opened them.
But if i instead clicked back to Home (from the bottom navigation) and clicked backbutton from there. I would traverse back from lane 1 instead of lane 2.
The big question
How can i achieve this lane like backstacking? is there a simple way im not thinking of?
What i managed to do so far
I have not managed to do a lot. I created a test project where i experiment with this type of navigation. What i managed to do so far is to create one massive backstack of all my bottombar navigation pages
My guess on how i would achieve this kind of feature is to move some parts of the backstack to the top and move the other back. But How is that possible?
I managed to fix the issue i was having. Dont give me full credit. I had a reference code that i looked at. Please check below.
This is maybe not a solid solution. But it may help you understand the logic behind how it works. So you can create your own solution that fits your needs.
Hope this helps :)
Background how my app works
To start with. I just want to let you all know how my app works. So you know why i chose my route of implementing this navigation. I have an activity that keeps the references to all the root fragments that it is using. The activity only adds one root fragment at a time. Depending on what button the user clicks.
When the activity creates a new root fragment. It will push it to the managing class that handles it.
The activity itself overrides onBackPressed() and calls the made up backstacks onBackpressed() function.
The activity will also pass a listener to the managing class. This listener will tell the activity when to shut down the app. And also when to refresh the current active fragment
BackStackManager.java
This is the managing class. It keeps a reference to all the different backstacks. It also delegates any fragment transactional duties to its FragmentManager class.
public class BackStackManager {
//region Members
/** Reference to the made up backstack */
private final LinkedList<BackStack> mBackStacks;
/** Reference to listener */
private final BackStackHelperListener mListener;
/** Reference to internal fragment manager */
private final BackStackFragmentManager mFragmentManager;
//endregion
//region Constructors
public BackStackManager(#NonNull final BackStackHelperListener listener,
#NonNull final FragmentManager fragmentManager) {
mBackStacks = new LinkedList<>();
mListener = listener;
mFragmentManager = new BackStackFragmentManager(fragmentManager);
}
//endregion
//region Methods
/** When adding a new root fragment
* IMPORTANT: Activity his holding the reference to the root. */
public void addRootFragment(#NonNull final Fragment fragment,
final int layoutId) {
if (!isAdded(fragment)) {
addRoot(fragment, layoutId);
}
else if (isAdded(fragment) && isCurrent(fragment)) {
refreshCurrentRoot();
}
else {
switchRoot(fragment);
mFragmentManager.switchFragment(fragment);
}
}
/** When activity is calling onBackPressed */
public void onBackPressed() {
final BackStack current = mBackStacks.peekLast();
final String uuid = current.pop();
if (uuid == null) {
removeRoot(current);
}
else {
mFragmentManager.popBackStack(uuid);
}
}
/** Adding child fragment */
public void addChildFragment(#NonNull final Fragment fragment,
final int layoutId) {
final String uuid = UUID.randomUUID().toString();
final BackStack backStack = mBackStacks.peekLast();
backStack.push(uuid);
mFragmentManager.addChildFragment(fragment, layoutId, uuid);
}
/** Remove root */
private void removeRoot(#NonNull final BackStack backStack) {
mBackStacks.remove(backStack);
//After removing. Call close app listener if the backstack is empty
if (mBackStacks.isEmpty()) {
mListener.closeApp();
}
//Change root since the old one is out
else {
BackStack newRoot = mBackStacks.peekLast();
mFragmentManager.switchFragment(newRoot.mRootFragment);
}
}
/** Adding root fragment */
private void addRoot(#NonNull final Fragment fragment, final int layoutId) {
mFragmentManager.addFragment(fragment, layoutId);
//Create a new backstack and add it to the list
final BackStack backStack = new BackStack(fragment);
mBackStacks.offerLast(backStack);
}
/** Switch root internally in the made up backstack */
private void switchRoot(#NonNull final Fragment fragment) {
for (int i = 0; i < mBackStacks.size(); i++) {
BackStack backStack = mBackStacks.get(i);
if (backStack.mRootFragment == fragment) {
mBackStacks.remove(i);
mBackStacks.offerLast(backStack);
break;
}
}
}
/** Let listener know to call refresh */
private void refreshCurrentRoot() {
mListener.refresh();
}
/** Convenience method */
private boolean isAdded(#NonNull final Fragment fragment) {
for (BackStack backStack : mBackStacks) {
if (backStack.mRootFragment == fragment) {
return true;
}
}
return false;
}
/** Convenience method */
private boolean isCurrent(#NonNull final Fragment fragment) {
final BackStack backStack = mBackStacks.peekLast();
return backStack.mRootFragment == fragment;
}
//endregion
}
BackStackFragmentManager.java
This class handles all fragment transactions. such as adding/removing/hiding/showing. This class lives within the BackStackManager class.
public class BackStackFragmentManager {
//region Members
/** Reference to fragment manager */
private final FragmentManager mFragmentManager;
/** Last added fragment */
private Fragment mLastAdded;
//endregion
//region Constructors
public BackStackFragmentManager(#NonNull final FragmentManager fragmentManager) {
mFragmentManager = fragmentManager;
}
//endregion
//region Methods
/** Switch root fragment */
public void switchFragment(#NonNull final Fragment fragment) {
final FragmentTransaction transaction = mFragmentManager.beginTransaction();
transaction.show(fragment);
transaction.hide(mLastAdded);
transaction.commit();
mLastAdded = fragment;
}
/** Adding child fragment to a root */
public void addChildFragment(#NonNull final Fragment fragment,
final int layoutId,
#NonNull final String tag) {
final FragmentTransaction transaction = mFragmentManager.beginTransaction();
transaction.add(layoutId, fragment, tag);
transaction.commit();
}
/** Add a root fragment */
public void addFragment(#NonNull Fragment fragment, int layoutId) {
final FragmentTransaction transaction = mFragmentManager.beginTransaction();
//since we hide/show. This should only happen initially
if (!fragment.isAdded()) {
transaction.add(layoutId, fragment, fragment.getClass().getName());
}
else {
transaction.show(fragment);
}
if (mLastAdded != null) {
transaction.hide(mLastAdded);
}
transaction.commit();
mLastAdded = fragment;
}
/** Pop back stack
* Function is removing childs that is not used!
*/
public void popBackStack(#NonNull final String tag) {
final Fragment fragment = mFragmentManager.findFragmentByTag(tag);
final FragmentTransaction transaction = mFragmentManager.beginTransaction();
transaction.remove(fragment);
transaction.commit();
}
//endregion
}
BackStack.java
This is a simple class that just handles internal references to the root and tags to all the backstack child entries. And also handling of these child entries
public class BackStack {
//region Members
public final Fragment mRootFragment;
final LinkedList<String> mStackItems;
//endregion
//region Constructors
public BackStack(#NonNull final Fragment rootFragment) {
mRootFragment = rootFragment;
mStackItems = new LinkedList<>();
}
//endregion
//region Methods
public String pop() {
if (isEmpty()) return null;
return mStackItems.pop();
}
public void push(#NonNull final String id) {
mStackItems.push(id);
}
public boolean isEmpty() {
return mStackItems.isEmpty();
}
//endregion
}
Listener
Not much to say about this. It is implemented by the activity
public interface BackStackHelperListener {
/** Let the listener know that the app should close. The backstack is depleted */
void closeApp();
/** Let the listener know that the user clicked on an already main root. So app can do
* a secondary action if needed
*/
void refresh();
}
References
https://blog.f22labs.com/instagram-like-bottom-tab-fragment-transaction-android-389976fb8759
This question already has answers here:
Android ListView in Fragment
(2 answers)
Closed 9 years ago.
I am a beginner in android development.
For a project, I would like to know how to implement a listview into a fragment ?
In my project, I have 3 fragments.
I have to retrieve all the contacts which are in my phone.
(I am using this example => http://samir-mangroliya.blogspot.fr/p/android-read-contact-and-display-in.html).
Then, I'll put them into one af the fragment, which contains a listview.
I am searching for many example, but it is only listview into ACTIVITY example.
Can you please help me ?
Best regards,
Tofuw
PS : Sorry for my bad english, I'm french :/
EDIT
Here is my code :
public class PhoneFrag extends ListFragment
{
private List<Contact>listecontact=new ArrayList<Contact>();
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
}
public void onActivityCreated(Bundle savedInstanceState, Context context)
{
super.onActivityCreated(savedInstanceState);
String[]values=new String[]{};
Cursor phones=context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);
while(phones.moveToNext())
{
String name=phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String num=phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
Contact contact=new Contact();
contact.setName(name);
contact.setNum(num);
listecontact.add(contact);
}
phones.close();
ArrayAdapter<String>adapter=new ArrayAdapter<String>(getActivity(), android.R.layout.simple_expandable_list_item_2, listecontact);
setListAdapter(adapter);
}
I have an error at the third last line :
the constructor ArrayAdapter(FragmentActivity, int,
List) is undefined
Can you help me please ?
Tofuw
Get your Fragment to extend ListFragment instead of Fragment.If you have used or looked at examples with ListActivity there is no need to associate this fragment with an xml layout file using setContentView(layout) anymore,Android creates a readymade Fragment with a ListView within it for your conveniance.Instead conveniance methods like setListAdapter(adapter) are available to you.However,in case you would like to access the ListView instance use the getListView().The Fragment seems to use onActivityCreated to do this:
I am guessing that you know fragments well enough to understand that this in ListActivity mean getActivity() in your ListFragment.
Here is the code that I found in the Android Fragment Documentation:
public static class TitlesFragment extends ListFragment {
boolean mDualPane;
int mCurCheckPosition = 0;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Populate list with our static array of titles.
setListAdapter(new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES));
// Check to see if we have a frame in which to embed the details
// fragment directly in the containing UI.
View detailsFrame = getActivity().findViewById(R.id.details);
mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;
if (savedInstanceState != null) {
// Restore last state for checked position.
mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
}
if (mDualPane) {
// In dual-pane mode, the list view highlights the selected item.
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
// Make sure our UI is in the correct state.
showDetails(mCurCheckPosition);
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("curChoice", mCurCheckPosition);
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
showDetails(position);
}
/**
* Helper function to show the details of a selected item, either by
* displaying a fragment in-place in the current UI, or starting a
* whole new activity in which it is displayed.
*/
void showDetails(int index) {
mCurCheckPosition = index;
if (mDualPane) {
// We can display everything in-place with fragments, so update
// the list to highlight the selected item and show the data.
getListView().setItemChecked(index, true);
// Check what fragment is currently shown, replace if needed.
DetailsFragment details = (DetailsFragment)
getFragmentManager().findFragmentById(R.id.details);
if (details == null || details.getShownIndex() != index) {
// Make new fragment to show this selection.
details = DetailsFragment.newInstance(index);
// Execute a transaction, replacing any existing fragment
// with this one inside the frame.
FragmentTransaction ft = getFragmentManager().beginTransaction();
if (index == 0) {
ft.replace(R.id.details, details);
} else {
ft.replace(R.id.a_item, details);
}
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
} else {
// Otherwise we need to launch a new activity to display
// the dialog fragment with selected text.
Intent intent = new Intent();
intent.setClass(getActivity(), DetailsActivity.class);
intent.putExtra("index", index);
startActivity(intent);
}
}
}
You can also use methods like setEmptyText and setListShown to display a message in case your Cursor has no rows and toggle the visibility of your listView respectively.Also,Android's Loader Framework is only supported with a Fragment and not an Activity in the support library.
In my MainActivity extends FragmentActivity, I have a FragmentA, When I press a Button in FragmentA, I call to FragmentB.
FragmentB f = FragmentB.newInstance(1);
getSupportFragmentManager().beginTransaction().replace(R.id.llMain, f).addToBackStack(null).commit();
In FragmentB, I create a Object People p1(with Name and age) . And When I press a Button B in FragmentB, I call
getFragmentManager().popBackStack();
It will return FragmentA,
So, I want to pass data Object People p1 from FragmentB to FragmentA. What do i have to do?
I try to search but can't find a solution.
create CallBack in your Fragment and handle it in FragmentActivity,
google example has this realization
declaring OnHeadlineSelectedListener callback
public class HeadlinesFragment extends ListFragment {
OnHeadlineSelectedListener mCallback;
// The container Activity must implement this interface so the frag can deliver messages
public interface OnHeadlineSelectedListener {
/** Called by HeadlinesFragment when a list item is selected */
public void onArticleSelected(int position);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// We need to use a different list item layout for devices older than Honeycomb
int layout = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
android.R.layout.simple_list_item_activated_1 : android.R.layout.simple_list_item_1;
// Create an array adapter for the list view, using the Ipsum headlines array
setListAdapter(new ArrayAdapter<String>(getActivity(), layout, Ipsum.Headlines));
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception.
try {
mCallback = (OnHeadlineSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnHeadlineSelectedListener");
}
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
// Notify the parent activity of selected item
mCallback.onArticleSelected(position);
// Set the item as checked to be highlighted when in two-pane layout
getListView().setItemChecked(position, true);
}
Realize callback method in FragmentActivity and send (by .setArguments()) data from HeadLinesFragment to ArticleFragment, if ArticleFragment is available
public class MainActivity extends FragmentActivity
implements HeadlinesFragment.OnHeadlineSelectedListener {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.news_articles);
// Check whether the activity is using the layout version with
// the fragment_container FrameLayout. If so, we must add the first fragment
if (findViewById(R.id.fragment_container) != null) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
return;
}
// Create an instance of ExampleFragment
HeadlinesFragment firstFragment = new HeadlinesFragment();
// In case this activity was started with special instructions from an Intent,
// pass the Intent's extras to the fragment as arguments
firstFragment.setArguments(getIntent().getExtras());
// Add the fragment to the 'fragment_container' FrameLayout
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, firstFragment).commit();
}
}
public void onArticleSelected(int position) {
// The user selected the headline of an article from the HeadlinesFragment
// Capture the article fragment from the activity layout
ArticleFragment articleFrag = (ArticleFragment)
getSupportFragmentManager().findFragmentById(R.id.article_fragment);
if (articleFrag != null) {
// If article frag is available, we're in two-pane layout...
// Call a method in the ArticleFragment to update its content
articleFrag.updateArticleView(position);
} else {
// If the frag is not available, we're in the one-pane layout and must swap frags...
// Create fragment and give it an argument for the selected article
ArticleFragment newFragment = new ArticleFragment();
Bundle args = new Bundle();
args.putInt(ArticleFragment.ARG_POSITION, position);
newFragment.setArguments(args);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack so the user can navigate back
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
}
}
You should use an interface within Activity for communication between fragments. Check this android training lesson.
All Fragment-to-Fragment communication is done through the associated
Activity. Two Fragments should never communicate directly.
You can pass arguments to a Fragment with Bundle. Change your code to:
FragmentB f = FragmentB.newInstance(1);
Bundle args = new Bundle();
args.putString("NAME", name);
args.putInt("AGE", age);
f.setArguments(args);
getSupportFragmentManager().beginTransaction().replace(R.id.llMain, f).addToBackStack(null).commit();
and then retrieve the arguments for example in FragmentA's onCreateView with:
int age = getArguments().getInt("AGE");
//or with a second parameter as the default value
int age = getArguments().getInt("AGE", 0);
If you want to pass the whole People object to the Bundle, you need to make the class serializable. I think it's easier to pass the variables and then recreate the object.
As we know, ActivityGroup is deprecated.I'm try to reconfigure my code.
this code use ActivityGroup :
public void lauchContentActivity(Intnet intent) {
View view = getLocationActivityManager().startActivity(
intent.getComponent().getShortClassName(),
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP))
.getDecorView();
setContentView(view);
}
So I can toogle any activity's visible ,and save the activity instance state which is hide.
But FragmentManager has only a backstack, and can't bring a fragment to front expect pressing the Back button.
How to manager Fragment like ActivityGroup manager Activity?
I achieved fragment tabs, Its was challenging for me to achieve and to understand fragment hierarchy while adding and removing fragment.
As the question of managing fragments, its depend on your requirement, this sample details you hierarchy of fragments & way to manage fragment with the help of HashMap.
Below class will explain you behaviorof fragment. (class present in that sample)
AppMainTabActivity.java
public class AppMainTabActivity extends FragmentActivity {
/* Your Tab host */
private TabHost mTabHost;
/* A HashMap of stacks, where we use tab identifier as keys.. */
private HashMap<String, Stack<Fragment>> mStacks;
/* Save current tabs identifier in this.. */
private String mCurrentTab;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.app_main_tab_fragment_layout);
/*
* Navigation stacks for each tab gets created.. tab identifier is used
* as key to get respective stack for each tab
*/
mStacks = new HashMap<String, Stack<Fragment>>();
mStacks.put(AppConstants.TAB_A, new Stack<Fragment>());
mStacks.put(AppConstants.TAB_B, new Stack<Fragment>());
mStacks.put(AppConstants.TAB_C, new Stack<Fragment>());
mTabHost = (TabHost) findViewById(android.R.id.tabhost);
mTabHost.setOnTabChangedListener(listener);
mTabHost.setup();
initializeTabs();
}
private View createTabView(final int id) {
View view = LayoutInflater.from(this).inflate(R.layout.tabs_icon, null);
ImageView imageView = (ImageView) view.findViewById(R.id.tab_icon);
imageView.setImageDrawable(getResources().getDrawable(id));
return view;
}
public void initializeTabs() {
/* Setup your tab icons and content views.. Nothing special in this.. */
TabHost.TabSpec spec = mTabHost.newTabSpec(AppConstants.TAB_A);
mTabHost.setCurrentTab(-3);
spec.setContent(new TabHost.TabContentFactory() {
public View createTabContent(String tag) {
return findViewById(R.id.realtabcontent);
}
});
spec.setIndicator(createTabView(R.drawable.toolkittabicon));
mTabHost.addTab(spec);
spec = mTabHost.newTabSpec(AppConstants.TAB_B);
spec.setContent(new TabHost.TabContentFactory() {
public View createTabContent(String tag) {
return findViewById(R.id.realtabcontent);
}
});
spec.setIndicator(createTabView(R.drawable.followtabicon));
mTabHost.addTab(spec);
spec = mTabHost.newTabSpec(AppConstants.TAB_C);
spec.setContent(new TabHost.TabContentFactory() {
public View createTabContent(String tag) {
return findViewById(R.id.realtabcontent);
}
});
spec.setIndicator(createTabView(R.drawable.myhuddletabicion));
mTabHost.addTab(spec);
}
/* Comes here when user switch tab, or we do programmatically */
TabHost.OnTabChangeListener listener = new TabHost.OnTabChangeListener() {
public void onTabChanged(String tabId) {
/* Set current tab.. */
mCurrentTab = tabId;
if (mStacks.get(tabId).size() == 0) {
/*
* First time this tab is selected. So add first fragment of
* that tab. Dont need animation, so that argument is false. We
* are adding a new fragment which is not present in stack. So
* add to stack is true.
*/
if (tabId.equals(AppConstants.TAB_A)) {
pushFragments(tabId, new ToolKitFragment(), false, true);
} else if (tabId.equals(AppConstants.TAB_B)) {
pushFragments(tabId, new FollowFragment(), false, true);
} else if (tabId.equals(AppConstants.TAB_C)) {
pushFragments(tabId, new HuddleFragment(), false, true);
}
} else {
/*
* We are switching tabs, and target tab is already has atleast
* one fragment. No need of animation, no need of stack pushing.
* Just show the target fragment
*/
pushFragments(tabId, mStacks.get(tabId).lastElement(), false,
false);
}
}
};
/*
* Might be useful if we want to switch tab programmatically, from inside
* any of the fragment.
*/
public void setCurrentTab(int val) {
mTabHost.setCurrentTab(val);
}
/*
* To add fragment to a tab. tag -> Tab identifier fragment -> Fragment to
* show, in tab identified by tag shouldAnimate -> should animate
* transaction. false when we switch tabs, or adding first fragment to a tab
* true when when we are pushing more fragment into navigation stack.
* shouldAdd -> Should add to fragment navigation stack (mStacks.get(tag)).
* false when we are switching tabs (except for the first time) true in all
* other cases.
*/
public void pushFragments(String tag, Fragment fragment,
boolean shouldAnimate, boolean shouldAdd) {
if (shouldAdd)
mStacks.get(tag).push(fragment);
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
if (shouldAnimate)
ft.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);
ft.replace(R.id.realtabcontent, fragment);
ft.commit();
}
public void popFragments() {
/*
* Select the second last fragment in current tab's stack.. which will
* be shown after the fragment transaction given below
*/
Fragment fragment = mStacks.get(mCurrentTab).elementAt(
mStacks.get(mCurrentTab).size() - 2);
/* pop current fragment from stack.. */
mStacks.get(mCurrentTab).pop();
/*
* We have the target fragment in hand.. Just show it.. Show a standard
* navigation animation
*/
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
ft.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right);
ft.replace(R.id.realtabcontent, fragment);
ft.commit();
}
#Override
public void onBackPressed() {
if (((BaseFragment) mStacks.get(mCurrentTab).lastElement())
.onBackPressed() == false) {
Log.d("######", "on back press");
/*
* top fragment in current tab doesn't handles back press, we can do
* our thing, which is
*
* if current tab has only one fragment in stack, ie first fragment
* is showing for this tab. finish the activity else pop to previous
* fragment in stack for the same tab
*/
if (mStacks.get(mCurrentTab).size() == 1) {
super.onBackPressed(); // or call finish..
} else {
popFragments();
}
} else {
// do nothing.. fragment already handled back button press.
}
}
/*
* Imagine if you wanted to get an image selected using ImagePicker intent
* to the fragment. Ofcourse I could have created a public function in that
* fragment, and called it from the activity. But couldn't resist myself.
*/
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mStacks.get(mCurrentTab).size() == 0) {
return;
}
/* Now current fragment on screen gets onActivityResult callback.. */
mStacks.get(mCurrentTab).lastElement()
.onActivityResult(requestCode, resultCode, data);
}
}
But if you have child fragment then it will create issue on back press which is not handle in that sample, solution# you have to Override onDetach() method and manage child fragment check below code snippet.
#Override
public void onDetach() {
super.onDetach();
try {
Field childFragmentManager = Fragment.class.getDeclaredField("mChildFragmentManager");
childFragmentManager.setAccessible(true);
childFragmentManager.set(this, null);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
Additionally refer developer site and dig fragment in details.
The feasibility of this answer will depend on what you want to accomplish with the different activities. I had a similar problem and I solved it by using Fragments. Imagine that you have one Activity for your task which has different facets. Then you can easily use the Activity to gather and persist data that you will need in each Fragment or to feed your business logic and your Fragments can each cater to different facets of the task. I would highly recommend this as Fragmentand FragmentManagerare supposed to replace the deprecated ActivityGroup. Here is some documentation on the matter:
FragmentManager
Fragment
Now, using Fragments is a bit different from using Activity but not to much. Basically, you declare your Fragmentlike any other object. To show your Fragment you will use:
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
YourFragment yourFragment = new YourFragment();
fragmentTransaction.replace(R.id.containerID, yourFragment);
fragmentTransaction.addToBackStack();
fragmentTransaction.commit();
The documentation that you can download using ADK also contains a lot of samples that use Fragments. I believe it is a good start if you want to get some quality code snippets!
if you've used the sherlock master detail flow, please help me.
I have added tabs and have removed the data inside the detail fragment/activity but when I try to inflate a button inside the tabs, it doesn't work.
Can you help me?
Here's the list activity that I've modified to display tabs when in the two-pane mode.
package com.example.sample;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.ActionBar.Tab;
import com.actionbarsherlock.app.SherlockFragmentActivity;
/**
* An activity representing a list of Courses. This activity has different
* presentations for handset and tablet-size devices. On handsets, the activity
* presents a list of items, which when touched, lead to a
* {#link CourseDetailActivity} representing item details. On tablets, the
* activity presents the list of items and item details side-by-side using two
* vertical panes.
* <p>
* The activity makes heavy use of fragments. The list of items is a
* {#link CourseListFragment} and the item details (if present) is a
* {#link CourseDetailFragment}.
* <p>
* This activity also implements the required
* {#link CourseListFragment.Callbacks} interface to listen for item selections.
*/
public class CourseListActivity extends SherlockFragmentActivity implements
CourseListFragment.Callbacks {
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
*/
private boolean mTwoPane;
private boolean once = true;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_course_list);
if (findViewById(R.id.course_detail_container) != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-large and
// res/values-sw600dp). If this view is present, then the
// activity should be in two-pane mode.
mTwoPane = true;
// In two-pane mode, list items should be given the
// 'activated' state when touched.
((CourseListFragment) getSupportFragmentManager().findFragmentById(
R.id.course_list)).setActivateOnItemClick(true);
}
// TODO: If exposing deep links into your app, handle intents here.
}
/**
* Callback method from {#link CourseListFragment.Callbacks} indicating that
* the item with the given ID was selected.
*/
#Override
public void onItemSelected(String id) {
if (mTwoPane) {
// In two-pane mode, show the detail view in this activity by
// adding or replacing the detail fragment using a
// fragment transaction.
CourseDetailFragment fragment = new CourseDetailFragment();
getSupportFragmentManager().beginTransaction()
.replace(R.id.course_detail_container, fragment).commit();
if (once) {
ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
// initiating both tabs and set text to it.
ActionBar.Tab assignTab = actionBar.newTab().setText("Assignments");
ActionBar.Tab schedTab = actionBar.newTab().setText("Schedule");
ActionBar.Tab contactTab = actionBar.newTab().setText("Contact");
// Create three fragments to display content
Fragment assignFragment = new Assignments();
Fragment schedFragment = new Schedule();
Fragment contactFragment = new Contact();
assignTab.setTabListener(new MyTabsListener(assignFragment));
schedTab.setTabListener(new MyTabsListener(schedFragment));
contactTab.setTabListener(new MyTabsListener(contactFragment));
actionBar.addTab(assignTab);
actionBar.addTab(schedTab);
actionBar.addTab(contactTab);
once = false;
}
} else {
// In single-pane mode, simply start the detail activity
// for the selected item ID.
Intent detailIntent = new Intent(this, CourseDetailActivity.class);
startActivity(detailIntent);
}
}
class MyTabsListener implements ActionBar.TabListener {
public Fragment fragment;
public MyTabsListener(Fragment fragment) {
this.fragment = fragment;
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
ft.replace(R.id.twopanecontainer, fragment);
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
ft.remove(fragment);
}
}
}
Here's the fragment course detail layout that holds a textview by default.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/twopanecontainer"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="#+id/course_detail"
style="?android:attr/textAppearanceLarge"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".CourseDetailFragment" />
</LinearLayout>
What should I modify so I can inflate a different view for each tab?
Thanks
Have a ViewPager in yout activity layout, then implement FragmentPagerAdapter