LiveData 개요   Android Jetpack의 구성요소

LiveData는 식별 가능한 데이터 홀더 클래스입니다. 식별 가능한 일반 클래스와 달리 LiveData는 수명 주기를 인식합니다. 즉 활동, 프래그먼트 또는 서비스와 같은 다른 앱 구성요소의 수명 주기를 고려합니다. 이러한 수명 주기 인식을 통해 LiveData는 활성 수명 주기 상태에 있는 앱 구성요소 관찰자만 업데이트합니다.

LiveData는 Observer 클래스로 표현되는 관찰자의 수명 주기가 STARTED 또는 RESUMED 상태에 있으면 관찰자가 활성 상태에 있는 것으로 간주합니다. LiveData는 활성 관찰자에게만 업데이트 정보를 알립니다. LiveData 개체를 관찰하도록 등록된 비활성 관찰자에게는 변경 관련 알림이 전달되지 않습니다.

LifecycleOwner 인터페이스를 구현하는 개체와 쌍을 이루는 관찰자를 등록할 수 있습니다. 이 관계를 통해 Lifecycle 개체의 상태가 DESTROYED로 변경될 때 관찰자를 삭제할 수 있습니다. 이는 활동 및 프래그먼트에 특히 유용합니다. 활동 및 프래그먼트가 LiveData 개체를 안전하게 관찰할 수 있으며, 수명 주기가 끝나는 즉시 활동 및 프래그먼트의 구독이 취소되어 누출을 걱정할 필요가 없기 때문입니다.

LiveData 사용 방법에 관한 자세한 내용은 LiveData 개체 사용을 참조하세요.

LiveData 사용의 이점

LiveData를 사용하면 다음의 이점이 있습니다.

UI와 데이터 상태의 일치 보장
LiveData는 관찰자 패턴을 따릅니다. LiveData는 수명 주기 상태가 변경되면 Observer 개체에 변경을 알립니다. 코드를 통합하여 이러한 Observer 개체의 UI를 업데이트할 수 있습니다. 앱 데이터가 변경될 때마다 UI를 업데이트하는 대신, 변경이 발생할 때마다 관찰자가 UI를 업데이트할 수 있습니다.
메모리 누출 없음
관찰자는 Lifecycle 개체와 결합해 있으며 연결된 수명 주기가 끝나면 자동으로 삭제됩니다.
중지된 활동으로 인한 비정상 종료 없음
활동이 백 스택에 있을 때를 비롯하여 관찰자의 수명 주기가 비활성 상태에 있으면 관찰자는 어떤 LiveData 이벤트도 받지 않습니다.
수명 주기를 더 이상 수동으로 처리할 필요 없음
UI 구성요소는 관련 데이터를 관찰하기만 하며 관찰을 중지하거나 재개하지 않습니다. LiveData는 관찰하는 동안 관련 수명 주기 상태의 변경을 인식하므로 이 모든 것을 자동으로 관리합니다.
항상 최신 데이터 유지
수명 주기가 비활성화되었다면 다시 활성화될 때 최신 데이터를 받습니다. 예를 들어 백그라운드에 있었던 활동은 포그라운드로 돌아온 직후 최신 데이터를 받습니다.
적절한 구성 변경
기기 회전과 같은 구성 변경으로 인해 활동 또는 프래그먼트가 다시 생성되면, 사용 가능한 최신 데이터를 즉시 받게 됩니다.
리소스 공유
싱글톤 패턴을 사용하는 LiveData 개체를 확장하여 시스템 서비스를 앱에서 공유하도록 래핑할 수 있습니다. LiveData 개체가 시스템 서비스에 한 번 연결되면 리소스가 필요한 모든 관찰자는 LiveData 개체를 바로 관찰할 수 있습니다. 자세한 내용은 LiveData 확장을 참조하세요.

LiveData 개체 사용

