2018년 2월 19일 월요일

[안드로이드] Architecture Component 2 - LiveData 공식문서 번역

LiveData

LiveData는 관찰가능한(Observable) 데이터 홀더 클래스이다. 일반적인 관찰가능함과는 다르게, LivaData는 Activity, Fragment, Service 같은 앱 컴포넌트의 생명주기를 인식하고, 그대로 따르도록 구현되어 있다. 이는 LiveData가 현재 활성화된 생명주기에 있는 앱 컴포넌트의 옵저버(Observer)만 업데이트하도록 보장한다.
  • Note : LiveData를 프로젝트에 추가하려면 다음 링크를 확인한다.
LiveData는 현재 생명주기 상태가 STARTED 이거나 RESUMED 일 때, 옵저버(Observer.class)가 활성상태인 것으로 간주한다. LiveData는 업데이트에 대하여 활성화중인 옵저버들에게만 알림을 준다. LiveData 오브젝트들을 관찰하기위해 등록된 비활성화 상태인 옵저버들은 변화에 대하여 알림을 받지 못한다.
LifecycleOwner 인터페이스를 구현한 객체와 쌍을 이루는 옵저버를 등록할 수 있다. 이렇게 쌍을 이루어 등록함으로써, Lifecycle 객체가 DESTROYED상태로 변할 때 해당 옵저버가 삭제될 수 있도록 할 수 있다. 이러한 특성은 특히 Activity, Fragment의 생명주기가 destroy 상태가 되었을 때, 즉시 관찰을 취소하므로 LiveData 객체를 안전하게 관찰할 수 있게 하고, 메모리 릭에 대한 우려도 없어지므로 유용하게 사용될 수 있다.

LiveData 사용시 장점

LiveData는 다음과 같은 장점이 있다.
  • UI를 데이터 상태와 일치시킬 수 있다.
    • LiveData는 옵저버패턴을 따르고 있다. 생명주기 상태에 변화가 생기면 LiveData는 Observer 인스턴스에 알림을 보낸다. 그러므로, 개발자는 Observer 객체 안에서 UI가 갱신되도록 코드를 통합할 수 있다. 앱의 데이터가 변할때마다 매번 UI를 갱신하는 대신, 옵저버에 변화가 감지될 때마다 UI를 갱신하도록 바꿀 수 있다.
  • 메모리 릭의 요소를 없앨 수 있다.
    • 옵저버들은 Lifecycle 객체와 바인드되고, 그 Lifecycle 객체가 destroy 상태가 되면 자동적으로 지워진다.
  • 정지된 엑티비티들때문에 Crash날 일이 없다.
    • 엑티비티가 백스택에 있을때와 같이, 옵저버의 생명주기가 비활성화 상태일 때는, 어떠한 LiveData이벤트도 받지 않는다.
  • 직접 생명주기를 핸들링할 필요가 없다.
    • UI 컴포넌트는 단지 연관된 데이터를 관찰하기만 하고, resume / stop을 신경쓸 필요가 없다. LiveData가 상태변화를 감지하여 알아서 관리해준다.
  • 항상 최신 데이터를 유지한다.
    • 생명주기가 비활성화 상태에서 활성화 상태로 변했을 때, UI 컴포넌트는 최신의 데이터를 받는다. 예를들어, 백그라운드에 있던 엑티비티가 다시 포그라운드가 되었을 때, 최신의 데이터를 받는다.
  • 구성(Configuration)이 변경되었을 때 적절하게 대응한다.
    • Activity나 Fragment가 구성변경되어 재생성될 때, 그 컴포넌트들은 LiveData로부터 즉시 사용가능한 최신데이터를 제공받는다.
  • 자원 공유
    • 싱글턴 패턴을 이용해 시스템 서비스를 래핑하여 LiveData객체가 공유될 수 있도록 확장할 수 있다. LiveData 오브젝트가 시스템 서비스에 한번 연결되면, 리소스가 필요한 모든 옵저버는 LiveData 객체를 볼 수 있다. 자세한 내용은 아래의 Extend LiveData항목을 참고한다.

