Fragment-Fragment Communication in Android

Basic communication between two fragments

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
}
}

Fragment-Fragment communication in Android

It should be done thought listener, so Fragments are still not depend on each other and can be used in one or two pane mode. Activity should handle listeners of both fragments.

Here is an example of activity with two Fragments:

package com.example;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;

import com.example.fragment.FragmentA;
import com.example.fragment.FragmentA.TextChangeListener;
import com.example.fragment.FragmentB;

public class ActivityAB extends FragmentActivity {

FragmentA fragmentA;
FragmentB fragmentB;

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

FragmentManager manager = getSupportFragmentManager();
fragmentA = (FragmentA) manager.findFragmentById(R.id.fragmentA);
fragmentB = (FragmentB) manager.findFragmentById(R.id.fragmentB);

fragmentA.setTextChangeListener(new TextChangeListener() {

@Override
public void onTextChange(CharSequence newText) {
fragmentB.updateTextValue(newText);
}
});
}

}

Here is Fragment A, that has custom listener for text change event.

package com.example.fragment;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

import com.example.R;

public class FragmentA extends Fragment {

TextChangeListener listener;

public interface TextChangeListener {
public void onTextChange(CharSequence newText);
}

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

Button btn = (Button) view.findViewById(R.id.button1);
final TextView textView = (TextView) view.findViewById(R.id.textView1);

btn.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
if (null != listener) {
listener.onTextChange(textView.getText());
}

}
});
return view;
}

public void setTextChangeListener(TextChangeListener listener) {
this.listener = listener;
}
}

Here is Fragment B that has public method to update text field:

package com.example.fragment;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.example.R;

public class FragmentB extends Fragment {

TextView textView;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_b, container, false);
textView = (TextView) view.findViewById(R.id.textView1);
return view;
}

public void updateTextValue(CharSequence newText) {
textView.setText(newText);
}
}

ActivityAB xml Layout:

<?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:baselineAligned="false"
android:orientation="horizontal" >

<fragment
android:id="@+id/fragmentA"
android:name="com.example.fragment.FragmentA"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />

<fragment
android:id="@+id/fragmentB"
android:name="com.example.fragment.FragmentB"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />

</LinearLayout>

Fragment A xml layout:

<?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" >

<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is text"
android:textAppearance="?android:attr/textAppearanceLarge" />

<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Show" />

</LinearLayout>

Fragment B xml layout:

<?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" >

<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="(here will be text)"
android:textAppearance="?android:attr/textAppearanceLarge" />

</LinearLayout>

Communication between two fragments with interface

I would recommend putting the code for replacing the fragment in your Activity and put the values which should be passed to SFragment() in its arguments directly:

SFragment newFragment = new SFragment();
Bundle args = new Bundle();
args.putString("place1", data1);
args.putString("place2", data1);
newFragment.setArguments(args);

You could get them in SFragment#onCreateView()

String place1 = getArguments().getString("place1");

Also have a look here: https://developer.android.com/training/basics/fragments/communicating.html

How to communicate between fragments?

When communicating from Fragment to Fragment you use an interface to pass data to the Activity which in turn updates the fragment you want to change.

For Example:

In Fragment 1:

public class FragmentOne extends Fragment{

public Callback mCallback;

public interface Callback{
void onUpdateFragmentTwo(String message);
}

@Override
public void onAttach(Activity activity){
super.onAttach(activity);
mCallback = (Callback) activity;
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v=inflater.inflate(R.layout.fragone, container,false);
Button btn = (Button) v.findViewById(R.id.button1);
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mCallback.onUpdateFragmentTwo("clicked");
}
});
return v;
}
}

then in main Activity implement the interface:

public class MainActivity extends AppCompatActivity implements Callback{

FragmentTwo fragmentTwo;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

// ... Load views or perform logic

// ... Load Fragment Two into your container
if(savedInstanceState == null){
fragmentTwo = FragmentTwo.newInstance(new Bundle()); // use real bundle here
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragment_holder, fragmentTwo, "Frag2").commit();
}
}

