Basic communication between two fragments
Asked Answered
L

15

77

I have one activity - MainActivity. Within this activity I have two fragments, both of which I created declaratively within the xml.

I am trying to pass the String of text input by the user into Fragment A to the text view in Fragment B. However, this is proving to be very difficult. Does anyone know how I might achieve this?

I am aware that a fragment can get a reference to it's activity using getActivity(). So I'm guessing I would start there?

Leonor answered 4/12, 2012 at 10:26 Comment(0)
S
127

Have a look at the Android developers page: http://developer.android.com/training/basics/fragments/communicating.html#DefineInterface

Basically, you define an interface in your Fragment A, and let your Activity implement that Interface. Now you can call the interface method in your Fragment, and your Activity will receive the event. Now in your activity, you can call your second Fragment to update the textview with the received value

Your Activity implements your interface (See FragmentA below)

public class YourActivity implements FragmentA.TextClicked{
    @Override
    public void sendText(String text){
        // Get Fragment B
        FraB frag = (FragB)
            getSupportFragmentManager().findFragmentById(R.id.fragment_b);
        frag.updateText(text);
    }
}

Fragment A defines an Interface, and calls the method when needed

public class FragA extends Fragment{

    TextClicked mCallback;

    public interface TextClicked{
        public void sendText(String text);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        // This makes sure that the container activity has implemented
        // the callback interface. If not, it throws an exception
        try {
            mCallback = (TextClicked) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                + " must implement TextClicked");
        }
    }

    public void someMethod(){
        mCallback.sendText("YOUR TEXT");
    }

    @Override
    public void onDetach() {
        mCallback = null; // => avoid leaking, thanks @Deepscorn
        super.onDetach();
    }
}

Fragment B has a public method to do something with the text

public class FragB extends Fragment{

    public void updateText(String text){
        // Here you have it
    }
}
Shopper answered 4/12, 2012 at 10:41 Comment(8)
How about this case, fragment a sent some string to fragment b and fragment b send some result to fragment a, both fragment must be communicate within activity?Loosetongued
@Shopper when i use getactivity in fragment B i get a Null Pointer Exception. Even textview is null in FragB. Any suggestions?Borgeson
@hemanthkumar in that case you probbably have not yet added Fragment B to the activity. You can do this by including it into your activity layout file, or by using the FragmentManager in the activity, and add it there. Good luckShopper
m getting the value in FragB and the value is cleared in onCreateView please helpRazzia
@Shopper I have one doubt. Instead of implementing an interface why not directly call a method of host Activity?Aerostation
@Entreco, "Instead of implementing an interface why not directly call a method of host Activity". Because when using interface your code will be more generic (so, you must not update code of FragmentA if some other activity needs to marshall event in the future)Runoff
@Entreco, +1 for your answer, but you leak activity here. Don't forget: onDetach() { mCallback = null; }Runoff
Can't it be easier by simply creating a method in FragmentA? (Maybe if the data is not changing at all or just put an infinite loop)Ilario
K
37

Some of the other examples (and even the documentation at the time of this writing) use outdated onAttach methods. Here is a full updated example.

enter image description here

Notes

  • You don't want the Fragments talking directly to each other or to the Activity. That ties them to a particular Activity and makes reuse difficult.
  • The solution is to make an callback listener interface that the Activity will implement. When the Fragment wants to send a message to another Fragment or its parent activity, it can do it through the interface.
  • It is ok for the Activity to communicate directly to its child fragment public methods.
  • Thus the Activity serves as the controller, passing messages from one fragment to another.

Code

MainActivity.java

public class MainActivity extends AppCompatActivity implements GreenFragment.OnGreenFragmentListener {

    private static final String BLUE_TAG = "blue";
    private static final String GREEN_TAG = "green";
    BlueFragment mBlueFragment;
    GreenFragment mGreenFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // add fragments
        FragmentManager fragmentManager = getSupportFragmentManager();

        mBlueFragment = (BlueFragment) fragmentManager.findFragmentByTag(BLUE_TAG);
        if (mBlueFragment == null) {
            mBlueFragment = new BlueFragment();
            fragmentManager.beginTransaction().add(R.id.blue_fragment_container, mBlueFragment, BLUE_TAG).commit();
        }

        mGreenFragment = (GreenFragment) fragmentManager.findFragmentByTag(GREEN_TAG);
        if (mGreenFragment == null) {
            mGreenFragment = new GreenFragment();
            fragmentManager.beginTransaction().add(R.id.green_fragment_container, mGreenFragment, GREEN_TAG).commit();
        }
    }

    // The Activity handles receiving a message from one Fragment
    // and passing it on to the other Fragment
    @Override
    public void messageFromGreenFragment(String message) {
        mBlueFragment.youveGotMail(message);
    }
}