LiveData 객체 사용하기

LiveData 객체를 사용하기 위해 다음의 절차를 수행한다.
  1. 특정 데이터를 유지할 LiveData 인스턴스를 생성한다. 이 작업은 주로 ViewModel 클래스 안에서 이루어진다.
  2. onChanged() 메서드를 정의하고 있는 Observer 인스턴스를 생성한다. onChanged()메서드는 LiveData가 유지하고 있는 데이터에 변화가 감지되었을 때 어떻게 동작할지를 제어하도록 구현한다. 대부분 Activity나 Fragment 같은 UI 컨트롤러에서 Observer 인스턴스를 생성한다.
  3. observe() 메서드를 Observer 인스턴스를 LiveData 인스턴스에 연결한다. observe 메서드는 LifecycleOwner 인스턴스를 받는다. 이는 Observer 인스턴스가 LiveData 인스턴스를 구독하도록 하여, 변화에 대해 알림을 받도록 한다. 일반적으로 Observer 인스턴스는 Activity나 Fragment 같은 UI 컨트롤러에 붙인다.
    Note : observeForever(Observer) 메소드를 이용하여 연관된 LifecycleOwner 인스턴스에 상관없이 옵저버를 등록할 수 있다. 이 경우, 옵저버는 항상 활성상태이고, 항상 변경사항에 알림을 받도록 간주된다. removeObserver(Observer) 메서드를 이용하여 등록된 옵저버를 제거할 수 있다.
LiveData객체에 저장된 값을 업데이트하면, 현재 붙어있는 활성상태의 LifecycleOwner들에게 트리거된다.
LiveData는 UI 컨트롤러의 옵저버가 변경사항을 구독하도록 할 수 있다. LiveData가 유지하고 있는 데이터가 바뀌면, UI는 자동적으로 화면을 갱신한다.

LiveData 객체 생성

LiveData는 List같은 Collections 구현체를 포함하여 모든 데이터와 함께 사용될 수 있는 래퍼 클래스이다. LiveData는 일반적으로 ViewModel 인스턴스 안에 저장되고, 아래의 예제처럼 Getter 메서드를 통해서 접근하도록 구현한다.
public class NameViewModel extends ViewModel {

    // Create a LiveData with a String
    private MutableLiveData<String> mCurrentName;

    public MutableLiveData<String> getCurrentName() {
        if (mCurrentName == null) {
            mCurrentName = new MutableLiveData<String>();
        }
        return mCurrentName;
    }

    // Rest of the ViewModel...
}
처음에는, LiveData 인스턴스 안의 실제 데이터는 저장되지 않는다.
Note : 다음과 같은 이유때문에 UI를 업데이트하는 LiveData 인스턴스는 Activity나 Fragment가 아닌 ViewModel에 저장되어야 한다.
  • Activity / Fragment 내부에 코드가 모이는 상황을 피하기 위해. - UI 컨트롤러는 데이터의 상태를 유지하는 역할을 몰라도 된다. 주어진 데이터를 잘 보여주는 역할만 수행하면 된다.
  • LiveData를 특정 Activity나 Fragment 인스턴스에 종속시키지 않고, 구성변경으로부터 LiveData 인스턴스가 살아있도록 하기 위해.

LiveData 객체 관찰

대부분의 경우에, 앱 컴포넌트의 onCreate() 메서드가 LiveData의 관찰을 시작하기 적절한 위치이다. 그 이유는 다음과 같다.
  • onResume() 메서드와 같이 중복으로 호출되지 않음을 보장한다.
  • Activity나 Fragment가 활성화가 되자마자 화면을 표시할 ㄷ이터가 있는지를 확인할 수 있다. 앱 컴포넌트가 STARTED 상태가 되자마자, LiveData로부터 가장 최근의 데이터를 받는다. 이는 관찰할 LiveData가 설정되어 있는 경우에만 발생한다.
