프레그먼트 통신(Fragment Commnication)

안드로이드 공식 문서인 Communicate with other fragments에 프레그먼트간 통신하는 방법에 대해서잘 나와있지만, 저는 또 다른 방법들에대해서 설명하고자 합니다.

AAC ViewModel 이용

ViewModel을 이용하면 Activity를 라이플싸이클 오너로 등록하여 공통된 뷰모델을 이용할 수 있습니다. 

app레벨의  build.gradle에 다음과 같이 의존성을 추가합니다.

//LiveData and ViewModel
def lifecycle_version = "1.1.1"
implementation "android.arch.lifecycle:extensions:$lifecycle_version"

의존성이 추가되었다면 ViewModel 클래스를 이용할 수 있습니다.

public class SharedViewModel extends ViewModel {

    private MutableLiveData<String> liveText = new MutableLiveData<>();

    public LiveData<String> getText(){
        return liveText;
    }

    public void setText(String text){
        liveText.setValue(text);
    }

}

FragmentB에서 FragmentA로 text를 전달하기 위해서, 저는 ViewModel에 옵저버블 객체로 LiveData를 선택했지만 다른 데이터타입을 선택하셔도 좋습니다.

public class FragmentB extends Fragment {

    private SharedViewModel sharedViewModel;
    private EditText editText;

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

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        editText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void afterTextChanged(Editable s) {
                if(sharedViewModel!=null){
                    sharedViewModel.setText(s.toString());
                }
            }
        });
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        sharedViewModel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
    }
}

FragmentA 에서는 FragmentB가 입력한 데이터를 받아서 TextView를 갱신 할 겁니다.
Activity를 오너로 등록하기 위해 Activity생성 지점인 onActivityCreated에서 sharedViewModel 인스턴스를 얻습니다.
뷰모델을 통해 LiveData<String>타입인 텍스트를 얻어 observe합니다.

public class FragmentA extends Fragment {
    private SharedViewModel sharedViewModel;
    private TextView textView;

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

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        sharedViewModel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        sharedViewModel.getText().observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                textView.setText(s);
            }
        });
    }
}

 

FragmentB는 SharedViewModel을 얻어 데이터를 발행할 준비를 합니다. EditText의 TextWatcher를 붙여서 텍스트가 입력되는것을 감지하고, 변경된 값을 SharedViewModel에 있는 LiveData에 발행합니다.

 

Interface 이용한 리스너 구현

공식문서에 나와있는 패턴입니다. 

인터페이스 하나와 onTextChagne라는 메소드를 선언합니다.

public interface FragmentListener {
    void onTextChange(String s);
}

FragmentB2에서 텍스트가 변경 되면 이 인터페이스를 통해 데이터를 통지할 것입니다.

public class FragmentB2 extends Fragment {

    private EditText editText;
    private FragmentListener fragmentListener;

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

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        editText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void afterTextChanged(Editable s) {
                if(fragmentListener!=null){
                    fragmentListener.onTextChange(s.toString());
                }
            }
        });
    }

    public void setFragmentListener(FragmentListener listener){
        this.fragmentListener = listener;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if(getActivity() instanceof FragmentListener){
            this.fragmentListener = (FragmentListener) getActivity();
        }
    }
}

Activity를 인터페이스 구현체로 하여 값이 변경 되었을 때 리스너를 통해 데이터의 변경을 통지 합니다.

public class InterfaceExamActivity extends AppCompatActivity implements FragmentListener{

    FragmentA2 fragmentA2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_interface_exam);
        fragmentA2 = (FragmentA2) getSupportFragmentManager().findFragmentById(R.id.fragmentA);
    }


    @Override
    public void onTextChange(String s) {
        if(fragmentA2!=null){
            fragmentA2.setText(s);
        }
    }
}

액티비티에서는 FragmentB2에서 받은 데이터를 FragmentA2 setText메소드를 통해 전달 합니다.

public class FragmentA2 extends Fragment {
    private TextView textView;

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

    public void setText(String s) {
        if(textView!=null){
            textView.setText(s);
        }
    }
}

 

RxJava2를 이용한 EventBus 만들기

public class RxEventBus {
    private static final RxEventBus instance = new RxEventBus();

    public static RxEventBus getInstance(){
        return instance;
    }

    private PublishSubject<Object> bus = PublishSubject.create();

    private RxEventBus(){
    }

    public void send(Object o) {
        bus.onNext(o);
    }

    public Observable<Object> toObservable() {
        return bus;
    }

RxJava2의 PublishSubject를 이용한 이벤트 버스를 하나 만듭니다. 저는 싱글톤패턴으로 만들었지만, 꼭 그럴필요는 없습니다.

send메소드를 통해 데이터를 발행하고
toObservable메소드를 통해 데이터를 구독할 것입니다.

RxJava2에 대한 내용은 RxJava2 포스트를 확인해주세요.

public class FragmentB3 extends Fragment {

    private EditText editText;

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

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        editText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void afterTextChanged(Editable s) {
                RxEventBus.getInstance().send(s.toString());
            }
        });
    }
}

FragmentB3에서는 이전과 마찬가지로 EditText의 변화를 감지하여 RxEventBus로 데이터를 발행하는 모습입니다.

public class FragmentA3 extends Fragment {
    private TextView textView;

    private CompositeDisposable disposables = new CompositeDisposable();

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

        Disposable disposable = RxEventBus.getInstance().toObservable().subscribe(new Consumer<Object>() {
            @Override
            public void accept(Object o) throws Exception {
                textView.setText((String)o);
            }
        }, new Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) throws Exception {

            }
        });
        disposables.add(disposable);
        return view;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        if(disposables.isDisposed()){
            disposables.dispose();
        }
    }
}

FragmentA3에서는 이벤트 버스의 옵저버블객체를 구독하고 데이터를 받습니다.
Note : RxJava의 subscribe메소드는 disposable객체를 반환하는데, 이를 dispose하지 않으면 메모리 누수로 이어지니 꼭 dispose해야합니다.

이 예제는 github에서 확인 가능합니다.


0개의 댓글

답글 남기기

Avatar placeholder

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.