LiveData 개체를 사용하려면 다음 단계를 따르세요.

  1. 특정 유형의 데이터를 보유할 수 있는 LiveData 인스턴스를 만듭니다. 이 작업은 대개 ViewModel 클래스 내에서 진행됩니다.
  2. onChanged() 메서드를 정의하는 Observer 개체를 만듭니다. 이 메서드는 LiveData 개체에서 보유한 데이터가 변경될 때 발생하는 작업을 제어합니다. 일반적으로 활동 또는 프래그먼트와 같은 UI 컨트롤러에 Observer 개체를 만듭니다.
  3. observe() 메서드를 사용하여 Observer 개체를 LiveData 개체에 연결합니다. observe() 메서드는 LifecycleOwner 개체를 가져옵니다. 이렇게 하면 Observer 개체가 LiveData 개체를 구독함으로써 변경 알림을 받을 수 있습니다. 일반적으로 활동 또는 프래그먼트와 같은 UI 컨트롤러에서 Observer 개체를 연결합니다.

연결된 LifecycleOwner가 활성 상태에 있는 한 LiveData 개체에 저장된 값을 업데이트하면 등록된 모든 관찰자가 트리거됩니다.

LiveData를 사용하면 UI 컨트롤러 관찰자가 업데이트를 구독할 수 있습니다. LiveData 개체가 보유하고 있는 데이터가 변경되면 UI가 이에 따라 자동으로 업데이트됩니다.

LiveData 개체 만들기

LiveData는 List와 같은 Collections를 구현하는 개체를 비롯하여 모든 데이터와 함께 사용할 수 있는 래퍼입니다. 일반적으로 LiveData 개체는 ViewModel 개체 내에 저장되며 다음 예에서와 같이 getter 메서드를 통해 액세스할 수 있습니다.

Kotlin

    class NameViewModel : ViewModel() {

        // Create a LiveData with a String
        val currentName: MutableLiveData<String> by lazy {
            MutableLiveData<String>()
        }

        // Rest of the ViewModel...
    }
    

자바

    public class NameViewModel extends ViewModel {

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

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

    // Rest of the ViewModel...
    }
    

처음에는 LiveData 개체의 데이터가 설정되어 있지 않습니다.

ViewModel 클래스의 이점과 사용에 관한 자세한 내용은 ViewModel 가이드를 참조하세요.

LiveData 개체 관찰

대부분의 경우 다음과 같은 이유로 앱 구성요소의 onCreate() 메서드는 LiveData 개체 관찰을 시작하기에 적합합니다.

  • 시스템이 활동이나 프래그먼트의 onResume() 메서드에서 중복 호출을 생성하지 않도록 하기 위해서입니다.
  • 활동 또는 프래그먼트가 활성 상태가 되는 즉시 표시할 수 있는 데이터를 보유하도록 하기 위해서입니다. 앱 구성요소는 STARTED 상태가 되면 곧바로 관찰 중인 LiveData 개체로부터 최신 값을 받습니다. 이는 관찰할 LiveData 개체를 설정했을 때에만 발생합니다.

일반적으로 LiveData는 데이터가 변경될 때만, 그리고 활성 관찰자에게만 업데이트를 전달합니다. 이 동작의 예외로, 관찰자가 비활성 상태에서 활성 상태로 변경될 때에도 관찰자는 업데이트를 받습니다. 또한 관찰자가 비활성 상태에서 활성 상태로 다시 변경되면 마지막으로 활성 상태가 된 이후 값이 변경된 경우에만 업데이트를 받습니다.

다음 샘플 코드는 LiveData 개체 관찰을 시작하는 방법을 보여줍니다.

Kotlin

    class NameActivity : AppCompatActivity() {

        private lateinit var model: NameViewModel

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)

            // Other code to setup the activity...

            // Get the ViewModel.
            model = ViewModelProviders.of(this).get(NameViewModel::class.java)

            // Create the observer which updates the UI.
            val nameObserver = Observer<String> { newName ->
                // Update the UI, in this case, a TextView.
                nameTextView.text = newName
            }

            // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
            model.currentName.observe(this, nameObserver)
        }
    }
    

자바

    public class NameActivity extends AppCompatActivity {

        private NameViewModel model;

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

            // Other code to setup the activity...

            // Get the ViewModel.
            model = 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.
                    nameTextView.setText(newName);
                }
            };

            // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
            model.getCurrentName().observe(this, nameObserver);
        }
    }
    

매개변수로 전달된 nameObserver와 함께 observe()가 호출되면 onChanged()가 즉시 호출되어 mCurrentName에 저장된 최신 값을 제공합니다. LiveData 개체가 mCurrentName에 값을 설정하지 않은 경우 onChanged()가 호출되지 않습니다.