GreenFragment.java

public class GreenFragment extends Fragment {

    private OnGreenFragmentListener mCallback;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_green, container, false);

        Button button = v.findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String message = "Hello, Blue! I'm Green.";
                mCallback.messageFromGreenFragment(message);
            }
        });

        return v;
    }

    // This is the interface that the Activity will implement
    // so that this Fragment can communicate with the Activity.
    public interface OnGreenFragmentListener {
        void messageFromGreenFragment(String text);
    }

    // This method insures that the Activity has actually implemented our
    // listener and that it isn't null.
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnGreenFragmentListener) {
            mCallback = (OnGreenFragmentListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnGreenFragmentListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mCallback = null;
    }
}

BlueFragment.java

public class BlueFragment extends Fragment {

    private TextView mTextView;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_blue, container, false);
        mTextView = v.findViewById(R.id.textview);
        return v;
    }

    // This is a public method that the Activity can use to communicate
    // directly with this Fragment
    public void youveGotMail(String message) {
        mTextView.setText(message);
    }
}

XML

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="vertical"
    android:padding="16dp">

    <!-- Green Fragment container -->
    <FrameLayout
        android:id="@+id/green_fragment_container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout_marginBottom="16dp" />

    <!-- Blue Fragment container -->
    <FrameLayout
        android:id="@+id/blue_fragment_container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>

fragment_green.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:background="#98e8ba"
              android:padding="8dp"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <Button
        android:id="@+id/button"
        android:text="send message to blue"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</LinearLayout>

fragment_blue.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:background="#30c9fb"
              android:padding="16dp"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <TextView
        android:id="@+id/textview"
        android:text="TextView"
        android:textSize="24sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</LinearLayout>
Kilkenny answered 23/10, 2017 at 14:48 Comment(1)
If we need to communicate bettween Activity and fragment in two ways, Create and Java interface file instead of declared in Activity or fragment.Milburt
M
14

The nicest and recommended way is to use a shared ViewModel.

https://developer.android.com/topic/libraries/architecture/viewmodel#sharing

From Google doc:

public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

public void select(Item item) {
    selected.setValue(item);
}

public LiveData<Item> getSelected() {
    return selected;
}
}


public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
    itemSelector.setOnClickListener(item -> {
        model.select(item);
    });
}
}


public class DetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
    model.getSelected().observe(this, { item ->
       // Update the UI.
    });
}
}

ps: two fragments never communicate directly

Mohandis answered 5/6, 2018 at 0:30 Comment(2)
I tried this, but as per your example, DetailFragment not observe the change. model.select(item); method is calledAreola
What is itemSelector in MasterFragment?Cassimere
T
4

Consider my 2 fragments A and B, and Suppose I need to pass data from B to A.

Then create an interface in B, and pass the data to the Main Activity. There create another interface and pass data to fragment A.

Sharing a small example:

Fragment A looks like

public class FragmentA extends Fragment implements InterfaceDataCommunicatorFromActivity {
public InterfaceDataCommunicatorFromActivity interfaceDataCommunicatorFromActivity;
String data;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    return super.onCreateView(inflater, container, savedInstanceState);
}

@Override
public void updateData(String data) {
    // TODO Auto-generated method stub
    this.data = data;
    //data is updated here which is from fragment B
}

@Override
public void onAttach(Activity activity) {
    // TODO Auto-generated method stub
    super.onAttach(activity);
    try {
        interfaceDataCommunicatorFromActivity = (InterfaceDataCommunicatorFromActivity) activity;
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString()
                + " must implement TextClicked");
    }

}

}

FragmentB looks like

class FragmentB extends Fragment {
public InterfaceDataCommunicator interfaceDataCommunicator;

@Override
public void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);

    // call this inorder to send Data to interface
    interfaceDataCommunicator.updateData("data");
}

public interface InterfaceDataCommunicator {
    public void updateData(String data);
}

@Override
public void onAttach(Activity activity) {
    // TODO Auto-generated method stub
    super.onAttach(activity);
    try {
        interfaceDataCommunicator = (InterfaceDataCommunicator) activity;
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString()
                + " must implement TextClicked");
    }

}

}

Main Activity is

public class MainActivity extends Activity implements InterfaceDataCommunicator {
public InterfaceDataCommunicatorFromActivity interfaceDataCommunicatorFromActivity;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
}

@Override
public void updateData(String data) {
    // TODO Auto-generated method stub
    interfaceDataCommunicatorFromActivity.updateData(data);

}

