Fragment에 대한 이야기

게시일:

OverView

Activity는 ForeGround에서 UI로 사용자와 상호작용을 위한 컴포넌트이다. Activity의 종류에는 FragmentActivity라는 파생된 클래스가 존재하는데 Fragment는 이 Activity의 특정 부분의 동작이나 UI를 담당한다. 프래그먼트는 Activity에 종속적이고 결합되어 사용이 되는데, 하나의 부분에서 여러 개의 Fragment을 사용하거나 여러 Activity에서 하나의 프래그먼트를 사용할 수 있다. 그렇기 때문에 Activity의 LifeCycle에 직접적인 영향을 받게 되며 이런 Fragment 또한 본인만의 Life Cycle을 가지고 있다.

한 부분에서 여러 Fragment들이 번갈아가며 실행되기도 하는데 이런 트랜잭션이 수행되면 Activity의 Stack에 프래그먼트에 대한 정보가 추가되는 것이 가능하다. Fragment를 쉽게 그려보자면 Activity가 안경테라면 각각의 Fragment는 안경알이 되어 색안경처럼 갈아끼울 수 있다고 생각하면 된다.

프래그먼트라는 개념이 생긴 것은 태블릿과 같은 큰 화면의 디바이스가 추가되기 시작될 무렵이라고 한다.

fragment_device

화면의 크기가 크다보니 유연하게 UI에 대한 구성을 바꿀 필요가 있었고 뷰 계층을 조작하기 보다 각각의 프래그먼트를 구성함으로써 외관을 여러 프래그먼트로 쪼개서 나눔으로써 UI를 수정할 수 있게 한 것이다.

이런 특징때문에 Fragment는 모듈과 같이 재사용이 가능하도록 디자인이 되어야 된다. 이를 위해 각각의 Fragment는 고유한 Life Cycle을 가지게 되는 것이고, 이렇기에 Fragment간의 내용 변경들은 좋은 코드가 아니라고 할 수 있다.

LifeCycle

Fragment의 LifeCycle은 Activity와 비슷한 순서로 이루어져있지만 세부적인 여러 단계가 추가된 형태이다. onCreate, onStart, onResume, onPause, onStop, onDestroy 또한 존재하지만 onAttach와 같은 여러 세부단계가 추가된 형태이다.

fragment_lifecycle

onAttach

Fragment를 Activity에 추가할 때 호출되는 콜백이다. Fragment는 Activity에 부착되어야 하기 때문에 Activity에 대한 검증이 필요한데 해당 작업이 onAttach를 통해 이루어진다.

@Override
public void onAttach(@NonNull Context context) {
    super.onAttach(context);
    Log.e(TAG, "onAttach");
}

개발자의 입장에서 onAttach에서 중요하게 하는 작업이 하나 존재한다. 바로 Callback을 정의하는 것이다. 호스팅할 Activity에 인터페이스를 구현하게 한 뒤 fragment에서 onAttach 콜백이 불리는 순간에 이에 대한 인스턴스를 생성하는 것이다.

FragmentCallback callback;

public interface FragmentCallback{
    public void onImageChanged();
}

@Override
public void onAttach(@NonNull Context context) {
    super.onAttach(context);
    Log.e(TAG, "onAttach");

    try{
        callback = (FragmentCallback) context;
    } catch(ClassCastException e){
        e.printStackTrace();
    }
}

콜백을 줄 수 있으므로 Activity와 Fragment는 이벤트에 대한 공유가 가능하다.

public class FragmentActivity extends androidx.fragment.app.FragmentActivity implements SimpleFragment.FragmentCallback{
	...

	@Override
    public void onImageChanged() {
        Log.e(TAG, "onImageChanged");
    }

}

물론 Interface에 대한 메소드 구현이 Activity 쪽에서 이루어져야만 한다. 하지만 덕분에 Activity에서 Fragment에서 수행된 결과 또는 행위에 대한 정보를 알 수 있게 되며 그에 대한 처리가 수월해진다.

onCreate