// Interface method
@Override
public void onUpdateFragmentTwo(String message){
// Call activity method with the argument
if(fragmentTwo != null){
fragmentTwo.updateFragmentTwo(message);
}
}

}

Update

In your second fragment I typically use a static newInstance(Bundle args) method to initialize and then would use a public method to communicate from the Activity to the Fragment for example:

 public class FragmentTwo extends Fragment{

public static FragmentTwo newInstance(Bundle args){
FragmentTwo fragment = new FragmentTwo();
fragment.setArguments(args);
return fragment;
}

//... Class overrides here onCreateView etc..

// declare this method
public void updateFragmentTwo(String updateText){
// .. do something with update text
}

}

Thats it, happy coding!

Parent Fragment communication with fragment inside viewpager

Create an interface inside your FragmentA

interface OnSpinnerValue{
fun onSpinnerValueChanged()
}

Create a WeakReference for the current selected fragment

private var _currentPage: WeakReference<OnSpinnerValue>? = null
private val currentPage
get() = _currentPage?.get()

fun setCurrentPage(page: OnSpinnerValue) {
_currentPage = WeakReference(page)
}

Now implement this interface in every child fragment of ViewPager

class Fragment1() : Fragment(), OnAddEvent {
override fun onSpinnerValueChanged() {
// implement your method
}
}

And, update currentPage value of the FragmentA, according to the selected fragment, and update it in onResume() of each child fragment

override fun onResume() {
super.onResume()
(parentFragment as FragmentA).setCurrentPage(this)
}

Now, trigger onSpinnerValueChanged from your spinner's onItemSelected methods

override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
currentPage?.onSpinnerValueChanged()
}

Kotlin: communication between different fragments

You can create a shared ViewModel to communicate between fragments. Create a ViewModel and access them using the hosting Activity's context inside each of the fragments.

Here is an example copied from the ViewModel documentation here: https://developer.android.com/topic/libraries/architecture/viewmodel

class SharedViewModel : ViewModel() {
val selected = MutableLiveData<Item>()

fun select(item: Item) {
selected.value = item
}
}

class MasterFragment : Fragment() {

private lateinit var itemSelector: Selector

private lateinit var model: SharedViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
ViewModelProviders.of(this).get(SharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")

itemSelector.setOnClickListener { item ->
model.select(item) // <-- This will notify the `DetailFragment`
}
}
}

class DetailFragment : Fragment() {

private lateinit var model: SharedViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
ViewModelProviders.of(this).get(SharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")

model.selected.observe(this, Observer<Item> { item ->
// Update the UI
})
}
}

Here, the SharedViewModel is accessed in both MasterFragment and the DetailFragment. Both are accessing the same instance of the SharedViewModel because both of them access the ViewModel from the Activity's Context:

ViewModelProviders.of(*ACTIVITY*).get(SharedViewModel::class.java)

Now you can include some LiveData in the SharedViewModel and both fragments can listen / update them, which is eventually reflected on the other fragment as well.

Communication between Fragments without Using Interface

Have a look at the Android deverlopers 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

  // You Activity implements your interface
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
}
}

Communication between nested fragments in Android

Following Rahul Sharma's advice in the comments, I used interface callbacks to communicate up from the Child Fragment to the Parent Fragment and to the Activity. I also submitted this answer to Code Review. I am taking the non-answer there (at the time of this writing) to be a sign that there are no major problems with this design pattern. It seems to me to be consistent with the general guidance given in the official fragment communication docs.

Example project

The following example project expands the example given in the question. It has buttons that initiate upward communication from the fragments to the activity and from the Child Fragment to the Parent Fragment.

I set up the project layout like this:

Sample Image

Main Activity

The Activity implements the listeners from both fragments so that it can get messages from them.

Optional TODO: If the Activity wanted to initiate communication with the fragments, it could just get a direct reference to them and then call one of their public methods.

