Связь между вложенными фрагментами в Android

Недавно я узнал как делать вложенные фрагменты в Android. Однако я не знаю, как должно случиться.

введите описание изображения здесь

Из чтения документации по фрагментации фрагментов Я знаю, что

Обмен данными между фрагментами и фрагментами осуществляется через связанные Мероприятия. Два фрагмента никогда не должны связываться напрямую.

Это имеет смысл для фрагментов сестры в рамках действия, но это не имеет большого значения для связи между родительским и дочерним фрагментами. Нужно ли мне полностью перейти к Управлению только для детского фрагмента, чтобы поговорить с родительским фрагментом? Если ответ - это просто "да", то я могу это сделать. Если это "нет", то как выглядит код?

Я вижу в документацию Nested Fragment, что можно использовать getParentFragment(), чтобы получить ссылку на родительский фрагмент. Значит ли это, что ребенок должен напрямую общаться с родителем? Это кажется противоположным тому, что рекомендуется с нормальным фрагментом, взаимодействующим с родительской активностью.

Ответы

Ответ 1

Следуя совету Rahul Sharma в комментариях, я использовал обратные вызовы интерфейса для связи с детским фрагментом с родительским фрагментом и активностью. Я также отправил этот ответ в обзор кода. Я беру там не ответ (на момент написания этой статьи), чтобы быть признаком отсутствия серьезных проблем с этим шаблоном проектирования. Мне кажется, что я согласен с общим руководством, приведенным в официальных документах .

Пример проекта

В следующем примере проект расширяет пример, указанный в вопросе. У этого есть кнопки, которые инициируют восходящую связь от фрагментов к активности и от Детского Фрагмента до родительского фрагмента.

Я установил макет проекта следующим образом:

введите описание изображения здесь

Основная деятельность

Активность реализует слушателей из обоих фрагментов, чтобы они могли получать от них сообщения.

Необязательный TODO: если Activity хочет инициировать связь с фрагментами, он может просто получить прямую ссылку на них, а затем вызвать один из своих общедоступных методов.

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

Родительский фрагмент

Родительский фрагмент реализует слушателя из детского фрагмента, чтобы он мог получать от него сообщения.

Необязательный TODO: если родительский фрагмент хотел инициировать связь с фрагментом ребенка, он мог просто получить прямую ссылку на него, а затем вызвать один из своих общедоступных методов.

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

}

Детский фрагмент

Детский фрагмент определяет интерфейсы слушателя как для Activity, так и для родительского фрагмента. Если Детский фрагмент нужен только для связи с одним из них, тогда можно удалить другой интерфейс.

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

}

Ответ 2

Хотя ответ @Suragch правильный, но я хочу добавить другой способ передачи данных между Fragments или Activity. Независимо от того, что это Activity или Fragment, вы можете передавать данные с шиной событий в 3 этапа:

1- Определить событие (сообщение):

public class OrderMessage {
    private final long orderId;
    /* Additional fields if needed */
    public OrderMessage(long orderId) {
        this.orderId = orderId;
    }
    public long getOrderId() {
        return orderId;
    }
}

2- Регистрация и отмена регистрации для событий: Чтобы иметь возможность принимать события, класс должен регистрироваться/отменить регистрацию для шины событий. Лучшее место для Activities и Fragments составляет onStart() и onStop()

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

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

Чтобы иметь возможность получать событие, вы должны подписаться на это событие. Для этого добавьте аннотацию @Subscribe к одному из ваших методов в вашем классе.

@Subscribe(threadMode = ThreadMode.MAIN)
   public void onMessage(OrderMessage message){
       /* Do something for example: */
       getContractDetails(message.getOrderId());
   }

3- Опубликовать событие

EventBus.getDefault().post(new OrderMessage(recievedDataFromWeb.getOrderId()));

Более подробную документацию и примеры можно найти Здесь. Существуют также другие библиотеки, такие как: Otto

Ответ 3

С выпуском компонентов архитектуры вы, вероятно, должны взглянуть на компонент архитектуры viewmodel.
В сочетании с живыми данными вы легко сможете общаться между произвольно вложенными фрагментами. Вы также можете взглянуть на todoapp и на то, как они обрабатывают события там.