일반적으로, LiveData는 데이터의 변화가 있을 때에만, 활성화된 관찰자에게만 업데이트를 알린다. 이 동작의 예외케이스로는, 관찰자의 상태가 비활성에서 활성으로 바뀔 때 업데이트 알림을 받는다. 또한, 관찰자의 상태가 비활성화에서 활성화로 두번째 변경되면, 마지막 활성화 상태 이후로 값의 변경이 있을 때에만 업데이트 알림을 받는다.
다음의 샘플코드는 어떻게 LvieData의 관찰을 시작하는지 보여주고 있다.
public class NameActivity extends AppCompatActivity {

    private NameViewModel mModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Other code to setup the activity...

        // Get the ViewModel.
        mModel = ViewModelProviders.of(this).get(NameViewModel.class);

        // Create the observer which updates the UI.
        final Observer<String> nameObserver = new Observer<String>() {
            @Override
            public void onChanged(@Nullable final String newName) {
                // Update the UI, in this case, a TextView.
                mNameTextView.setText(newName);
            }
        };

        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
        mModel.getCurrentName().observe(this, nameObserver);
    }
}
nameObserver 인스턴스가 파라미터로 전달되어 observer()메서드가 호출된 후에, mCurrentName에 저장된 가장 최근값을 제공하기 위해 즉시 onChanged() 메서드가 호출될 것이다. 만약 mCurrentName에 LiveData가 설정되어 있지 않으면, onChanged() 메서드는 호출되지 않는다.

LiveData 객체 갱신

LiveData는 저장된 데이터를 갱신하기 위한 공개적으로 사용가능한 메서드가 없다. MutableLiveData 클래스는 공개적으로 setValue(T)postValue(T) 메서드를 노출하고, LiveData안에 저장된 데이터를 수정할 필요가 있다면, 반드시 저 메서드들만 이용해야 한다. 일반적으로 수정가능한 MutableLiveData는 ViewModel 내부에서 사용되고, ViewModel은 수정 불가능한 LiveData로 관찰자에게 노출한다.
옵저버를 연결한 후에, 다음의 예제와 같이 LiveData 인스턴스의 값을 변경할 수 있다.
mButton.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        String anotherName = "John Doe";
        mModel.getCurrentName().setValue(anotherName);
    }
});
이 예제에서 setValue(T) 메서드를 호출하면 관찰자들은 값이 바뀐 John Doe 로 onChanged() 메서드를 호출할 것이다. 이 예제에서는 버튼클릭을 보여줬지만, setValue() / postValue()는 네트워크 요청, 데이터베이스 조회 등 다양한 상황에서 데이터를 바꾸고 싶을 때 호출될 수 있다. setValue() / postValue() 메서드가 호출된 다음에는 관찰자를 트리거하여 UI를 갱신하도록 진행된다.
Note : 메인스레드에서 LiveData를 갱신하려면 반드시 setValue(T) 메서드를 호출해야 한다. 만약, 작업스레드에서 LiveData를 갱신하려면 postValue(T) 메서드를 호출해야 한다.

LiveData를 Room과 함꼐 사용하기

Room은 LiveData 인스턴스를 반환하여 관찰가능한 쿼리를 제공하는 DB 라이브러리이다. 관찰가능한 쿼리는 Database Access Object(DAO)의 파트로써 작성된다.
Room은 데이터베이스가 업데이트 될 때 LiveData객체를 업데이트하기 위한 모든 필요한 코드를 생성한다. 생성된 코드는 필요하다면 백그라운드 스레드에서 비동기로 쿼리를 실행한다. 이 패턴은 DB에 저장된 데이터와 UI를 동기화하여, 화면에 보여지는 데이터를 유지하는데 효율적이다.

LiveData 확장하기

관찰자의 상태가 STARTED / RESUMED 일때, LiveData는 관찰자를 활성 상태로 간주한다. 다음의 예제코드는 어떻게 LiveData 클래스가 확장되는지를 보여준다.
public class StockLiveData extends LiveData<BigDecimal> {
    private StockManager mStockManager;

    private SimplePriceListener mListener = new SimplePriceListener() {
        @Override
        public void onPriceChanged(BigDecimal price) {
            setValue(price);
        }
    };

