본문 바로가기

카테고리 없음

State of the art communication between fragments and their activity

728x90
반응형

출처 : https://towardsdev.com/state-of-the-art-communication-between-fragments-and-their-activity-daa1fe4e014d

 

Android single-activity screen 의 아키텍처 구조

오늘날 현대적인 기본 Android 앱의 개발은 single-activity 아키텍처를 기반으로 하는 경향이 있습니다.

이는 부모로서의 single activity 가 하나 이상의 fragment 를 자식으로 가질 수 있음을 의미합니다. 이벤트 또는 탐색 내에서 fragment 만 교체되므로 원래 상위 activity 가 계속 컨테이너로 작동합니다. 계층적으로 보면 자식 fragment 자체에 자식 fragment 가 있을 수 있습니다.

 

 

fragment 간의 런타임 통신을 위한 가능한 솔루션

이 아키텍처 접근 방식은 런타임 시 fragment 간의 통신과 같은 새로운 문제를 만듭니다. 새 fragment 를 호출할 때 information parameter 를 전송할 수 있는 옵션이 있는 경우 fragment 아래에서 통신할 때 몇 가지 옵션이 있으며 해당되는 경우 상위 activity 와도 통신할 수 있습니다.

  • Interfaces
  • SharedViewModel
  • setTargetFragment()/onActivityResult()

 

fragment 와 activity 간의 통신을 위해 인터페이스를 사용하는 것은 생각할 수 있지만 일정량의 오버헤드를 포함하는 구식 접근 방식이며 현재 Google에서 이 형식으로 직접 권장하지 않습니다.

언뜻보기에 SharedViewModel은 만능 솔루션을 나타내는 것처럼 보이며 Google에서 fragment 간의 통신에도 권장합니다.

여기에서 부모 activity 와 fragment 는 공통 ViewModel을 공유하고 모든 시작 fragment 는 LiveData 또는 Flows를 사용하는 observer 패턴을 통해 해당 이벤트를 발생시킬 수 있습니다. 그러면 각각의 대상 fragment 에서 reaction 이 이루어질 수 있습니다.

이것은 대부분의 상황에서 실행 가능한 접근 방식이지만 검색 결과를 표시하는 검색 마스크가 있는 fragment 와 같이 자주 재사용할 수 있는 fragment 에는 제한이 있습니다. 서로 다른 상호 작용 fragment 가 검색 fragment 와의 개별 통신 요구 사항을 갖는 순간, 이는 매우 빠르게 복잡하고 오류가 발생하기 쉬우며 혼란스러운 오버헤드가 발생할 수 있습니다.

request 및 result code 와 관련하여 target fragment API( setTargetFragment/onActivityResult ) 를 통한 인텐트가 있는 fragment 간의 런타임 시 통신은 API 레벨 28 이후 및 버전 1.3.0-alpha04의 fragment-ktx 라이브러리를 사용하여 더 이상 사용되지 않습니다. . 대신 fragment 간의 통신을 위해 새로운 fragment result API를 사용하는 것이 좋습니다.

 

Fragment Result API

Google은 기기 전반에 걸쳐 일관된 동작과 수명 주기 액세스로 Jetpack fragment 라이브러리를 사용하는 방법을 설명합니다.

버전 Fragment-ktx:1.3.0-alpha04(2020년 4월 29일)부터 FragmentManagers 는 프래그먼트 간 통신에 사용할 수 있습니다.
버전 1.3.0은 2021년 2월 10일부터 안정 버전으로 릴리스되었습니다. 가장 최신 버전은 여기에서 볼 수 있습니다.
https://developer.android.com/jetpack/androidx/releases/fragment

 

프래그먼트  |  Android 개발자  |  Android Developers

프래그먼트 활동 내에서 호스팅되는 여러 개의 독립적인 화면으로 앱을 분할합니다. 최근 업데이트 공개 버전 출시 후보 베타 버전 알파 버전 2022년 6월 29일 1.5.0 - - - 종속 항목 선언 Fragment의 종

developer.android.com

 

 

이제 FragmentManager 는 기본적으로 FragmentResultOwner 를 구현하고 Fragment Results Listener 뿐만 아니라 Fragment Results 를 설정하고 정리하는 메서드를 덮어씁니다 .

작동 방식은 다음과 같이 설명할 수 있습니다.

  • FragmentManagerrequestKey 를 고유 ID로 사용 하여 Fragment ResultsFragment Result Listener 를 서로 다른 에 보관합니다 .
  • 해당 requestKey 로 등록된 Fragment Result Listener 는 result 가 설정되거나 업데이트 되는 즉시 알림을 받습니다.
  • onActivityResult를 사용할 때 다음과 같은 "resultCode"는 더 이상 필요하지 않으며 데이터도 번들을 통해 보내고 받습니다. 번들이 지원하는 모든 데이터 유형을 여기에서 사용할 수 있습니다.
  • 이러한 방식으로 fragment 는 동일한 FragmentManager자식 에서 부모 fragment 로 또는 그 반대로 통신할 수 있습니다.

 