LiveData 개체 업데이트

LiveData에는 저장된 데이터를 업데이트하는 데 공개적으로 사용할 수 있는 메서드가 없습니다. MutableLiveData 클래스는 setValue(T)postValue(T) 메서드를 공개적으로 노출합니다. LiveData 개체에 저장된 값을 수정해야 한다면 이러한 메서드를 사용해야 합니다. 일반적으로 MutableLiveDataViewModel에서 사용되면 ViewModel은 변경할 수 없는 LiveData 개체만 관찰자에게 노출합니다.

관찰자 관계를 설정한 후 다음 예에서와 같이 사용자가 버튼을 탭할 때 모든 관찰자를 트리거하는 LiveData 개체의 값을 업데이트할 수 있습니다.

Kotlin

    button.setOnClickListener {
        val anotherName = "John Doe"
        model.currentName.setValue(anotherName)
    }
    

자바

    button.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            String anotherName = "John Doe";
            model.getCurrentName().setValue(anotherName);
        }
    });
    

이 예에서 setValue(T)를 호출하면 관찰자가 John Doe 값이 있는 onChanged() 메서드를 호출합니다. 이 예에서는 버튼 누르기를 보여주지만, 네트워크 요청이나 데이터베이스 로드 완료에 대한 응답을 비롯하여 다양한 이유로 setValue() 또는 postValue()를 호출하여 mName을 업데이트할 수 있습니다. 모든 경우에 setValue() 또는 postValue()를 호출하면 관찰자가 트리거되고 UI가 업데이트됩니다.

Room과 함께 LiveData 사용

Room 지속성 라이브러리는 LiveData 개체를 반환하는 식별 가능한 쿼리를 지원합니다. 식별 가능한 쿼리는 DAO(Database Access Object)의 일부로 작성됩니다.

데이터베이스가 업데이트되면 Room에서는 LiveData 개체를 업데이트하는 데 필요한 모든 코드를 생성합니다. 생성된 코드는 필요 시 백그라운드 스레드에서 비동기적으로 쿼리를 실행합니다. 이 패턴은 UI에 표시된 데이터와 데이터베이스에 저장된 데이터의 동기화를 유지하는 데 유용합니다. Room 및 DAO에 관한 자세한 내용은 Room 지속성 라이브러리 가이드를 참조하세요.

LiveData와 함께 코루틴 사용

LiveData에는 Kotlin 코루틴 지원이 포함됩니다. 자세한 내용은 Android 아키텍처 구성요소와 함께 Kotlin 코루틴 사용을 참조하세요.

LiveData 확장

LiveData는 관찰자의 수명 주기가 STARTED 또는 RESUMED 상태에 있을 때 관찰자가 활성 상태라고 간주합니다. 다음 샘플 코드는 LiveData 클래스를 확장하는 방법을 보여줍니다.

Kotlin

    class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
        private val stockManager = StockManager(symbol)

        private val listener = { price: BigDecimal ->
            value = price
        }

        override fun onActive() {
            stockManager.requestPriceUpdates(listener)
        }

        override fun onInactive() {
            stockManager.removeUpdates(listener)
        }
    }
    

자바

    public class StockLiveData extends LiveData<BigDecimal> {
        private StockManager stockManager;

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

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

        @Override
        protected void onActive() {
            stockManager.requestPriceUpdates(listener);
        }

        @Override
        protected void onInactive() {
            stockManager.removeUpdates(listener);
        }
    }
    

이 예에서 가격 리스너 구현에는 다음과 같은 중요한 메서드가 포함되어 있습니다.

  • onActive() 메서드는 LiveData 개체에 활성 관찰자가 있을 때 호출됩니다. 즉, 이 메서드에서 주가 업데이트 관찰을 시작해야 합니다.
  • onInactive() 메서드는 LiveData 개체에 활성 관찰자가 없을 때 호출됩니다. 수신 대기 중인 관찰자가 없으므로 StockManager 서비스와 연결 상태를 유지할 이유가 없습니다.
  • setValue(T) 메서드는 LiveData 인스턴스의 값을 업데이트하고 모든 활성 관찰자에 변경사항을 알립니다.