Activty의 onCreate와 동일한 역할을 하는데 보통 onCreateView 및 onViewCreated에서 레이아웃과 관련된 작업들을 수행한다. 왜 그런지를 알아보았더니 Activity의 onCreate메소드가 완료되지 않은 상태에서 수행될 수도 있기 때문에 UI와 관련된 작업을 이 부분에서 수행하게 되면 crash가 발생할 수 있다고 한다. 그래서 보통 이 부분에서는 그래픽적인 요소 또는 계층 구조와 관련이 없는 값들에 대한 처리가 이루어지고 Intent로부터 데이터를 가져오거나 변수를 할당하고 초기화하는 코드들이 위치한다고 한다.

private int init = -1;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.e(TAG, "onCreate");

    // non-graphical
    init = 1;
}

onCreateView

onCreateView는 Fragment가 사용자 인터페이스를 그려야 되는 순간에 호출되는 콜백이다. Activity의 onCreate에서와 같이 layout에 해당되는 View에 대한 반환이 이루어지는 순간이기도 하며 이 View는 Fragment Layout의 root가 된다.

두번째 파라미터로 전달되는 ViewGroup인 container는 상위 ViewGroup인 Activity의 Layout으로, Fragment의 layout이 그 하위에 삽입된다.

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    super.onCreateView(inflater, container, savedInstanceState);
    Log.e(TAG, "onCreateView");
    return inflater.inflate(R.layout.fragment_simple, container, false);
}

findViewById 등을 통한 변수에 대한 초기화를 해당 부분에서 초기화를 하지 않는데 view에 대한 초기화가 완전히 종료되었음이 보장되지 않기 대문이다. 그렇기 때문에 UI 요소의 초기화에 대한 작업은 onViewCreated 부분에서 해주는 것이 좋다.

onViewCreated

onViewCreated는 onCreateView가 호출이 완료되고 나면 바로 호출이 되는 메소드로 onCreateView가 null을 리턴할 경우에는 호출이 되지 않는다는 특징이 있다. View에 대한 inflate 작업의 완료가 보장되었기 때문에 이 부분에서 여러 버튼과 같은 UI 요소들에 대한 초기화를 수행한다.

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    Log.e(TAG, "onViewCreated");
    super.onViewCreated(view, savedInstanceState);

    imageView = (ImageView) view.findViewById(R.id.imageView);
    imageView.setImageDrawable(getResources().getDrawable(R.drawable.ic_launcher_foreground));
    callback.onImageChanged();
}

예제에서는 imageView의 이미지를 변경하고 난 뒤 onAttach에서 정의한 callback 인스턴스를 통해 Activity에 정의되어 있는 onImageChanged을 호출한다. 이를 통해 Activity는 Fragment에서 Image 정보에 대한 변경이 있었음을 알 수 있다.

onActivityCreated

Activity가 onCreate에 대한 작업을 완료하고 반환하는 경우에 호출되는 콜백으로 이 부분에서 getActivity를 통해 activity와 관련된 작업들을 수행하는 것이 좋다.

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    Log.e(TAG, "onActivityCreated");

    View frame = getActivity().findViewById(R.id.fragmentB);
    if(frame != null){
        frame.setVisibility(View.GONE);
    }
}

Activity를 구성할 때 FrameLayout을 2개를 생성하여서 weight를 1로 주어 각각 1:1 비율의 공간을 차지하도록 하였었는데, onActivityCreated를 통해 Activity의 fragment를 가져와 setVisibility로 GONE을 해줌으로써 공간을 혼자 사용할 수 있도록 변경하는 예제를 만들어보았다.

View frame = getActivity().findViewById(R.id.fragmentB);
if(frame != null){
    ImageView imgView = frame.findViewById(R.id.imageView);
    if(imgView != null){
        imgView.setImageDrawable(getResources().getDrawable(R.drawable.ic_launcher_background));
    }
}

물론 Fragment 내부의 변수에 접근하여 작업을 하는 것 또한 가능하다.

위와 같이 Activity에 대한 onCreate 작업이 마무리 된 시점에서 가져오는 것이 안정성이 보장되기 때문에 onActivityCreated 부분에서 수행해주는 것이 좋다.