FragmentManager.java 


 @SuppressLint("SyntheticAccessor")
    @Override
    public final void setFragmentResultListener(@NonNull final String requestKey,
            @NonNull final LifecycleOwner lifecycleOwner,
            @NonNull final FragmentResultListener listener) {
        final Lifecycle lifecycle = lifecycleOwner.getLifecycle();
        if (lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) {
            return;
        }

        LifecycleEventObserver observer = new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_START) {
                    // once we are started, check for any stored results
                    Bundle storedResult = mResults.get(requestKey);
                    if (storedResult != null) {
                        // if there is a result, fire the callback
                        listener.onFragmentResult(requestKey, storedResult);
                        // and clear the result
                        clearFragmentResult(requestKey);
                    }
                }

                if (event == Lifecycle.Event.ON_DESTROY) {
                    lifecycle.removeObserver(this);
                    mResultListeners.remove(requestKey);
                }
            }
        };
        lifecycle.addObserver(observer);
        LifecycleAwareResultListener storedListener = mResultListeners.put(requestKey,
                new LifecycleAwareResultListener(lifecycle, listener, observer));
        if (storedListener != null) {
            storedListener.removeObserver();
        }
    }


    @Override
    public final void setFragmentResult(@NonNull String requestKey, @NonNull Bundle result) {
        // Check if there is a listener waiting for a result with this key
        LifecycleAwareResultListener resultListener = mResultListeners.get(requestKey);
        // if there is and it is started, fire the callback
        if (resultListener != null && resultListener.isAtLeast(Lifecycle.State.STARTED)) {
            resultListener.onFragmentResult(requestKey, result);
        } else {
            // else, save the result for later
            mResults.put(requestKey, result);
        }
    }

 

동일한 FragmentManager를 통한 통신

동일한 FragmentManager에 있는 두 fragment 간의 통신은 fragment 가 동일한 계층 수준에서 통신할 때마다 발생하며 다음과 같이 설명할 수 있습니다.

 

발신자 FragmentA 는 setFragmentResult() 명령 을 통해 친구의 이름을 FragmentManager에 보냅니다 . 메시지는 요청 키를 사용하여 래핑되고 번들 키를 사용하여 친구의 이름이 래핑됩니다.

 

"STARTED" 상태에 도달하는 즉시 수신자 fragment FragmentB 는 setFragmentResultListener() 를 호출하여 결과를 수신하고 발신자가 이전에 메시지를 보낸 요청 키에 대한 리스너 콜백을 실행합니다.
메시지 값은 해당 번들 키를 통해 얻습니다.
이 예에서 값이 전달되고 즉시 수신된 다음 대상 fragment 의 ViewModel에 있는 LiveData 속성에 매핑되었습니다. 발신자와 수신자 모두 동일한 FragmentManager에 액세스합니다.

 

부모 fragment 와 자식 fragment 간의 통신

서론에서 이미 언급했듯이 계층적으로 보면 fragment 자체가 fragment 를 호스팅할 수 있습니다. 이 경우 부모 fragment 는 자식 fragment 에서 데이터를 받거나 자식 fragment 자체에 데이터를 보내기 위해 childFragmentManager 가 필요합니다.

 

 

이전 예에서와 같이 대상 fragment 는 STARTED 상태 에 도달하면 데이터를 수신합니다 .

 

 

자식 fragment 는 fragment manager 를 통해 메시지를 설정하거나 검색합니다.

 

부모 activity 의 ​​데이터 보존

부모 activity 가 fragment 에서 메시지를 수신 하려면 이 경우 supportFragmentManager 가 필요합니다.

 

고려해야 할 사항

Fragment Result API로 원활한 처리를 위해서는 다음과 같은 API 원칙을 고려해야 합니다.

  • 특정 요청 키에 대해 하나의 리스너만 등록할 수 있습니다.
    o 동일한 키에 둘 이상의 리스너가 등록된 경우 이전 리스너가 최신 리스너로 대체됩니다.
  • setFragmentResult() 가 발신자 fragment 에서 여러 번 실행 되면 대상 프래그먼트는 항상 가장 최근 값을 가져옵니다.
  • setFragmentResult() 를 통해 값이 전송 되고 키에 대해 등록된 리스너가 없는 경우 동일한 키를 가진 리스너가 등록될 때까지 가장 최근 값이 FragmentManager에 저장됩니다. 이 동작은 "hot observable"에 해당합니다.
    o 리스너가 등록되고 fragment 수명 주기가 " Lifecycle.State.STARTED " 상태에 도달하는 즉시 값이 즉시 소비됩니다.
  • 대상 프래그먼트가 값을 소비하면 FragmentManager 에서 값을 삭제 합니다.

요약

새로운 Fragment Result API를 사용하면 런타임 시 fragment 간의 단순하고 작은 데이터 패킷의 통신이 훨씬 쉬워지고 불필요한 오버헤드도 제거되었습니다.
라이프 사이클과 메시지의 고유성 및 적시성과 같은 메시지의 소비자 행동에 대한 규칙/원칙을 고려하여, 특정 안정성도 보장하므로 Fragment Result API가 매우 좋은 대안이거나 최소한 서로 런타임 통신을 위해 SharedViewModel에 추가합니다.
또한 여러 번 재사용되고 특정 view 에서 서로 다른 데이터 교환이 필요한 fragment 간의 문제를 매우 효율적으로 해결합니다.

개인적인 생각 :

새롭게 추가된 Fragment Result API 에 대해서 알게 되었습니다.

fragment 간의 통신이 필요할 때 viewModel 을 share 하지 않고 fragment result api 를 사용해보는 것도 좋을 것 같습니다.

728x90
반응형