I have an Activity which hosts a Fragment.
The Activity layout file:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment class="com.my.ContentFragment"
android:id="#+id/fragment_content"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
Java code of Activity:
import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBarActivity;
public class ContentActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//data from previous Activity
Bundle data = getIntent().getExtras();
Fragment contentFragment = getSupportFragmentManager()
.findFragmentById(R.id.fragment_content);
// Pass data to fragment
/*java.lang.IllegalStateException: Fragment already active*/
contentFragment.setArguments(data);
}
...
}
I try to find the fragment in onCreate() of Activity, and then pass some data to it. But when I contentFragment.setArguments(data);, I get java.lang.IllegalStateException: Fragment already active.
Then I also checked contentFragment.getArguments() which is null. So, why I can not set arguments to my fragment?
If it is not possible to pass bundle to fragment this way, how can I pass the bundle to fragment?
Arguments are typically read in Fragment.onCreate() .. If you inflate the Fragment from xml layout, then the fragment is already added through the FragmentManager to the activity and can not take arguments anymore.
If a fragment needs arguments it is better for you to add it to the FragmentManager programatically and not using the xml way. I encourage you to have a look to this doc where it is explained the correct fragment lifecycle and how to attach this fragment to the activity correctly.
Btw. you may find FragmentArgs useful.
Just use FrameLayout in your xml file instead of fragment tag.
Then just create new Fragment object from onCreate() of your activity [above] and call fragment.setArguments(Bundle);.
And inside onCreate() of Fragment class, call getArguments(), which will return passed Bundle.
It is best approach ever to pass bundle to Fragment.
Related
In my project I have an activity and multiple fragments.
Currently the fragments are declared in my activity xml
e.g:
<fragment
tools:layout="#layout/fragment_do_you_know"
android:name="myapp.fragments.DoYouKnowFragment"
android:id="#+id/doYouKnowFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"/>
<fragment
tools:layout="#layout/fragment_whats_new"
android:name="myapp.fragments.WhatsNewFragment"
android:id="#+id/whatsNewFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"/>
During activity initialization I get fragment references:
mWhatsNewFragment = (WhatsNewFragment) mFragmentManager.findFragmentById(R.id.whatsNewFragment);
mDoYouKnowFragment = (DoYouKnowFragment) mFragmentManager.findFragmentById(R.id.doYouKnowFragment);
On different actions I show one of the fragment and hide all other in FragmentManager transaction:
protected void showFragment(BaseFragment fragment) {
FragmentTransaction transaction = mFragmentManager.beginTransaction();
transaction.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);
BaseFragment visibleFragment = null;
for (BaseFragment fr: mAllFragments) {
if (fragment == fr) {
transaction.show(fr);
visibleFragment = fr;
}
else {
transaction.hide(fr);
}
}
transaction.commit();
if (visibleFragment != null) {
visibleFragment.onShow();
}
}
I know that the other approach is to use newInstance() factory method for getting the fragment refereces.
In that case I suppose I have to set the layout parameters (layout_width, layout_height) by code.
But I think this is the right way if I want to pass initialization paramters to fragment.
So I wonder which approach is better.
And also is keeping references to all fragments is Ok or is better creating during transaction?
Not at all. when you are creating newInstance factory method you do so because you want to pass some arguments from activity to fragment. normally you would do it with constructor but thats not an option when working with fragments. so thats only reason to create factory method for fragments other times you would just call default constructor. now in either case that doesnt mean that you will need to write layout paramets in code. there is nice workaround for that. you will create FrameLayout or any ViewGroup and set its layout parameters in xml. now at some point when you will want to add your fragment you can just add your fragment(or replace) in that ViewGroup. code is as simple as anything can get.
supportFragmentManager
.beginTransaction()
.replace(R.id.your_view_group_id, BadAssFragment.newInstance(someCoolData))
.commit()
I'm learning android fundamentals and I came across this problem while creating my first app. I have an activity which passes on data to a fragment. The OnCreate method of the activity has a block like this:
if(savedInstanceState == null){
DetailActivityFragment detailFrag = DetailActivityFragment.newInstance(movieId);
getSupportFragmentManager().beginTransaction().add(android.R.id.content,detailFrag).commit();
}
setContentView(R.layout.activity_detail);
At the fragment (activity_detail) if I perform getParameters(), I receive null. By playing around, I found that if I remove setContentView method from the snippet above, the fragment shows up with the data. Any ideas as to why that was a problem? Thanks!
Edit: Here is my static newInstance method in the fragment
public static DetailActivityFragment newInstance(String id) {
DetailActivityFragment fragment = new DetailActivityFragment();
Bundle args = new Bundle();
args.putString(Intent.EXTRA_TEXT, id);
fragment.setArguments(args);
return fragment;
}
Here's my fragment from the layout activity_detail:
<fragment android:name="app.appone.DetailActivityFragment"
android:id="#+id/fragment_detail"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
You have to pass the data to your fragment.
Create a static method on your fragment for instance creation. It should look like this:
public static newInstance(Object param) {
DetailActivityFragment yourFragment = new DetailActivityFragment();
Bundle args = new Bundle();
args.put(key, value);
yourFragment.setArguments(args);
return yourFragment;
}
And in your onCreate method of the fragment you can get that data using the method "getArguments();
Your activity code is ok. But I would prefer using "replace" instead of "add" method.
Your latest edit shows you are using a static fragment in your layout xml, but creating it dynamically. A static fragment is created in your xml file:
<fragment android:name="app.appone.DetailActivityFragment"
android:id="#+id/fragment_detail"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
Whereas a dynamic fragment is generated in your code with FragmentManager. It makes sense that calling setContentView() would cause a conflict, as the fragment you are creating with FragmentManager is being replaced by the fragment you are defining in your xml file. The one in your xml, unlike your dynamic fragment, has no arguments, which is why it's returning null.
As you use android.R.id.content, you can remove this static fragment from your xml completely. Replace it with an empty layout, such as FrameLayout, and set an id attribute. Then, when using FragmentManager, replace android.R.id.content for this id.
For example:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/frag_container" />
And in your Activity file:
DetailActivityFragment frag = (DetailActivityFragment) getSupportFragmentManager().findFragmentById(R.id.frag_container);
if (frag == null) {
frag = DetailActivityFragment.newInstance(id);
getSupportFragmentManager()
.beingTransaction()
.add(R.id.frag_container, frag)
.commit();
}
Thanks for your edit. I think you are using the wrong id for fragment replacement.
As in a previous comment you should first set the content view. Your layout file should have a placeholder view, e.g. Framelayout. Give your layout an id and reference this id in your replacement code.
Your "R.layout.activity_detail" should have a layout snippet like this:
<FrameLayout id="+#id/my_detail_frag"/>
And your activity code should look like this:
getSupportFragmentManager().beginTransaction().add(R.id.my_detail_frag,detailFrag).commit();
This answer will do the trick for you:
Best practice for instantiating a new Android Fragment
You should use setArguments() and getArguments() to pass the Bundle into the Fragment.
Good luck!
I have this Activity which at first shows a Fragment with a list of elements. This works perfectly with this code:
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.list_act);
if(null == savedInstanceState)
{
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
ListFragment glfragment = new ListFragment();
fragmentTransaction.add(R.id.listfrag1, glfragment);
fragmentTransaction.commit();
}
}
Well I have a ListFragment and a DetailFragment. But I don't know how to do the transition when I click an element of the list. I know the fragmentTransaction.replace(), but I don't know WHEN to call it.
I thought I should use the OnListItemClick() inside the ListFragment, but I don't know how to use the FragmentManager inside the Fragment and not in the main Activity... Also I want to "export" some data to the DetailFragment as if it was a Intent, but it's not.
To use the fragment manager inside your Fragment, simply call
getActivity().getFragmentManager() instead of getFragmentManager(). Implementing this in your OnItemClickListener should suffice.
What I would do is:
Define an interface with one method listItemSelected() with as an argument the id of the selected item
Let your activity implement this interface
In the onAttach of your list fragment, take the activity and keep it as a member variable, cast to the interface type. Make sure that in the onDetach you dereference it.
In your onListItemClick, call this method on your activity
In the activity, you can now do a new fragmenttransaction, this time you need to replace instead of add the fragment
To create your detail fragment with the correct argument (the id), use the method described here.
This should normally work fine.
I am creating a fragment and adding it to a layout using java code. To do this, i created 2 classes and 2 layouts. One of the classes extends Fragment and other extends FragmentActivity. One of the xml files is the container and other is the fragment. Here is my code:
public class FragmentClass extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_layout, container,false);
return v;
}
}
And here is how i add the fragment to the layout:
public class Fragment_Activity extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.container_layout);
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.container_layout_eklenecek_yer);
if(fragment==null){
fragment=new FragmentClass();
fm.beginTransaction().add(R.id.container_layout_eklenecek_yer, fragment).commit();
}
}
}
This code works as i expect, but here is my question: I have a piece of code in Fragment_Activity class that says:
fragment=new FragmentClass();
and FragmentClass has no constructors. Is a default, empty constructor is called here, or onCreateView works as a constructor? I am confused here.
Thanks
No.
You need a default constructor with fragments, a constructor that takes no arguments is a "default constructor" (this might be c++ terminology) because it allows you to construct an object, for certain.
Because Android might need RAM or something it can kill your fragments, it might also bring them back.
If you pass stuff to the constructor how will the Android OS know that stuff to pass it to you when it needs to re-create the fragment? It doesn't - this question has no answer.
Hence the default constructor.
When re-creating your fragment Android will attach an activity, and you can use onActivityAttached (or something to that tune, look up fragment life-cycles) to get the activity. If you know the activity implements/extends a whatever you can cast that activity to a whatever and store it, whatever you need to do.
The onCreateview method is what the name says: the thing that is called that returns a view. It is called when Android wants the view that represents your fragment, it is NOT a constructor, neither to be thought of, or in actual fact.
I want to pass arguments from my activity to a fragment, embedded into the activity. Fragment is embedded statically in xml layout.
I tried to call setArgument() like this:
setContentView(R.layout.detail_activity);
DetailFragment detailFragment = (DetailFragment) getFragmentManager().findFragmentById(R.id.detailFragment);
detailFragment.setArguments(getIntent().getExtras());
but it is already too late, because setArguments has to be called immediately after fragment's creation. The only was I see it to getArguments() and the change the bundle. Any better way?
AFAIK, you can't use setArguments() like that when you embed the fragment within XML. If it's critical, you'd be better off dynamically adding the fragment instead. However if you truly want the fragment to be embedded via XML, there are different ways you can pass along that data.
Have the Activity implement the fragment's event listener. Have the fragment then request the required parameters from the Activity at creation or whenever needed. Communication with Fragment
Create custom attributes that can be embedded in xml along with the fragment. Then during fragment's inflation process, parse the custom attributes to obtain their data. Custom fragment attributes
Create public setters in the fragment and have the activity use them directly. If it's critical to set them prior to the fragment's onCreate() method, then do it from the activity's onAttachFragment() method.
You have two options here
If you just need information in the activity's intent, then placing information from the intent into the fragment arguments just adds an unneeded step. You might just a well keep things simple and from your fragment call
Bundle data = getActivity().getIntent().getExtras();
If you need to add information that is not in the activity's intent then in you fragment create a no parameter constructor like:
public DetailFragment() {
this.setArguments(new Bundle());
}
then in your activity you can add whatever arguments you need with code like:
DetailFragment frg = (DetailFragment) getFragmentManager().findFragmentById(R.id.detailFragment);
frg.getArguments().putBundle("key", data);
the point here is to use the existing bundle object rather than trying to call setArguments() after the fragment has been attached to the activity.
Another way to pass data to Fragment is as following:
//In DetailFragment (for Instance) define a public static method to get the instance of the fragment
public static final DetailFragment getInstance(Bundle data) {
DetailFragment fragment = new DetailFragment();
fragment.setArguments(data);
return fragment;
}
And when attaching DetailFragment from inside Activity
Bundle data = new Bundle();
//Add data to this bundle and pass it in getInstance() of DetailFragment
fragmentTransaction.replace(R.id.frament_layout, DetailFragment.getInstance(data));