onViewStateRestored

Fragment가 처음 생성된 경우 또는 reattached가 된 경우 등의 상태에서만 불리며 restart 등의 상황에서는 불리지 않는다. onRestoreInstanceState와 비슷한 역할을 수행한다고 보면 된다. 해당 부분에서 savedInstanceState의 값들을 복구해주면 된다.

onStart, onResume, onPause, onStop, onDestroy

유저에게 Fragment가 보이게 되는 순간부터 제거가 되는 순간으로 Activity의 Life Cycle과 동일한 역할을 한다. 그리고 Activity와 동일한 시점에 호출이 된다.

2020-06-18 06:34:40.443 25734-25734/wizley.android.playground E/FragmentActivity: onCreate
2020-06-18 06:34:40.499 25734-25734/wizley.android.playground E/SimpleFragment: onAttach
2020-06-18 06:34:40.499 25734-25734/wizley.android.playground E/FragmentActivity: onAttachFragment
2020-06-18 06:34:40.499 25734-25734/wizley.android.playground E/SimpleFragment: onCreate
2020-06-18 06:34:40.500 25734-25734/wizley.android.playground E/SimpleFragment: onCreateView
2020-06-18 06:34:40.514 25734-25734/wizley.android.playground E/SimpleFragment: onViewCreated
2020-06-18 06:34:40.515 25734-25734/wizley.android.playground E/FragmentActivity: onImageChanged
2020-06-18 06:34:40.515 25734-25734/wizley.android.playground E/SimpleFragment: onActivityCreated
2020-06-18 06:34:40.515 25734-25734/wizley.android.playground E/SimpleFragment: onViewStateRestored
2020-06-18 06:34:40.516 25734-25734/wizley.android.playground E/SimpleFragment: onStart
2020-06-18 06:34:40.516 25734-25734/wizley.android.playground E/FragmentActivity: onStart

lifecycle

Google Developer Blog 부분의 그림에 잘 설명되어 있다.

onDestroyView

Fragment와 연결된 View의 계층이 제거되기 시작하는 순간에 호출되는 콜백이다. 화면 전환등으로 View를 다시 구성해야 되는 순간에도 호출이 되며 onCreateView 상태로 돌아가게 된다.

onDetach

더이상 Activity와 Fragment의 연결이 필요하지 않은 순간에 호출된다. 제거하는 작업의 마지막 부분에 해당된다고 보면 된다.

화면 전환

하나의 Fragment와 Activity로 화면전환에 관한 과정을 Log로 찍어보았다.

2020-06-18 06:49:43.115 29928-29928/wizley.android.playground E/FragmentActivity: onCreate
2020-06-18 06:49:43.193 29928-29928/wizley.android.playground E/SimpleFragment: onAttach
2020-06-18 06:49:43.193 29928-29928/wizley.android.playground E/FragmentActivity: onAttachFragment
2020-06-18 06:49:43.194 29928-29928/wizley.android.playground E/SimpleFragment: onCreate
2020-06-18 06:49:43.194 29928-29928/wizley.android.playground E/SimpleFragment: onCreateView
2020-06-18 06:49:43.211 29928-29928/wizley.android.playground E/SimpleFragment: onViewCreated
2020-06-18 06:49:43.213 29928-29928/wizley.android.playground E/FragmentActivity: onImageChanged
2020-06-18 06:49:43.213 29928-29928/wizley.android.playground E/SimpleFragment: onActivityCreated
2020-06-18 06:49:43.213 29928-29928/wizley.android.playground E/SimpleFragment: onViewStateRestored
2020-06-18 06:49:43.214 29928-29928/wizley.android.playground E/SimpleFragment: onStart
2020-06-18 06:49:43.214 29928-29928/wizley.android.playground E/FragmentActivity: onStart
2020-06-18 06:49:43.216 29928-29928/wizley.android.playground E/FragmentActivity: onResume
2020-06-18 06:49:43.216 29928-29928/wizley.android.playground E/SimpleFragment: onResume