public interface InterfaceDataCommunicatorFromActivity {
    public void updateData(String data);
}

}
Teleplay answered 30/10, 2013 at 9:59 Comment(1)
Why is variable differente for FragmentB when compared to FragmentA and MainActivity? FragmentB is InterfaceDataCommunicator, but for FragmentA and MainActivity is InterfaceDataCommunicatorFromActivity.Reagan
E
3

There are multiple ways to communicate between fragments.

  • Traditional way of communication via interface Example
  • Via ViewModel if you are following MVVM pattern Example
  • BroadcastReceivers: via LocalBraodcastManager Example or EventBus Example etc...
Erdmann answered 6/7, 2021 at 7:9 Comment(0)
W
1

Take a look at https://github.com/greenrobot/EventBus or http://square.github.io/otto/

or even ... http://nerds.weddingpartyapp.com/tech/2014/12/24/implementing-an-event-bus-with-rxjava-rxbus/

Washwoman answered 31/8, 2015 at 9:22 Comment(1)
Roboguice is an unnecessary extra overhead for this very simple scenario.Nightwalker
H
1

There is a simple way to implement communication between fragments of an activity using architectural components. Data can be passed between fragments of an activity using ViewModel and LiveData.

Fragments involved in communication need to use the same view model objects which is tied to activity life cycle. The view model object contains livedata object to which data is passed by one fragment and the second fragment listens for changes on LiveData and receives the data sent from fragment one.

For complete example see http://www.zoftino.com/passing-data-between-android-fragments-using-viewmodel

Homs answered 18/8, 2018 at 15:58 Comment(0)
S
1

Since Fragment 1.3.0 we have available a new way to communicate between fragments.

As of Fragment 1.3.0, each FragmentManager implements FragmentResultOwner. That means that a FragmentManager can act as a central storage for fragment results. This change allows components to communicate with each other by setting chunk results and listening to those results without those components having direct references to each other.

Fragment listener:

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Use the Kotlin extension in the fragment-ktx artifact
setFragmentResultListener("requestKey") { requestKey, bundle ->
    // We use a String here, but any type that can be put in a Bundle is supported
    val result = bundle.getString("bundleKey")
    // Do something with the result
 }
}

Fragment emitter:

button.setOnClickListener {
val result = "result"
// Use the Kotlin extension in the fragment-ktx artifact
setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}
Spokeshave answered 16/11, 2021 at 15:24 Comment(1)
Hi.It worked but in my case I had to make minor change .from another stackoverflow post: "In the case of passing data from Child to Parent fragment, using the childFragmentManager is the key when setting the resultListener on the parent fragment" I had to use: childFragmentManager.setFragmentResultListenerBatfowl
Y
0

Learn " setTargetFragment() "

Where " startActivityForResult() " establishes a relationship between 2 activities, " setTargetFragment() " defines the caller/called relationship between 2 fragments.

Yearling answered 18/8, 2018 at 20:6 Comment(0)
H
0

I give my activity an interface that all the fragments can then use. If you have have many fragments on the same activity, this saves a lot of code re-writing and is a cleaner solution / more modular than making an individual interface for each fragment with similar functions. I also like how it is modular. The downside, is that some fragments will have access to functions they don't need.

    public class MyActivity extends AppCompatActivity
    implements MyActivityInterface {

        private List<String> mData; 

        @Override
        public List<String> getData(){return mData;}

        @Override
        public void setData(List<String> data){mData = data;}
    }


    public interface MyActivityInterface {

        List<String> getData(); 
        void setData(List<String> data);
    }

    public class MyFragment extends Fragment {

         private MyActivityInterface mActivity; 
         private List<String> activityData; 

         public void onButtonPress(){
              activityData = mActivity.getData()
         }

        @Override
        public void onAttach(Context context) {
            super.onAttach(context);
            if (context instanceof MyActivityInterface) {
                mActivity = (MyActivityInterface) context;
            } else {
                throw new RuntimeException(context.toString()
                        + " must implement MyActivityInterface");
            }
        }

        @Override
        public void onDetach() {
            super.onDetach();
            mActivity = null;
        }
    } 
Hole answered 14/8, 2019 at 15:33 Comment(0)
J
0

You can user 2 approcach to communicate between 2 fragments:

1 )

You can use LiveData to observe data changes of one fragment in another

Create shared ViewModel

public class SharedViewModel extends ViewModel {

private MutableLiveData<String> name;

public void setNameData(String nameData) {
    name.setValue(nameData);
}

public MutableLiveData<String> getNameData() {
    if (name == null) {
        name = new MutableLiveData<>();
    }

    return name;
}
}