다음과 같이 StockLiveData 클래스를 사용할 수 있습니다.

Kotlin

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        val myPriceListener: LiveData<BigDecimal> = ...
        myPriceListener.observe(this, Observer<BigDecimal> { price: BigDecimal? ->
            // Update the UI.
        })
    }
    

자바

    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() 메서드는 LifecycleOwner 인스턴스인 프래그먼트를 첫 번째 인수로 전달합니다. 이렇게 하면 이 관찰자가 소유자와 연결된 Lifecycle 개체에 결합되어 있음을 나타내며 그 의미는 다음과 같습니다.

  • Lifecycle 개체가 활성 상태가 아니면 값이 변경되더라도 관찰자가 호출되지 않습니다.
  • Lifecycle 개체가 폐기되면 관찰자가 자동으로 삭제됩니다.

LiveData 개체가 수명 주기를 인식한다는 사실은 개발자가 여러 활동, 프래그먼트, 서비스 간에 개체를 공유할 수 있음을 의미합니다. 예를 단순하게 유지하기 위해 다음과 같이 LiveData 클래스를 싱글톤으로 구현할 수 있습니다.

Kotlin

    class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
        private val stockManager: StockManager = StockManager(symbol)

        private val listener = { price: BigDecimal ->
            value = price
        }

        override fun onActive() {
            stockManager.requestPriceUpdates(listener)
        }

        override fun onInactive() {
            stockManager.removeUpdates(listener)
        }

        companion object {
            private lateinit var sInstance: StockLiveData

            @MainThread
            fun get(symbol: String): StockLiveData {
                sInstance = if (::sInstance.isInitialized) sInstance else StockLiveData(symbol)
                return sInstance
            }
        }
    }
    

자바

    public class StockLiveData extends LiveData<BigDecimal> {
        private static StockLiveData sInstance;
        private StockManager stockManager;

        private SimplePriceListener listener = 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) {
            stockManager = new StockManager(symbol);
        }

        @Override
        protected void onActive() {
            stockManager.requestPriceUpdates(listener);
        }

        @Override
        protected void onInactive() {
            stockManager.removeUpdates(listener);
        }
    }
    

그리고 다음과 같이 프래그먼트에서 클래스를 사용할 수 있습니다.

Kotlin

    class MyFragment : Fragment() {

        override fun onActivityCreated(savedInstanceState: Bundle?) {
            StockLiveData.get(symbol).observe(this, Observer<BigDecimal> { price: BigDecimal? ->
                // Update the UI.
            })

        }
    

자바

    public class MyFragment extends Fragment {
        @Override
        public void onActivityCreated(Bundle savedInstanceState) {
            StockLiveData.get(symbol).observe(this, price -> {
                // Update the UI.
            });
        }
    }
    

여러 프래그먼트와 활동이 MyPriceListener 인스턴스를 관찰할 수 있습니다. LiveData는 하나 이상의 프래그먼트 및 활동이 공개되고 활성 상태일 때에만 시스템 서비스에 연결됩니다.

LiveData 변환

LiveData 개체를 관찰자에게 전달하기 전에 개체에 저장된 값을 변경하고자 할 수 있습니다. 또는 다른 인스턴스 값에 따라 다양한 LiveData 인스턴스를 반환해야 할 수 있습니다. Lifecycle 패키지는 이러한 시나리오를 지원하는 도우미 메서드가 포함된 Transformations 클래스를 제공합니다.

Transformations.map()
LiveData 개체에 저장된 값에 함수를 적용하고 결과 다운스트림을 전파합니다.

Kotlin

    val userLiveData: LiveData<User> = UserLiveData()
    val userName: LiveData<String> = Transformations.map(userLiveData) {
        user -> "${user.name} ${user.lastName}"
    }
    

자바

    LiveData<User> userLiveData = ...;
    LiveData<String> userName = Transformations.map(userLiveData, user -> {
        user.name + " " + user.lastName
    });
    
Transformations.switchMap()
map()과 유사하며, LiveData 개체에 저장된 값에 함수를 적용하고 결과 다운스트림을 래핑 해제하여 전달합니다. switchMap()에 전달된 함수는 다음 예에서와 같이 LiveData 개체를 반환해야 합니다.

Kotlin

    private fun getUser(id: String): LiveData<User> {
      ...
    }
    val userId: LiveData<String> = ...
    val user = Transformations.switchMap(userId) { id -> getUser(id) }
    

자바

    private LiveData<User> getUser(String id) {
      ...;
    }

    LiveData<String> userId = ...;
    LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
    

변환 메서드를 사용하여 관찰자의 수명 주기 전반에 걸쳐 정보를 전달할 수 있습니다. 관찰자가 반환된 LiveData 개체를 관찰하지 않는 한 변환은 계산되지 않습니다. 변환은 느리게 계산되기 때문에 수명 주기 관련 동작은 추가적인 명시적 호출이나 종속성 없이도 암시적으로 전달됩니다.

ViewModel 개체 내에 Lifecycle 개체가 필요하다고 생각되면 변환이 더 좋은 솔루션일 수 있습니다. 예를 들어 주소를 받아서 주소의 우편번호를 반환하는 UI 구성요소가 있다고 가정해 보세요. 이러한 경우 다음 샘플 코드에서와 같이 이 구성요소의 기본 ViewModel을 구현할 수 있습니다.

Kotlin

    class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {

        private fun getPostalCode(address: String): LiveData<String> {
            // DON'T DO THIS
            return repository.getPostCode(address)
        }
    }
    

자바

    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 구성요소를 이전 LiveData 개체에서 등록 취소하고 getPostalCode()를 호출할 때마다 새 인스턴스에 등록해야 합니다. 또한 UI 구성요소를 다시 생성하면 구성요소는 이전 호출의 결과를 사용하는 대신 또 다른 repository.getPostCode() 메서드 호출을 트리거합니다.

대신 다음 예에서와 같이 주소 입력의 변환으로 우편번호 조회를 구현할 수도 있습니다.

Kotlin

    class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {
        private val addressInput = MutableLiveData<String>()
        val postalCode: LiveData<String> = Transformations.switchMap(addressInput) {
                address -> repository.getPostCode(address) }

        private fun setInput(address: String) {
            addressInput.value = address
        }
    }
    

자바

    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 필드는 addressInput의 변환으로 정의됩니다. 앱에 postalCode 필드와 연결된 활성 관찰자가 있는 한 addressInput이 변경될 때마다 필드의 값이 다시 계산되고 검색됩니다.

이 메커니즘을 사용하면 하위 수준의 앱이 요청 시 느리게 계산되는 LiveData 개체를 만들 수 있습니다. ViewModel 개체는 LiveData 개체 참조를 쉽게 확보한 후 참조를 기반으로 변환 규칙을 정의할 수 있습니다.

새 변환 만들기

앱에 유용할 수 있는 다양한 관련 변환이 있지만 이러한 변환은 기본적으로 제공되지 않습니다. 자체적으로 변환을 구현하기 위해 MediatorLiveData 클래스를 사용할 수 있습니다. 이 클래스는 다른 LiveData 개체를 수신 대기하고 개체에서 내보낸 이벤트를 처리합니다. MediatorLiveData는 소스 LiveData 개체에 상태를 올바르게 전파합니다. 이 패턴에 관해 자세히 알아보려면 Transformations 클래스 참조 문서를 확인하세요.

여러 LiveData 소스 병합

MediatorLiveDataLiveData의 서브클래스로, 이 클래스를 통해 여러 LiveData 소스를 병합할 수 있습니다. 병합 후 MediatorLiveData 개체의 관찰자는 원본 LiveData 소스 개체가 변경될 때마다 트리거됩니다.

예를 들어 UI에 로컬 데이터베이스 또는 네트워크에서 업데이트될 수 있는 LiveData 개체가 있다면 MediatorLiveData 개체에 다음 소스를 추가할 수 있습니다.

  • 데이터베이스에 저장된 데이터와 연결된 LiveData 개체
  • 네트워크에서 액세스된 데이터와 연결된 LiveData 개체

활동은 두 소스에서 업데이트를 받기 위해 MediatorLiveData 개체만 관찰하면 됩니다. 자세한 예는 앱 아키텍처 가이드부록: 네트워크 상태 노출 섹션을 참조하세요.

추가 리소스

LiveData 클래스에 관해 자세히 알아보려면 다음 리소스를 참조하세요.

샘플

Codelab

블로그

동영상