import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity implements ParentFragment.OnFragmentInteractionListener, ChildFragment.OnChildFragmentToActivityInteractionListener {

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

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.parent_fragment_container, new ParentFragment());
ft.commit();
}

@Override
public void messageFromParentFragmentToActivity(String myString) {
Log.i("TAG", myString);
}

@Override
public void messageFromChildFragmentToActivity(String myString) {
Log.i("TAG", myString);
}
}

Parent Fragment

The Parent Fragment implements the listener from the Child Fragment so that it can receive messages from it.

Optional TODO: If the Parent Fragment wanted to initiate communication with the Child Fragment, it could just get a direct reference to it and then call one of its public methods.

import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class ParentFragment extends Fragment implements View.OnClickListener, ChildFragment.OnChildFragmentInteractionListener {

// **************** start interesting part ************************

private OnFragmentInteractionListener mListener;

@Override
public void onClick(View v) {
mListener.messageFromParentFragmentToActivity("I am the parent fragment.");
}

@Override
public void messageFromChildToParent(String myString) {
Log.i("TAG", myString);
}

public interface OnFragmentInteractionListener {
void messageFromParentFragmentToActivity(String myString);
}

// **************** end interesting part ************************

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

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_parent, container, false);
view.findViewById(R.id.parent_fragment_button).setOnClickListener(this);
return view;
}

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
Fragment childFragment = new ChildFragment();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.replace(R.id.child_fragment_container, childFragment).commit();
}

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

}

Child Fragment

The Child Fragment defines listener interfaces for both the Activity and for the Parent Fragment. If the Child Fragment only needed to communicate with one of them, then the other interface could be removed.

import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class ChildFragment extends Fragment implements View.OnClickListener {

// **************** start interesting part ************************

private OnChildFragmentToActivityInteractionListener mActivityListener;
private OnChildFragmentInteractionListener mParentListener;

@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.child_fragment_contact_activity_button:
mActivityListener.messageFromChildFragmentToActivity("Hello, Activity. I am the child fragment.");
break;
case R.id.child_fragment_contact_parent_button:
mParentListener.messageFromChildToParent("Hello, parent. I am your child.");
break;
}
}

public interface OnChildFragmentToActivityInteractionListener {
void messageFromChildFragmentToActivity(String myString);
}

public interface OnChildFragmentInteractionListener {
void messageFromChildToParent(String myString);
}

@Override
public void onAttach(Context context) {
super.onAttach(context);

// check if Activity implements listener
if (context instanceof OnChildFragmentToActivityInteractionListener) {
mActivityListener = (OnChildFragmentToActivityInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnChildFragmentToActivityInteractionListener");
}

// check if parent Fragment implements listener
if (getParentFragment() instanceof OnChildFragmentInteractionListener) {
mParentListener = (OnChildFragmentInteractionListener) getParentFragment();
} else {
throw new RuntimeException("The parent fragment must implement OnChildFragmentInteractionListener");
}
}

// **************** end interesting part ************************

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_child, container, false);
view.findViewById(R.id.child_fragment_contact_activity_button).setOnClickListener(this);
view.findViewById(R.id.child_fragment_contact_parent_button).setOnClickListener(this);
return view;
}

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

}

Fragment to fragment communication within a NavigationDrawer

The following code takes care of replacing your Fragment when the user clicks on one of the navigation items

// Insert the fragment by replacing any existing fragment
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.flContent, fragment).commit();

Besides replacing the fragment you will want to also update the title of your activity using something along the lines of:

((AppCompatActivity)getActivity()).getSupportActionBar().setTitle("Home");

And finally you must tell your NavigationView to highlight the right item using the setChecked(boolean) function. It should look something like this:

nvDrawer.getMenu().findItem(/*your item id*/).setChecked(true);

If you want to perform the same action when the user clicks on a menu item then make sure to add a new case to the switch block found in your onOptionsItemSelected(MenuItem item) and just call the same lines passing in the right Fragment



Related Topics



Leave a reply



Submit