71

I've got an activity that I've replaced with a fragment. The activity took an Intent that had some extra information on what data the activity was supposed to display.

Now that my Activity is just a wrapper around a Fragment that does the same work, how do I get that bundle to the Fragment if I declare the fragment in XML with the tag?

If I were to use a FragmentTransaction to put the Fragment into a ViewGroup, I'd get a chance to pass this info along in the Fragment constructor, but I'm wondering about the situation where the fragment is defined in XML.

Plantage
  • 1,373
  • 2
  • 12
  • 8

6 Answers6

52

Now that my Activity is just a wrapper around a Fragment that does the same work, how do I get that bundle to the Fragment if I declare the fragment in XML with the tag?

You can't.

However, you are welcome to call findFragmentById() on your FragmentManager to retrieve the fragment post-inflation, then call some method on the fragment to associate data with it. While apparently that cannot be setArguments(), your fragment could arrange to hold onto the data itself past a configuration change by some other means (onSaveInstanceState(), setRetainInstance(true), etc.).

CommonsWare
  • 910,778
  • 176
  • 2,215
  • 2,253
  • When I asked this question, I decided to go a different route. But just today, I had a similar situation, and came back to this post. I thought I'd give it a try. The setArguments solution doesn't seem to work: 10-24 12:48:33.276: E/AndroidRuntime(21417): Caused by: java.lang.IllegalStateException: Fragment already active Going to try just calling a method on the Fragment. – Plantage Oct 24 '13 at 17:50
  • I ran into the same IllegalStateException the other day. The problem seems to be that you need to call `setContentView()` in order for the Fragments to be inflated. But `setContentView()` also attaches them to the Activity which makes it too late to call `setArguments()`. – Michael Jan 24 '14 at 18:44
  • 3
    This should not be marked as correct. It is incorrect. According to the fragment documentation (http://developer.android.com/reference/android/app/Fragment.html#setArguments(android.os.Bundle), setArguments() must be called before the fragment has been attached to the activity. If you can find the fragment via findFragmentById() then the fragment has already been attached. See http://stackoverflow.com/questions/21403040/android-fragment-declared-in-layout-how-to-set-arguments?lq=1 for the correct solution. – Neil Sainsbury Mar 18 '14 at 00:13
  • @Neil: I can understand the source of your confusion. I have made a small edit to clarify the timing of the `setArguments()` call. – CommonsWare Mar 18 '14 at 00:51
  • @CommonsWare I'm still not sure if that's correct. Your edit says "do this before executing the FragmentTransaction" which isn't applicable here because the fragment is declared in an XML layout and there is no fragment instance to interact with. – Neil Sainsbury Mar 19 '14 at 02:08
  • @Neil: You are correct, insofar as my edit was wrong. Remind me never to make an edit to an answer from my phone. However, your linked-to answer is irrelevant, as it too does not address fragment inflation, other than to say that you can't use it. – CommonsWare Mar 19 '14 at 11:25
  • 6
    See http://stackoverflow.com/questions/18124150/set-arguments-of-fragment-from-activity for options on how to pass data to an XML defined Fragment. – TheIT Aug 12 '14 at 23:37
  • To resolve the issues with `setArguments()`, you **could** `@Override` it in your `Fragment` subclass. Deal with the arguments directly and don't chain to `super`. Now no more `IllegalStateException`. Of course, writing your own method and casting to your subclass to call it is also a decent solution. – Code-Apprentice Jun 05 '15 at 17:28
  • You are right, you cannot pass a bundle, but you're answer is missleading cause what people usually want is just pass values to their fragments. I added an answer showing how to do so via attributes – Daniele Segato Sep 13 '18 at 07:46
46

It's not an encapsulated way, but I ended up "pulling" the bundle from the parent activity:

Bundle bundle = getActivity().getIntent().getExtras();
Oded Breiner
  • 25,024
  • 9
  • 99
  • 66
13

Another option is to not declare the fragment in the XML. I know it is not exactly what you want to do. However you could declare a simple layout in your view like this:

    <LinearLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" />

And then in your Activity class you programatically inflate the layout with the fragment. This way you can pass through parameters using args.

FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
MyFragment fragment = MyFragment.newInstance();
Bundle args = new Bundle();
args.putInt(Global.INTENT_INT_ROLE, 1);
fragment.setArguments(args);
fragmentTransaction.add(R.id.fragment_container, fragment, "MyActivity");
fragmentTransaction.commit();

In the fragment,

if (getArguments() != null) {
   int role = getArguments().getInt(Global.INTENT_INT_ROLE); }

This approach is not as clean and simple as declaring it in the xml however I have moved to it as it gives you a lot more control over the fragment.

Fattie
  • 30,632
  • 54
  • 336
  • 607
Zapnologica
  • 20,003
  • 39
  • 136
  • 229
  • This is a bit of a "then don't do that" approach, however it makes more sense to me than the idea of exchanging data via singleton objects (as another answer suggests) – Konrad Morawski Feb 03 '16 at 14:25
  • If the singleton objects are not backed by a persisting database, then Android can destroy your apps process in the background and try to recreate its former state later from the fragment arguments or the bundle. Singletons wont get restored and app state will be lost, this leads to bad UX - try to avoid doing that by either using fragment arguments or backing your singletons up by a database. – A. Steenbergen May 14 '18 at 13:23
  • Your answers ARE SO HELPFUL !!!!!!!!! WHAAAAAA ! https://tenor.com/search/thank-you-anime-gifs – Fattie Apr 12 '21 at 19:54
  • @A.Steenbergen sure, but a common situation is if the agrs don't exist, perform some default; if they do exist use that. – Fattie Apr 12 '21 at 19:56
13

You can't pass a Bundle (unless you inflate your fragment programmatically rather then via XML) but you CAN pass parameters (or rather attributes) via XML to a fragment.

The process is similar to how you define View custom attributes. Except AndroidStudio (currently) do not assist you in the process.

suppose this is your fragment using arguments (I'll use kotlin but it totally works in Java too):

class MyFragment: Fragment() {

    // your fragment parameter, a string
    private var screenName: String? = null

    override fun onAttach(context: Context?) {
        super.onAttach(context)
        if (screenName == null) {
            screenName = arguments?.getString("screen_name")
        }
    }
}

And you want to do something like this:

<fragment
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/myFragment"
    android:name="com.example.MyFragment"
    app:screen_name="@string/screen_a"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

Note the app:screen_name="@string/screen_a"

to make it work just add this in a values file (fragment_attrs.xml or pick any name you want):

<!-- define your attribute name and type -->
<attr name="screen_name" format="string|reference"/>

<!-- define a bunch of constants you wanna use -->
<string name="screen_a" translatable="false">ScreenA</string>
<string name="screen_b" translatable="false">ScreeenB</string>

<!-- now define which arguments your fragment is gonna have (can be more then one) -->
<!-- the convention is "FragmentClassName_MembersInjector" -->
<declare-styleable name="MyFragment_MembersInjector">
    <attr name="screen_name"/>
</declare-styleable>

Almost done, you just need to read it in your fragment, so add the method:

override fun onInflate(context: Context?, attrs: AttributeSet?, savedInstanceState: Bundle?) {
    super.onInflate(context, attrs, savedInstanceState)
    if (context != null && attrs != null && screenName == null) {
        val ta = context.obtainStyledAttributes(attrs, R.styleable.MyFragment_MembersInjector)
        if (ta.hasValue(R.styleable.MyFragment_MembersInjector_screen_name)) {
            screenName = ta.getString(R.styleable.MyFragment_MembersInjector_screen_name)
        }
        ta.recycle()
    }
}

et voilá, your XML attributes in your fragment :)

Limitations:

  • Android Studio (as of now) do not autocomplete such arguments in the layout XML
  • You can't pass Parcelable but only what can be defined as Android Attributes
Daniele Segato
  • 10,371
  • 4
  • 55
  • 80
8

I know its too late answer, but i think someone need that :)

Just in activity override onAttachFragment()

@Override
public void onAttachFragment(Fragment fragment)
{
    super.onAttachFragment(fragment);

    if (fragment.getId() == R.id.frgBlank)
    {
        Bundle b = new Bundle();
        b.putString("msg", "Message");

        fragment.setArguments(b);
    }
}

and in fragment onCreateView method

Bundle b = getArguments();
if (b != null)
{
    Toast.makeText(getBaseContext(), b.getString("msg"), Toast.LENGTH_SHORT).show();
}
A S A D I
  • 131
  • 2
  • 8
4

The only solution I see is to not use the arguments as data exchange channel. Instead, make your fragment to obtain the necessary information from elsewhere. Call back to get the proper activity, consult a temporary storage memory, a Singleton object, etc..

Another solution that can be helpful is to employ frameworks that allow unrelated objects to exchange messages via Mediator design pattern, as Otto.

Renascienza
  • 1,627
  • 1
  • 12
  • 14