    public StockLiveData(String symbol) {
        mStockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
        mStockManager.requestPriceUpdates(mListener);
    }

    @Override
    protected void onInactive() {
        mStockManager.removeUpdates(mListener);
    }
}
이 예제에서 price listener 구현은 다음의 중요한 메서드들에 포함되어 있다.
  • onActivity() 메서드는 LiveData 인스턴스가 활성 관찰자를 가지고 있을 때 호출된다. 이 예제에서는 stock 가격 갱신의 관찰을 이 메서드로부터 시작했다.
  • onInactive() 메서드는 LiveData 인스턴스가 어떤 활성화된 관찰자도 가지고있지 않을 때 호출된다. 관찰중인 관찰자가 없으므로, StockManager 서비스와 계속 연결을 유지할 이유가 없다.
  • setValue(T) 메서드는 LiveData 인스턴스의 값을 갱신하고, 변화에 대하여 활성 관찰자들에게 알려준다.
StockLiveData 클래스는 다음의 예제처럼 사용될 수 있다.
public class MyFragment extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        LiveData<BigDecimal> myPriceListener = ...;
        myPriceListener.observe(this, price -> {
            // Update the UI.
        });
    }
}
observe() 메서드는 첫번째 인자로 Fragment를 받았다. 이렇게 함으로써 소유자와 관련된 Lifecycle 객체가 관찰자와 바인딩된다. 즉,
  • Lifecycle 객체가 활성상태가 아니면, 관찰자는 값이 변경되더라도 호출되지 않는다.
  • Lifecycle 객체가 파괴된 후에, 관찰자는 자동으로 제거된다.
LiveData 인스턴스가 라이프사이클을 인지할 수 있다는 사실은 여러 Activity, Fragment, Service 사이에서 공유될 수 있다는 것을 의미한다. 다음의 예제는 예제를 쉽게 유지하기 위해, LiveData 클래스를 싱글턴 패턴으로 구현했다.
public class StockLiveData extends LiveData<BigDecimal> {
    private static StockLiveData sInstance;
    private StockManager mStockManager;

    private SimplePriceListener mListener = new SimplePriceListener() {
        @Override
        public void onPriceChanged(BigDecimal price) {
            setValue(price);
        }
    };

    @MainThread
    public static StockLiveData get(String symbol) {
        if (sInstance == null) {
            sInstance = new StockLiveData(symbol);
        }
        return sInstance;
    }

    private StockLiveData(String symbol) {
        mStockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
        mStockManager.requestPriceUpdates(mListener);
    }

    @Override
    protected void onInactive() {
        mStockManager.removeUpdates(mListener);
    }
}

//Fragment에서 사용
public class MyFragment extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        StockLiveData.get(getActivity()).observe(this, price -> {
            // Update the UI.
        });
    }
}
여러 Fragment / Activity들이 MyPriceListener인스턴스를 관찰할 수 있다. 하나 이상의 UI 컴포넌트가 화면에 보이고 활성화 상태라면, LiveData는 시스템 서비스에만 연결된다.

LiveData 변형

관찰자에게 이벤트가 전달되기 전에 저장된 값을 변경하거나, 다른 값을 기반으로 다른 LiveData 인스턴스를 리턴해야 할 필요가 있을 수도 있다. 이러한 시나리오를 지원하는 헬퍼 메서드를 가진 Transformations 클래스가 Lifecycle 패키지에 포함되어 있다.
Transformations.map() : LiveData 인스턴스 안에 저장된 값에 함수를 적용하고, 아래방향으로 결과를 전파한다.
LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
    user.name + " " + user.lastName
});
Transformations.switchMap() : map() 메서드와 비슷하게, LiveData 인스턴스 안에 저장된 값에 함수를 적용하고, 래핑을 풀고, 아래방향으로 결과를 전파한다. switchMap()에 전달되는 함수는 반드시 LiveData 인스턴스를 반환해야 한다.
private LiveData<User> getUser(String id) {
  ...;
}

LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
변환(transformation) 메서드를 사용하여 관찰자의 생명주기 전반에 걸쳐 정보를 전달 할 수 있다. 관찰자가 리턴된 LiveData 인스턴스를 보고있지 않으면, 변환 기능은 동작하지 않는다. 변환 계산은 느리므로, 생명주기 관련된 동작은 명시적으로 호출하거나 의존성이 없다면 암묵적으로 지나칠 것이다.
만약 ViewModel 인스턴스 안에 Lifecycle 인스턴스가 필요하다면, 변형이 더 좋은 해결책일 것이다. 예를들어, 주소를 받아서 우편번호를 반환하는 UI가 있다고 가정해보자. 다음의 예제처럼 간단한 ViewModel을 구현할 수 있다.
class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    public MyViewModel(PostalCodeRepository repository) {
       this.repository = repository;
    }

    private LiveData<String> getPostalCode(String address) {
       // DON'T DO THIS
       return repository.getPostCode(address);
    }
}
UI 컴포넌트는 getPostCode() 메서드가 불릴때마다 이전의 LiveData 인스턴스를 해제하고, 새로운 인스턴스를 등록해줄 필요가 있다. 게다가 UI 컴포넌트가 재생성되면, 이전 호출값을 사용하는 대신 **respository.getPostCode()**이 다시 트리거된다.
다음 예제는 위의 문제점을 해결하기 위해,변형 방법으로 주소를 받아 우편번호를 조회하는 것이다.
class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    private final MutableLiveData<String> addressInput = new MutableLiveData();
    public final LiveData<String> postalCode =
            Transformations.switchMap(addressInput, (address) -> {
                return repository.getPostCode(address);
             });

  public MyViewModel(PostalCodeRepository repository) {
      this.repository = repository
  }

  private void setInput(String address) {
      addressInput.setValue(address);
  }
}
이 예제에서는, postalCode 필드는 절대 변할 일이 없기때문에 public final 이다. postalCode필드는 addressInput 필드의 변형으로 정의되었는데, 이는 addressInput이 변할때마다 repository.getPostCode() 메서드가 호출되는 것을 의미한다. 현재 활성 관찰자가 있다면 정상동작할 것이고, ** repository.getPostCode()**메서드를 호출하는 그 때에 활성관찰자가 없다면 관찰자가 추가되기 전까지 어떠한 계산도 일어나지 않을 것이다.
이 메카니즘은 앱의 로우레벨단에서 상황에 따라 늦게 계산되는 LiveData 객체를 생성할 수 있도록 한다. ViewModel 객체는 쉽게 LiveData 인스턴스의 참조를 얻어서 그 위에 변형규칙을 적용할 수 있다.

새로운 변형 생성

앱에서 유용하게 적용할 수 있는 12개의 특정 변형들이 있지만, 기본적으로 제공되지는 않는다. 변형을 직접 구현하려면, 다른 LiveData 인스턴스를 들을 수 있고, 자신이 생성한 이벤트를 처리할 수 있는 MediatorLiveData 클래스를 이용해야 한다. MediatorLiveData는 원래의 LiveData 인스턴스로 상태를 정확하게 전달한다. 이 패턴을 더 배우려면, Transformations 클래스를 참고한다.

여러 LiveData를 병합하기

LiveData의 서브클래스인 MediatorLiveData는 여러개의 LiveData를 병합가능하도록 지원한다. MediatorLiveData 인스턴스의 관찰자는 원래의 LiveData 인스턴스들중 하나라도 변경될 때마다 트리거된다.
예를들어, 앱의 UI에 로컬 DB / 네트워크로부터 갱신될 수 있는 LiveData 인스턴스가 있다면, 다음과 같은 LiveData 소스들을 MediatorLiveData 객체에 추가할 수 있다.
  • 데이터베이스에 저장된 데이터와 관련된 LiveData 인스턴스
  • 네트워크로부터 받아오는 데이터와 관련된 LiveData 인스턴스
UI에서는 양쪽 소스로부터의 갱신알림을 방기 위해 MediatorLiveData 인스턴스만 관찰하면 된다.

원문 : https://developer.android.com/topic/libraries/architecture/livedata.html


댓글 2개: