简体   繁体   中英

If I declare a fragment in an XML layout, how do I pass it a Bundle?

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.

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.).

这不是封装的方式,但我最终从父活动“拉”了包:

Bundle bundle = getActivity().getIntent().getExtras();

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

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.

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();
}

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 .

this approach worked for me.

You will not pass the Bundle from Anywhere, but instead you can set the arguments in onAttach method in the fragment itself.

and later in the lifecycle methods of the fragments you can use those bundle arguments.

override fun onAttach(context: Context) {
        super.onAttach(context)
        if(arguments == null){
            val bundle = Bundle()
            bundle.putString("mykey", "myvalue")
            arguments = bundle
        }
    }

Anyone might ask a question, why to set the arguments in the fragment while we can directly use the values in the usable places. It's right, but This approach can also work when you will be passing these arguments to some other classes let's say any view model.

for example

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        /*
         Here I am passing these arguments to a viewmodel 
        */
        viewModel.prepareData(arguments) 
        
        --------
        --------
        --------

    }

Thanks.

Since v1.3.0 of the androidx.fragment module (Feb 2021), they deprecated the onAttachFragment() callbacks in favor of using a FragmentOnAttachListener , which is attached to your FragmentManager . So inside your FragmentActivity 's onCreate , you could do something like:

getSupportFragmentManager().addFragmentOnAttachListener((fragmentManager, fragment) -> {
    if (fragment instanceof MyFragment) {
        Bundle args = getIntent().getExtras();
        fragment.setArguments(args);
    }
}

This will fire after setContentView() if you use <fragment> in your XML, but happens later if you used a <FragmentContainerView> .

Be careful, as I've noticed that a configuration change could re-attach an existing Fragment inside super.onCreate , and therefore needed to add the listener before this point. (That said, this isn't necessary for the purpose of passing Bundle args, and otherwise one should try to use onSaveInstanceState() first.)


Incidentally, listening for the "onAttach" could also be a handy place to add an observer of the Fragment's view Lifecycle if needed, should you need to do anything in the Activity once the Fragment's view is instantiated. eg add:

        LiveData<LifecycleOwner> liveData = fragment.getViewLifecycleOwnerLiveData();
        // Let this observer be constrained to the fragment's lifecycle.
        liveData.observe(fragment, new Observer<LifecycleOwner>() {
            @Override
            public void onChanged(LifecycleOwner lifecycleOwner) {
                // ...do work...
                // If you don't need to listen anymore, can clean ourselves up early.
                liveData.removeObserver(this);
            }
        });

Or as an alternative to observing the Fragment's view lifecycle from the Activity (and as a replacement for the Fragment's deprecated onActivityCreated() ), you can listen the other way, to the Activity's Lifecycle from the Fragment! From the Fragment's onAttach(context) callback, add your own observer that implements onCreate(@NonNull LifecycleOwner owner) . re: onActivityCreated is deprecated, how to properly use LifecycleObserver?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM