Skip to content

When calling a method between fragments, must it go through an activity?

I want to open a dialog fragment from A fragment, and pass data to A fragment when the dialog fragment ends.

To do this, I defined an interface in the dialog fragment and implemented it in the A fragment.

The result was that the listener was null.

This is because onAttach() used context and context meant activity.

The activity did not implement the interface.

Of course, it might have been nice to implement the interface in the activity, but I didn’t want it because the method role didn’t match the activity.

Is there no way?

WriteRoutineFragment.java (A Fragment)

public class WriteRoutineFragment extends Fragment implements 
        WorkoutListDialogFragment.OnAddRoutineListener {

    RecyclerView routine_rv;
    List<RoutineModel> items;
    List<String> titleData;
    RoutineListAdapter listAdapter;


    public static WriteRoutineFragment newInstance(Bundle data) {
        WriteRoutineFragment f = new WriteRoutineFragment();
        f.setArguments(data);
        return f;
    }

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

        titleData = getArguments().getStringArrayList("title");
        listAdapter = new RoutineListAdapter(diffUtil2);
        items = new ArrayList<>();
//        routineAdapter = new RoutineAdapter();
        routine_rv.setAdapter(listAdapter);

        // ADD Routine
        listAdapter.setOnAddRoutineClickListener(new RoutineListAdapter.OnAddRoutineClickListener() {
            @Override
            public void onAddRoutineClick() {
                WorkoutListDialogFragment routineDialog = new WorkoutListDialogFragment();
                routineDialog.show(getActivity().getSupportFragmentManager(), "RoutineListDialog");
            }
        });

        return rootView;
    }

    @Override
    public void onAddRoutine(String routine) {
        RoutineModel routineModel = new RoutineModel(routine);
        RoutineDetailModel routineDetailModel = new RoutineDetailModel();
        routineModel.addDetail(routineDetailModel);

        items.add(routineModel);
        List<Object> list = getUpdatedList();
        listAdapter.submitList(list);
    }
}

DialogFragment.java

public class WorkoutListDialogFragment extends DialogFragment {
    static final String TAG = "RoutineListDialog";
    WorkoutListAdapter workoutListAdapter;
    OnAddRoutineListener listener;
    
    public interface OnAddRoutineListener {
        public void onAddRoutine(String routine);
    }

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        if(context != null && context instanceof OnAddRoutineListener) {
            listener = (OnAddRoutineListener) context; // null
        }
        return;
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_workout_list_dialog, container, false);
        routineListRecycler = view.findViewById(R.id.routine_list_recycler);
        rountineChipGroup = view.findViewById(R.id.routine_chipgroup);

        workoutListAdapter.setOnWorkoutListClickListener(new WorkoutListAdapter.OnWorkoutListItemClickListener() {
            @Override
            public void OnItemClick(String routine) {
                if(listener != null) {
                    listener.onAddRoutine(routine);
                    dismiss();
                }
            }
        });
        return view;
    }
}

Answer

To handle this kind of problem Live Data comes to play. By using Live Data we can pass data between fragments with out any issue. Just create a View Model like below:

    public class RoutineViewModel extends ViewModel {

    private MutableLiveData<String> mRoutine;

    public RoutineViewModel() {
        mRoutine = new MutableLiveData();
    }
    public void setRoutine(String data){
        mRoutine.setValue(data);
    }

    public LiveData<String> getRoutine(){ return mRoutine;}
}

And use this View Model in both the fragments in order to get the latest updated value of Live Data.

Using View Model and Live Data in WriteRoutineFragment looks like below:

private RoutineViewModel routineViewModel;

public View onCreateView(@NonNull LayoutInflater inflater,
                         ViewGroup container, Bundle savedInstanceState) {
    routineViewModel =
            new ViewModelProvider(requireActivity()).get(RoutineViewModel.class);
    View root = inflater.inflate(R.layout.fragment_home, container, false);

    routineViewModel.getRoutine().observe(getViewLifecycleOwner(), new Observer<String>() {
        @Override
        public void onChanged(String s) {
            Log.d("TAG==>>","Routine changed = "+s);
        }
    });
    listAdapter.setOnAddRoutineClickListener(new RoutineListAdapter.OnAddRoutineClickListener() {
        @Override
        public void onAddRoutineClick() {
            WorkoutListDialogFragment routineDialog = new WorkoutListDialogFragment();
            routineDialog.show(getChildFragmentManager(), "RoutineListDialog");
        }
    });

    return root;
}

Using View Model and Live Data in WorkoutListDialogFragment looks like below:

    public class WorkoutListDialogFragment extends DialogFragment {
    private RoutineViewModel routineViewModel;
    WorkoutListAdapter workoutListAdapter; 
    public WorkoutListDialogFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        routineViewModel =
                new ViewModelProvider(requireActivity()).get(RoutineViewModel.class);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_workout_list_dialog, container, false);
    }
    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState){
        workoutListAdapter.setOnWorkoutListClickListener(new WorkoutListAdapter.OnWorkoutListItemClickListener() {
            @Override
            public void OnItemClick(String routine) {
                routineViewModel.setRoutine(routine);
                dismiss();
            }
        });
    }
}