윗 부분이 바로 Fragment와 Activity가 inflate되고 onResume으로 사용자와 상호작용을 하는 그 순간까지의 과정을 담고 있다.

2020-06-18 06:49:46.559 29928-29928/wizley.android.playground E/SimpleFragment: onPause
2020-06-18 06:49:46.559 29928-29928/wizley.android.playground E/FragmentActivity: onPause
2020-06-18 06:49:46.581 29928-29928/wizley.android.playground E/SimpleFragment: onStop
2020-06-18 06:49:46.581 29928-29928/wizley.android.playground E/FragmentActivity: onStop
2020-06-18 06:49:46.585 29928-29928/wizley.android.playground E/SimpleFragment: onDestroyView
2020-06-18 06:49:46.590 29928-29928/wizley.android.playground E/SimpleFragment: onDestroy
2020-06-18 06:49:46.590 29928-29928/wizley.android.playground E/SimpleFragment: onDetach
2020-06-18 06:49:46.591 29928-29928/wizley.android.playground E/FragmentActivity: onDestroy

화면 전환이 요청 된 순간 view에 대한 재생성을 하기위해 onPause -> onStop -> onDestroy가 호출이 된다. 그리고 Fragment는 onStop -> onDestroy의 과정의 중간에 onDestroyView가 호출이 되며 onDestroy가 호출이 된 후에 onDetach를 통해 Activity와 연결을 끊는 작업을 호출하는 것을 알 수 있다. 이 작업이 마무리 되면 Activity가 onDestroy를 호출해준다.

2020-06-18 06:49:46.613 29928-29928/wizley.android.playground E/SimpleFragment: onAttach
2020-06-18 06:49:46.613 29928-29928/wizley.android.playground E/FragmentActivity: onAttachFragment
2020-06-18 06:49:46.613 29928-29928/wizley.android.playground E/SimpleFragment: onCreate
2020-06-18 06:49:46.613 29928-29928/wizley.android.playground E/FragmentActivity: onCreate
2020-06-18 06:49:46.629 29928-29928/wizley.android.playground E/SimpleFragment: onCreateView
2020-06-18 06:49:46.630 29928-29928/wizley.android.playground E/SimpleFragment: onViewCreated
2020-06-18 06:49:46.631 29928-29928/wizley.android.playground E/FragmentActivity: onImageChanged
2020-06-18 06:49:46.631 29928-29928/wizley.android.playground E/SimpleFragment: onActivityCreated
2020-06-18 06:49:46.631 29928-29928/wizley.android.playground E/SimpleFragment: onViewStateRestored
2020-06-18 06:49:46.633 29928-29928/wizley.android.playground E/SimpleFragment: onStart
2020-06-18 06:49:46.633 29928-29928/wizley.android.playground E/FragmentActivity: onStart
2020-06-18 06:49:46.634 29928-29928/wizley.android.playground E/FragmentActivity: onResume
2020-06-18 06:49:46.635 29928-29928/wizley.android.playground E/SimpleFragment: onResume

그리고 다시 Life Cycle에 의거하여 onAttach -> onAttachFragment -> onCreate -> onCreateView -> onViewCreated -> (onActivityCreated) -> onViewStateRestored -> onStart -> onResume 순으로 호출이 된 것을 확인할 수 있다.

Transaction

replace

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

        if(savedInstanceState != null){
            return;
        }

        button = findViewById(R.id.button);

        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        fragmentA = new SimpleFragment();
        Bundle bundle = new Bundle();
        bundle.putString("tag", "A");
        fragmentA.setArguments(bundle);

        fragmentB = new SimpleFragment();
        Bundle bundle2 = new Bundle();
        bundle2.putString("tag", "B");
        fragmentB.setArguments(bundle2);

        fragmentTransaction.add(R.id.fragmentA, fragmentA, "A");
        fragmentTransaction.addToBackStack("A");
        fragmentTransaction.commit();

        button.setOnClickListener(this);
    }

이번에는 하나의 FrameLayout을 갈아끼우는 예제이다. R.id.FragmentA 부분의 초기값은 fragmentA이다.

@Override
public void onClick(View v) {
    if(v.getId() == R.id.button){
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        int idx = getSupportFragmentManager().getBackStackEntryCount()-1;
        String tag = getSupportFragmentManager().getBackStackEntryAt(idx).getName();

        if(tag.equals("A")){
            fragmentTransaction.replace(R.id.fragmentA, fragmentB, "B");
            fragmentTransaction.addToBackStack("B");
        } else if(tag.equals("B")){
            fragmentTransaction.replace(R.id.fragmentA, fragmentA, "A");
            fragmentTransaction.addToBackStack("A");
        }
        fragmentTransaction.commit();
    }
}

Button 이벤트가 발생하게 되면 BackStack으로부터 getName을 통해 TAG 값을 가져오게 되는데 그 값을 토대로 비교를 하여 A일 경우는 B를, B일 경우는 A에 해당하는 fragment로 갈아끼우게 된다. 그 과정에서 replace가 사용되며 addToBackStack을 통해 replace된 기록이 Stack에 쌓이게 되며 사용자에 의해 back 버튼이벤트가 발생하면 그 전 상태로 돌아가게 된다.

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.e(TAG, "onCreate");

    Bundle arguments = getArguments();
    if(arguments != null){
        tag = arguments.getString("tag");
        Log.e(TAG, tag);
    }

    // non-graphical
    init = 1;
}

두 Fragment는 구조는 같고 imageView에 표시되는 이미지 종류를 다르게 하였는데 이를 위해서 Fragment의 onCreate 부분에서 getArguments를 통해 tag값을 초기화 하여 Drawable에서 다른 이미지를 set하게 한다.

    if(tag!= null && tag.equals("A")) {
        imageView.setImageDrawable(getResources().getDrawable(R.drawable.ic_launcher_foreground));
    } else if(tag!= null && tag.equals("B")){
        imageView.setImageDrawable(getResources().getDrawable(R.drawable.ic_launcher_background));
    }
    callback.onImageChanged();

위에서 설명했듯이 Fragment의 onCreate는 UI와 관련되지 않은 작업만 수행해주는 것이 좋기 때문에 onViewCreated부분에서 아래와 같이 처리를 해주었다.

2020-06-19 17:57:25.668 7582-7582/wizley.android.playground E/SimpleFragment: onPause
2020-06-19 17:57:25.668 7582-7582/wizley.android.playground E/SimpleFragment: onStop
2020-06-19 17:57:25.672 7582-7582/wizley.android.playground E/SimpleFragment: onDestroyView
2020-06-19 17:57:25.673 7582-7582/wizley.android.playground E/SimpleFragment: onCreateView
2020-06-19 17:57:25.674 7582-7582/wizley.android.playground E/SimpleFragment: onViewCreated
2020-06-19 17:57:25.675 7582-7582/wizley.android.playground E/FragmentActivity: onImageChanged
2020-06-19 17:57:25.675 7582-7582/wizley.android.playground E/SimpleFragment: onActivityCreated
2020-06-19 17:57:25.675 7582-7582/wizley.android.playground E/SimpleFragment: onViewStateRestored
2020-06-19 17:57:25.675 7582-7582/wizley.android.playground E/SimpleFragment: onStart
2020-06-19 17:57:25.676 7582-7582/wizley.android.playground E/SimpleFragment: onResume

이번에는 Life Cycle을 살펴보았다. A와 B모두 생성이 완료되었다는 가정하에 A->B로 replace가 수행되면 A가 onPause->onStop->onDestroyView를 순차적으로 호출을 하며 B가 onCreateView->onViewCreated->onViewStateRestored->onStart->onResume 순서로 수행된 것을 확인할 수 있다.

FragmentResultListener

androidx.fragment:fragment:1.3.0-alpha04에서부터 FragmentResultListener가 추가되었는데 FragmentManager가 프래그먼트 들의 중간 저장소 역할을 할 수 있다. 즉, fragmentA와 fragmentB가 직접적으로 통신을 하지 않고 Activity의 fragmentManager를 통해서 정보에 대한 전송 및 리시브가 가능하다는 것이다.

listener

Goodle Developer Docs의 그림을 가져와보았다. 여기서 FragmentManager는 fragmentA와 fragmentB를 add한 activity의 manager가 되고 fragmentA는 receiver, fragmentB는 sender가 된다.

sender인 fragmentB가 Bundle에 결과를 저장하여 setFragmentResult를 parentFragmentManager에게 전송하면 receiver인 fragmentA에 설치된 listener 콜백이 requestKey를 확인해서 본인이 받아야 될 정보이면 receive를 하게 된다.

public class FragmentActivity extends androidx.fragment.app.FragmentActivity {
    private static final String TAG = "FragmentActivity";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fragment);
        Log.e(TAG, "onCreate");

        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        FragmentA fragmentA = new FragmentA();
        FragmentB fragmentB = new FragmentB();

        fragmentTransaction.add(R.id.fragmentA, fragmentA, "A");
        fragmentTransaction.add(R.id.fragmentB, fragmentB, "B");
        fragmentTransaction.commit();
    }
}

Activity는 간단하게 fragmentA와 fragmentB를 transaction에서 add로 추가하고 commit을 수행한다.

// androidx.fragment:fragment:1.3.0-alpha04
public class FragmentA extends Fragment {
    private static final String TAG = "FragmentA";

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.e(TAG, "onCreate");
        
        getParentFragmentManager().setFragmentResultListener("requestKey", this, new androidx.fragment.app.FragmentResultListener() {
            @Override
            public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle bundle) {
                String result = bundle.getString("bundleKey");
                Log.e(TAG, "onFragmentResult");
                assert result != null;
                Log.e(TAG, result);
            }
        });
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return super.onCreateView(inflater, container, savedInstanceState);
    }
}

FragmentA의 onCreate 부분에서는 parentManager에 listener를 추가하는데 requestKey가 requestKey라는 이름으로 오는 경우에 onFragmentResult로 값을 받게 된다. 위의 코드에서는 Bundle로부터 bundleKey값을 가져와 로그를 찍어주는 행위를 수행한다.

// androidx.fragment:fragment:1.3.0-alpha04
public class FragmentB extends Fragment {
    private static final String TAG = "FragmentB";

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.e(TAG, "onCreate");

        Bundle result = new Bundle();
        result.putString("bundleKey", "This is FragmentResultListener");

        Log.e(TAG, "setFragmentResult");
        getParentFragmentManager().setFragmentResult("requestKey", result);
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return super.onCreateView(inflater, container, savedInstanceState);
    }
}

FragmentB는 데이터를 전송하는 주체로 onCreate 부분에서 Bundle에 bundleKey 에 대한 값을 넣어준뒤 ParentManager를 통해 setFragmentResult를 호출하는데 그 requestKey를 requestKey로 전송한다. 이렇게 전송이 되면 parentManager에 설치된 listener가 requestKey를 확인하고 fragmentA에게 정보를 전달해주게 된다.

2020-06-19 21:30:45.186 12147-12147/wizley.android.playground E/FragmentActivity: onCreate
2020-06-19 21:30:45.212 12147-12147/wizley.android.playground E/FragmentA: onCreate
2020-06-19 21:30:45.213 12147-12147/wizley.android.playground E/FragmentB: onCreate
2020-06-19 21:30:45.213 12147-12147/wizley.android.playground E/FragmentB: setFragmentResult
2020-06-19 21:30:45.215 12147-12147/wizley.android.playground E/FragmentA: onFragmentResult
2020-06-19 21:30:45.215 12147-12147/wizley.android.playground E/FragmentA: This is FragmentResultListener

실행해보면 다음과 같이 FragmentA가 정상적으로 result값을 받아온 것을 확인할 수 있다. 하위 프래그먼트로도 똑같이 활용할 수 있는데 그 경우에는 getChildFragmentManager를 사용하면 된다. 이를 사용하면 ChildFragmentManager가 listener를 가지고 있다가 정보를 parent에게 전달해준다.

이렇게 Fragment간에 직접적인 통신을 하지 않고 중개 매개체인 FragmentManager를 통해 통신을 하는 것도 가능하다.