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!
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 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.
My Android app consists three fragments: A, B and C. They're loaded in the two containers defined in the MainActivity layout.
When the app is started, it shows the fragmentA loaded in the left_container and the fragmentC in the right_container.
If you press the button in the fragmentA, a FragmentTransaction changes FragmentC by FragmentB.
At the moment everything OK. But the trouble appears when I try to get a reference to the loaded fragmentB using findFragmentByTag(), because it returns null. I've used the method replace in the FragmentTransaction and I've finished it with commit(), but there isn't way to call FragmentB method. My code:
MainActivity.java:
public class MainActivity extends Activity{
static String fragmentTag = "FRAGMENTB_TAG";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Adds the left container's fragment
getFragmentManager().beginTransaction().add(R.id.left_container, new FragmentA()).commit(); //Adds the fragment A to the left container
//Adds the right container's fragment
getFragmentManager().beginTransaction().add(R.id.right_container, new FragmentC()).commit(); //Adds the Fragment C to the right container
}
/**
* Called when the button "Activate Fragment B" is pressed
*/
public void buttonListener(View v){
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.right_container, new FragmentB(),fragmentTag); //Replaces the Fragment C previously in the right_container with a new Fragment B
ft.commit(); //Finishes the transaction
//!!HERE THE APP CRASHES (java.lang.NullPointerException = findFragmentByTag returns null
((FragmentB) getFragmentManager().findFragmentByTag(fragmentTag)).testView();
}
}
FragmentB.java:
public class FragmentB extends Fragment {
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_b, container,false);
}
/**
* Gets a reference to the text_fragment_b TextView and calls its method setText(), changing "It doesn't work" text by "It works!"
*/
public void testView(){
TextView tv = (TextView)getView().findViewById(R.id.text_fragment_b);
tv.setText("It works!");
}
}
activity_main.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:orientation="horizontal" >
<FrameLayout android:id="#+id/left_container" android:layout_width="0px" android:layout_weight="50" android:layout_height="match_parent"/>
<FrameLayout android:id="#+id/right_container" android:layout_width="0px" android:layout_weight="50" android:layout_height="match_parent"/>
</LinearLayout>
fragment_b.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:orientation="vertical"
android:layout_margin="5sp">
<TextView
android:id="#+id/text_fragment_b"
android:text="It doesn't works!"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
Please help me! I'm a beginner in Android development!
I've fixed it! I called getSupportFragmentManager().executePendingTransactions() after doing the transaction and it worked! After calling that method I can get the fragment using both findFragmentById() and findFragmentByTag() methods.
if you use setRetainInstance(true) than you can't use findFragmentByTag() in onCreate from the Activity. Do it at onResume
see the documentation: setRetainInstance
I'll start by apologising since I'm still very new myself...
I think the problem may be in the declaration of the fragmentTag static String not properly getting access from the class's instances, just change that line to:
private final static String FRAGMENT_TAG = "FRAGMENTB_TAG"; // using uppercase since it's a constant
Also, I would be more explicit when declaring instances, for example:
public void buttonListener(View v){
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.right_container, new FragmentB(), FRAGMENT_TAG);
ft.commit();
FragmentB fragB = (FragmentB) getFragmentManager().findFragmentByTag(FRAGMENT_TAG);
fragB.testView();
}
I hope you get this sorted, as I seen this question posted earlier and was surprised that it hadn't got any activity yet.
Also, here are a couple of links to the android documentation on replace:
Android Training - Replace
Android Reference - Replace
I had the same problem and realized that there is a really simple way to fix this. When using a tag please do make sure to add the
fragmentTransaction.addToBackStack(null);
method so that your Fragment is resumed instead of destroyed as mentioned in the developer guides.
If you don't call addToBackStack() when you perform a transaction that removes a fragment, then that fragment is destroyed when the transaction is committed and the user cannot navigate back to it. Whereas, if you do call addToBackStack() when removing a fragment, then the fragment is stopped and is later resumed if the user navigates back.
You can find this at the end of this section.
Every time I tried to reference back to my created Fragment, it turns out it had already been destroyed so I lost about 30 minutes trying to figure out why my Fragment was not being found through a simple findFragmentByTag(); call.
Hope this helps!
Be sure you are adding or replacing the fragment in the proper way
Next statement will add the fragment but it will return null when using getFragmentManager().findFragmentByTag(tag):
transaction.add(R.id.mainContent, fragment);
This way it will work;
transaction.add(R.id.mainContent, fragment, tag);
We are also seeing this problem but the cause is slightly different. The suggested solution by https://stackoverflow.com/a/21170693/1035008 doesn't work for us.
void updateFragment(Fragment newFragment) {
FragmentTransaction ft = getFragmentManager().beginTransaction();
// We have these 2 lines extra
Fragment current = getChildFragmentManager().findFragmentByTag(fragmentTag);
if (current != null) { ft.remove(current); }
ft.replace(R.id.right_container, newFragment, fragmentTag); //Replaces the Fragment C previously in the right_container with a new Fragment B
ft.commit(); //Finishes the transaction
//!!HERE THE APP CRASHES (java.lang.NullPointerException = findFragmentByTag returns null
((FragmentB) getFragmentManager().findFragmentByTag(fragmentTag)).testView();
}
And after reading the documentation about replace:
Replace an existing fragment that was added to a container. This is essentially the same as calling remove(Fragment) for all currently added fragments that were added with the same containerViewId and then add(int, Fragment, String) with the same arguments given here.
I realize that the remove call was not necessary since it is done by replace automatically. So after delete ft.remove(current), it works fine.
In my case I used the code to replace and add to BackStack, but set wrong tag:
val fragment = { SomeFragment.newInstance() }
fragmentManager?.replaceAndAddToBackStack(R.id.container, fragment, WrongAnotherFragment.TAG)
Of course, supportFragmentManager.findFragmentByTag(SomeFragment.TAG) didn't find SomeFragment.
For me probably it was a newbie mistake that I was calling super.onCreate(savedInstanceState); after I was trying to access the Fragment using findFragmentByTag.
I moved super.onCreate(savedInstanceState) up in the order and it started working for me.
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));
I've got an Activity with a DrawerLayout, using the guidelines from http://developer.android.com/training/implementing-navigation/nav-drawer.html.
When I click on an drawerItem, I replace the current view with the new fragment:
Fragment fragment;
Bundle args = new Bundle();
fragment = new NewsListFragment();
args.putInt("category", position);
// Insert the fragment by replacing any existing fragment
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.content_frame, fragment)
.commit();
mDrawerList.setItemChecked(position, true);
Now, sometimes the old fragment is not replaced but the new fragment is placed on top of the old one:
http://img15.imageshack.us/img15/3179/1kqj.png
Why is this, and how to solve this problem?
Relevant XML:
<!-- The main content view -->
<LinearLayout
android:id="#+id/rlMain"
android:layout_width="fill_parent"
android:orientation="vertical"
android:layout_height="fill_parent">
<FrameLayout
android:id="#+id/content_frame"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1">
</FrameLayout>
This only happens sometimes, and I haven't found a flow to reproduce this yet. The app doesn't support rotating, so it won't happen there.
We went live with this version and havent received any complaints about this, so I will assume this was the correct answer:
in your onCreateView method add:
if (container != null) {
container.removeAllViews();
}
Be sure to check if container is not null!
Thanks https://stackoverflow.com/users/2677588/lia-pronina!
After about 1 week, I found the solution without adding background color or anything else. Just add this code and fix that bullshit. I hope it will help all of you.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
container.clearDisappearingChildren();
return inflater.inflate(R.layout.fragment, container, false);
}
Add in the every layout
android:background="#FFFFFF"
The layouts background in default are transparen, so just put a background color and the new elements fragment, not display over old fragment
let's try with
fragmentManager.beginTransaction().executePendingTransactions();
after you call commit() method
I run in this same problem and I see that there's already an accepted answer but these answer is not 100% right and didn't fix my problem.
The proposed answer by #Niels removes the views but the fragment(s) is(are) still added.
This is what I am using:
/**
* Call this to remove all the other added fragments and keep only the current one.
*
* #param activity the activity to which the fragment has been attached.
* #param fragment the fragment we want to keep.
*/
public static void removeOtherAddedFragments(#NonNull AppCompatActivity activity, #NonNull Fragment fragment) {
FragmentManager fragmentManager = activity.getSupportFragmentManager();
for (Fragment frag : fragmentManager.getFragments()) {
if (frag != null && !frag.equals(fragment) && frag.isAdded()) {
fragmentManager.beginTransaction().remove(frag).commit();
}
}
}
I am calling this in my onResume to be sure that it will be called also when I navigate back to the fragment.
I also encounter this issue I found that we are doing a wrong when replacing fragment
private void changeFragment(Fragment targetFragment){
assert getFragmentManager() != null;
getFragmentManager()
.beginTransaction()
.replace(R.id.main_fragment, targetFragment, "fragment")
.setTransitionStyle(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.commit();
}
this method is replace fragment as other people's code. the reason why fragments are display each other is we define different ID in framelayout in xml. we have to define framelayout IDs(old fragment and new fragment) same.
old framelayout and new framelayout ID in xml for above code should be
<FrameLayout
android:layout_width="match_parent"
android:id="#+id/main_fragment"
android:layout_height="match_parent"/>
Let's say that you have the root fragment A which you add to a container. Next you add fragment B and you set addToBackStack in the transaction. Lastly you add fragment C but you omit addToBackStack. Now when you press the back button you get these fragments on top of each other.
To recap:
Fragment A is added
Fragment B replaces A with addToBackStack
Fragment C replaces B without addToBackStack
Pressing back results in weird overlaid fragments where A lies on top of C.
You could solve this issue by also adding C to the backStack.