Trying to find a ListFragment in the xml layout is always returning null. I'm using SherlockFragmentActivity with a SherlockListFragment. Here is the code:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_custom_layout);
adapter = new My_Custom_Adapter(this, My_Object, My_Object_2);
}
// Create the list fragment and add it as the list content.
if (getSupportFragmentManager().findFragmentById(android.R.id.content) == null) {
mListFragment list = new mListFragment();
getSupportFragmentManager().beginTransaction().add(android.R.id.content, list).commit();
}
}
public static class mListFragment extends SherlockListFragment {
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListAdapter(adapter);
setListShown(true);
}
}
This is a shortened version of the xml layout:
<RelativeLayout
android:id="#+id/top_control_bar">
<!-- Text Views -->
</RelativeLayout>
<RelativeLayout >
<!-- Button -->
</RelativeLayout>
<LinearLayout
android:id="#+id/start_data"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:layout_below="#id/top_control_bar"
android:orientation="vertical" >
<fragment
android:name="android.support.v4.app.ListFragment"
android:id="#android:id/content"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
findFragmentById(android.R.id.content)
always returns null. Why can't it find the fragment within my layout?
The problem that you have is that you declared in the xml the Fragment as a ListFragment.
getSupportFragmentManager().findFragmentById(android.R.id.content) uses Support fragment manager, so you will need a SupportMapFragment
Try this:
<fragment
android:name="com.google.android.gms.maps.SupportMapFragment"
android:id="#android:id/content"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
Shouldn't
android:id="#android:id/content"
be
android:id="#+id/content"
?
From the documentation it seems like you are using the wrong syntax.
I'm also not sure your chained methods will work correctly as written. Change
getSupportFragmentManager().beginTransaction().add(android.R.id.content, list).commit();
to
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.add(android.R.id.content, list);
transaction.commit();
even then, I honestly don't know what you'd be trying to do there. You are aware that FragmentTransaction's add method as you're using it is trying to add a fragment to a container which, in your if, already was established as having a null value?
Try this in mListFragment class override onCreate() and call setRetainInstance(true);
public static class mListFragment extends SherlockListFragment {
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListAdapter(adapter);
setListShown(true);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
}
Related
I have simple app with fragments.
At the beginning list of fragments is empty.
And there are 2 buttons:
ADD FRAGMENT
REMOVE FRAGMENT
And following use-case:
Add fragment
Add fragment
Remove first fragment
Add fragment
As result onCreateView() on 4th step is not executed.
If I add fragment again on 5th step - onCreateView is executed again.
Why is it so? I didn't find right answer in google.
Source code:
public class MyActivity extends FragmentActivity {
private ViewPager pager;
private FragmentAdapter adapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
pager = (ViewPager) findViewById(R.id.pager);
final Button addButton = (Button) findViewById(R.id.add_button);
addButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
adapter.addFragment(new MyFragment());
adapter.notifyDataSetChanged();
}
});
final Button removeButton = (Button) findViewById(R.id.remove_button);
removeButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
adapter.removeFragment(0);
adapter.notifyDataSetChanged();
}
});
adapter = new FragmentAdapter(getSupportFragmentManager());
pager.setAdapter(adapter);
pager.setOffscreenPageLimit(10);
}
private class FragmentAdapter extends FragmentStatePagerAdapter {
final List<MyFragment> fragments = new ArrayList<MyFragment>();
public FragmentAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
return fragments.get(i);
}
#Override
public int getCount() {
return fragments.size();
}
public void addFragment(MyFragment fragment) {
fragments.add(fragment);
}
public void removeFragment(int index) {
fragments.remove(index);
}
}
private class MyFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment, container, false);
}
}
}
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add"
android:id="#+id/add_button"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Remove first"
android:id="#+id/remove_button"/>
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New Text"
android:id="#+id/textView"
android:layout_gravity="center_horizontal"/>
</LinearLayout>
OnCreateView() is only called when the actual Fragment is being drawn on the screen. Since you have removed a fragment at the beginning the pager no longer looks at the same fragment.
To better understand what is going on put a visible number on each of the fragments and then try adding and removing the fragments.
The issue is 'FragmentStatePagerAdapter' and the way it's implemented. The way to fix it is this:
Go to googlesource and grab the source for 'FragmentStatePagerAdapter'.
refactor (name, package,etc) to make it your own implementation.
Extend from your NEW version of 'FragmentStatePagerAdapter'
Modify your NEW version of 'FragmentStatePagerAdapter' using these steps below:
a) Change the mFragments.set(index, object) command to mFragments.add(index, object), in the the 'instantiateItem()' function.
b) Change the mFragments.set(index, object) command to mFragments.remove(index), in the 'destroyItem' function.
All of this assumes that you are using an ArrayList to hold fragments in your 'extends' class and that they are synced to match the ViewPager.
You will need to add a 'pageIndex' to each fragment, so that you can tell ViewPager when your item has 'moved' (due to deleting an item, that is NOT the LAST item).
This took several days to run down...hope it helps someone :-)
Overview of what I need:
Pass String from FragmentActivity to FragmentList. Said FragmentList is specified in the xml as below:
<LinearLayout 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"
android:orientation="vertical"
tools:context=".MultiContactsFragmentList" >
...
<fragment
android:id="#+id/multiContactsList_fragment"
android:name="com.sf.lidgit_android.content.MultiContactsFragmentList"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
The problem in the code lies here (by the way, I'm using Support Library because I target API level 10), in my class that extends from FragmentActivity:
#Override
protected void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
...
inviteByEmail();
}
public void inviteByEmail(){
setContentView(R.layout.activity_multi_contacts_email_picker);
MultiContactsFragmentList fragmentList =
(MultiContactsFragmentList)
getSupportFragmentManager().findFragmentById(R.id.multiContactsList_fragment);
Bundle extras = new Bundle();
extras.putString(MultiContactsFragmentList.DATA_TO_PICK, MultiContactsFragmentList.EMAIL_DATA);
fragmentList.setArguments(extras);
}
Here is the thing, fragmentList is null. I figure it's because of the life cycle (I'm not aware to the FragmetnActivity-FragmentList life cycle). The FragmentList is yet to be created. Is that it? I know the other way around would be to have my FragmentActivity "save" the data, and have the FragmentList access it (since FragmentActivity is available to it's Fragments, I believe).
EDIT
My FragmentList class (the fragment I'm trying to pass a Bundle to), implements LoaderManager.LoaderCallbacks<Cursor> . I don't know if that influences or not, but I tried the first answer below and got an error on my FragmentList caused by a null pointer Exception, it appears that the onLoadFinished(..) method was called before the onCreate(..), because the null Pointer Exception happened on the onLoadFinished(..), stating the adapter was null.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("invite", "oncreate called");
String[] fromColumns = {ContactsContract.Contacts.DISPLAY_NAME, this.view_ID_Data};
int[] toViews = {R.id.contact_name, R.id.contact_data};
getLoaderManager().initLoader(MULTI_CONTACTS_LIST_LOADER, null, this);
this.adapter = new SimpleCursorAdapter(
getActivity().getApplicationContext(), R.layout.custom_list_contacts_multiple_choice,
null, fromColumns, toViews,
CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
setListAdapter(adapter);
}
#Override
public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) {
adapter.swapCursor(cursor);
}
Change your layout to this:
<LinearLayout 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"
android:orientation="vertical">
<FrameLayout
android:id="#+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
And in your activity:
#Override
protected void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
...
inviteByEmail();
}
public void inviteByEmail(){
setContentView(R.layout.activity_multi_contacts_email_picker);
MultiContactsFragmentList fragmentList = new MultiContactsFragmentList();
Bundle extras = new Bundle();
extras.putString(MultiContactsFragmentList.DATA_TO_PICK, MultiContactsFragmentList.EMAIL_DATA);
fragmentList.setArguments(extras);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.content, fragmentList);
ft.commit();
}
The method setArguments(Bundle args) for the fragment can only be called just after creating the fragment, so if you embed you fragment in the XML the argumments setting may no be happening at all even if you call it. This way you have to attach the fragment yourself but you will have the arguments when you access it.
I have a layout file containing a listview that I would like to fill with the help of a Fragment. But it continues to give me errors.
The layout file:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<ListView
android:id="#+id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true" >
</ListView>
<TableLayout
android:id="#+id/details"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:stretchColumns="1" >
<Button
android:id="#+id/create_patient_button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/create_patient_button" />
</TableLayout>
</RelativeLayout>
My fragmentActivity:
public class BasicFragmentActivity extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.create_patient_view);
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.list);
if (fragment == null) {
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.list, new BasicFragment());
ft.commit(); // Make sure you call commit or your Fragment will not be added.
// This is very common mistake when working with Fragments!
}
}
}
My ListFragment:
public class BasicFragment extends ListFragment {
private PatientAdapter pAdapter;
#Override
public void onActivityCreated(Bundle savedState) {
super.onActivityCreated(savedState);
pAdapter = new PatientAdapter(getActivity(), GFRApplication.dPatients);
setListAdapter(pAdapter);
}
}
The error:
java.lang.UnsupportedOperationException: addView(View) is not supported in AdapterView
findFragmentById(...) function takes ID of fragment(!) as parameter. But you call it with R.id.list which is ID of ListView (<ListView android:id="#+id/list" ...). It's wrong because ListView is NOT a fragment. It's the first problem.
The second problem is:
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.list, new BasicFragment());
in ft.add() function first parameter is ID of container in which you want to put your fragment. But you use R.id.list which is id of your ListView. It is wrong because ListView is not a container in which you can put fragments directly.
If you want put fragments into ListView items you can:
fill ListView with custom views.
declare <fragment ...> into custom view layout (XML). Or create fragment container in custom view layout (FrameLayout for example) and put there fragment at runtime (in getView() method).
I would like to use one Activity which holds several fragments and navigate among the fragments. For example, in the activity, there is a list view which is a fragment, when user select one item from the list, the view will navigate to another fragment, How could this be implemented?
I know there is a nice tutorial on the developer site, but it handles the tablet screen in which two pane layout with one list fragment and one detailed fragment showing in one screen. I only want to navigate among fragments without show two fragments in one screen.
Are there tutorials can teach me how to do it?
In the nutshell the answer to your question is to notify your host activity and then have your host activity replace your current fragment container using FragmentManager.
One of the approach is to make an interface in your first fragment, have your host activity register/listen (implement) to this interface and then have your FragmentManager to replace the container content with the second fragment on listener callback.
I'm not sure about tutorial but here is my snippet:
First Fragment
public class First extends Fragment{
private static onMySignalListener listener;
//call this function from whatever you like i.e button onClickListener
public void switchWindow() {
if(listener != null){
listener.onMySignal();
}
}
public interface onMySignalListener {
//customize this to your liking
//plain without argument
void onMySignal();
//with argument
void onMySignalWithNum(int mNum);
}
public static void setOnMySignalListener(onMySignalListener listener) {
First.listener = listener;
}}
Host Activity
public class HostActivity extends FragmentActivity implements onMySignalListener{
private final String ADD_TAG_IF_NECESSARY = "mTag";
#Override
public void onCreate(Bundle ssi) {
setContentLayout(R.layout.main);
FirstFragment.setOnMySignalListener(this);
}
#Override
public void onMySignal() {
//if you're using compat library
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
//initialize your second fragment
sfragment = SecondFragment.newInstance(null);
//replace your current container being most of the time as FrameLayout
transaction.replace(R.id.container, fragment, ADD_TAG_IF_NECESSARY);
transaction.commit();
}
#Override
public void onMySignalWithNum(int mNum) {
//you can do the same like the above probably with your own customization
}}
This is only an example on how you'd implement interface, kindly tidy it up by yourself. And please be noted though that this is not effective if you have a lot of fragment wanting to notify your host activity about something. doing so will lead you to implement various listener to your host activity.
I think this will be useful for you. It is example of two fragments in one screen works independently.
MainActivity :
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
Fragment newFragment = new Test();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.add(R.id.UprLayout, newFragment);
// transaction.addToBackStack(null);
transaction.commit();
Fragment newFragment2 = new TestRight();
FragmentTransaction transaction2 = getFragmentManager().beginTransaction();
transaction2.add(R.id.dwnLayout, newFragment2);
// transaction.addToBackStack(null);
transaction2.commit();
}
}
main_activity.xml :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/LinearLayout2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center|center_horizontal"
android:orientation="vertical" >
<LinearLayout
android:id="#+id/UprLayout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical" />
<LinearLayout
android:id="#+id/dwnLayout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical" />
</LinearLayout>
Fragment Test :
public class Test extends Fragment {
TextView tv;
#Override
public void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.test, container, false);
return view;
}
}
Fragment TestRight :
public class TestRight extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.test_right, container, false);
return view;
}
#Override
public void onStart() {
super.onStart();
Button button = (Button)getActivity().findViewById(R.id.button1);
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Fragment newFragment = new Right2nd();
FragmentTransaction transaction = getFragmentManager()
.beginTransaction();
transaction.replace(R.id.dwnLayout, newFragment);
transaction.addToBackStack("aa");
transaction.commit();
//transaction.add(R.id.frag, newFragment).commit();
}
});
}
}
test.xml :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical" >
<TextView
android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="test"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="50sp" />
</LinearLayout>
test_right.xml :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical" >
<Button
android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click" />
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test right"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="45sp" />
</LinearLayout>
Fragment Right2nd :
public class Right2nd extends Fragment{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View vw = inflater.inflate(R.layout.right_2nd, container, false);
return vw;
}
}
right_2nd.xml :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical" >
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Right 2nd"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="50sp" />
</LinearLayout>
I know this is quite old question but this is currently implemented in Navigation Component https://developer.android.com/guide/navigation/
Here is a very nice tutorial about it. MULTIPLE FRAGMENTS STACK IN EACH VIEWPAGER TAB
I think ChildFragment
would do the job for you. Ignore the Tab ViewPager. It's performing the same thing in multiple tabs. Hope this helps a bit.
I'm trying to build an Android 4.0 App with the new fragments and action bar.
I'm doing well but I have a little problem now.
When I put a fragment inside a tab, and for example, a time picker in the fragment layout, in the emulator it will appear twice, one on top of the other.
Here is my code:
public class NetworksActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.networks);
// setup Action Bar for tabs
ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
// instantiate fragment for the tab
Fragment networksFragment = new NetworksFragment();
// add a new tab and set its title text and tab listener
actionBar.addTab(actionBar.newTab().setText("Sensors")
.setTabListener(new ActionTabListener(networksFragment)));
}}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<fragment
android:id="#+id/net_frag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:name="my.package.test.NetworksFragment" />
</FrameLayout>
public class NetworksFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.networks_fragment, container, false);
}
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TimePicker
android:id="#+id/timePicker1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</FrameLayout>
public class ActionTabListener implements ActionBar.TabListener {
private NetworksFragment frag;
// Called to create an instance of the listener when adding a new tab
public ActionTabListener(NetworksFragment fragment) {
frag = fragment;
}
#Override
public void onTabReselected(Tab arg0, FragmentTransaction arg1) {
// TODO Auto-generated method stub
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
ft.add(R.id.net_frag, frag, null);
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
ft.remove(frag);
}
}
It seems like its putting two fragments (two copies) in the same activity.
Do you know what are causing this?
Thanks in advance.
It looks like you are defining a fragment in XML, which will instantiate it when that layout is inflated (from NetworksActivity.setContentView(R.layout.networks)) then you are creating another instance of it just below that.
Unless I am missing something, that is your problem. No need to define it in XML if you are going to instantiate it manually and add it yourself in code.
I had a similar problem. The way i solved it was to remove this code from my Fragment-class:
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
CameraFragment cameraFragment = new CameraFragment();
fragmentTransaction.add(R.id.spinnerFragment, spinnerFragment);
fragmentTransaction.commit();
And left this code from my fragment-XML:
<fragment
android:id="#+id/spinnerFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
class="com.example.SpinnerFragment" />
I guess you can do it the oposite way also :)