Fragment One

 private SharedViewModel sharedViewModel;

 public FragmentOne() {

 }

 @Override
 public void onCreate(@Nullable Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);

     sharedViewModel = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
     submitButton.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View view) {

            sharedViewModel.setNameData(submitText.getText().toString());
           }
      });

 }

Fragment Two

 private SharedViewModel sharedViewModel;

 public FragmentTwo() {

 }

 @Override
 public void onCreate(@Nullable Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);

     sharedViewModel = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);

     sharedViewModel.getNameData().observe(this, nameObserver);
 }

 Observer<String> nameObserver = new Observer<String>() {
    @Override
    public void onChanged(String name) {
       receivedText.setText(name);
    }
 };

For more details on viewmodel you can refer to : mvvm-viewmodel-livedata , communicate fragments

2 )

You can use eventbus to achieve the same

implementation 'org.greenrobot:eventbus:3.2'

Define Event

public static class MessageEvent { /* Additional fields if needed */ }

Register/Unregister Subsciber

 @Override
 public void onStart() {
     super.onStart();
     EventBus.getDefault().register(this);
 }

 @Override
 public void onStop() {
     super.onStop();
     EventBus.getDefault().unregister(this);
 }

Listen To Events

@Subscribe(threadMode = ThreadMode.MAIN)  
public void onMessageEvent(MessageEvent event) {/* Do something */};

Post Events

 EventBus.getDefault().post(new MessageEvent());
Jarib answered 7/10, 2021 at 8:37 Comment(0)
P
0

Basically, following are the ways for communication between two fragments:

i) ViewModel
ii) Fragment Result API
iii) Interface
Predisposition answered 2/2, 2022 at 12:29 Comment(0)
B
0

I use many fragments on tabs that need to share data between them, such as a ble scan tab that needs up update a device id on a settings tab.
The communication is a mess for something simple like one edittext. My solution was to save data to sharedpreferences and use the fragment onResume to read and update. I can extend the fields in Sharedpreferences later if I need to as well.

Bashemeth answered 22/2, 2023 at 17:39 Comment(0)
A
-1

Update

Ignore this answer. Not that it doesn't work. But there are better methods available. Moreover, Android emphatically discourage direct communication between fragments. See official doc. Thanks user @Wahib Ul Haq for the tip.

Original Answer

Well, you can create a private variable and setter in Fragment B, and set the value from Fragment A itself,

FragmentB.java

private String inputString;
....
....

public void setInputString(String string){
   inputString = string;
}

FragmentA.java

//go to fragment B

FragmentB frag  = new FragmentB();
frag.setInputString(YOUR_STRING);
//create your fragment transaction object, set animation etc
fragTrans.replace(ITS_ARGUMENTS)

Or you can use Activity as you suggested in question..

Adriatic answered 4/12, 2012 at 10:33 Comment(1)
That’s now how you should do it because it violates a fundamental concept of modularity. Android documentations says : > Two Fragments should never communicate directly. Fragments should be unaware of each other, be self-contained and work independently.Ganja
G
-1

I recently created a library that uses annotations to generate those type casting boilerplate code for you. https://github.com/zeroarst/callbackfragment

Here is an example. Click a TextView on DialogFragment triggers a callback to MainActivity in onTextClicked then grab the MyFagment instance to interact with.

public class MainActivity extends AppCompatActivity implements MyFragment.FragmentCallback, MyDialogFragment.DialogListener {

private static final String MY_FRAGM = "MY_FRAGMENT";
private static final String MY_DIALOG_FRAGM = "MY_DIALOG_FRAGMENT";

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    getSupportFragmentManager().beginTransaction()
        .add(R.id.lo_fragm_container, MyFragmentCallbackable.create(), MY_FRAGM)
        .commit();

    findViewById(R.id.bt).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            MyDialogFragmentCallbackable.create().show(getSupportFragmentManager(), MY_DIALOG_FRAGM);
        }
    });
}

Toast mToast;

@Override
public void onClickButton(MyFragment fragment) {
    if (mToast != null)
        mToast.cancel();
    mToast = Toast.makeText(this, "Callback from " + fragment.getTag() + " to " + this.getClass().getSimpleName(), Toast.LENGTH_SHORT);
    mToast.show();
}

@Override
public void onTextClicked(MyDialogFragment fragment) {
    MyFragment myFragm = (MyFragment) getSupportFragmentManager().findFragmentByTag(MY_FRAGM);
    if (myFragm != null) {
        myFragm.updateText("Callback from " + fragment.getTag() + " to " + myFragm.getTag());
    }
}

}

Gebelein answered 30/5, 2017 at 4:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.