본문

180328(목) - Android Architecture Components (LiveData)

Android Architecture Components


LiveData

- observable data holder class

- regular observable과 다르게 lifecycle-aware 이고, activities, fragments or service 같은 app components의 lifecycle을 respects한다.

- 그래서 active lifecycle state에 있는 app component observers 만 update 되도록 ensures한다.

- lifecycle이 STARTED or RESUMED state이면 observer가 active state라고 간주하고, LiveData는 active observers에게만 updates를 notifies한다.


- LifecycleOwner implements와 paired하는 observer register 가능

- 이러한 relationship은 Lifecycle이 DESTROYED로 바뀔때 observer가 제거될 수 있도록 한다.

- 위와같은 동작은 leaks에 대해서 걱정할 필요가 줄어들어 유용함

- activity and fragment는 lifecycles이 destroyed되면 instantly unsubscribed


The advantages of using LiveData

Ensure your UI matches your data state

- observer object에서 UI를 update하기 위해 code consolidate 가능

- 매번 app date change일때 UI update를 하는 대신, observer update시에만 update 가능


No memory leaks

- observers는 Lifecycle objects에 bounding


No crashes due to stopped activities

- observer's lifecycle이 inactive이면 LiveData events를 받지 않는다.


No more manual lifecycle handling

- LiveData는 observing하고 있는 동안 relevant lifecycle status changes를 aware하므로  automatically manages 가능하다.


Always up to date data

- ex) background (not receive data) -> foreground (receive data)


Proper configuration changes

- configurations changes시 immediately receives latest available data


Sharing resoureces

- singleton pattern을 사용하여 LiveData를 wrapping하고  app에 shared 가능하다.


Work with LIveData objects

1. create and instance of LiveData.

- usually ViewModel class에서 작업


2. create Observer object onChanged() method

- usually UI controller (activity, fragment)


3. observe() method를 사용하여 LiveData object를 Observer object에 attach

- observe() method takes LifecycleOwner object

- Observer object를 LiveData object에 subscribes하여 changes notified

※ LifecycleOwner  대신에  observeForever(Observer)를 사용해도 되지만, 항상 active상태로 판단하므로 modifications notified도 항상 하게 된다. remove시에 removeObserver(Observer)를 사용


Create LiveData objects

- LiveData는 usually ViewModel에 저장 (이유는 ↓)

1. avoid activity and fragment

UI controllers는 displaying data만 하고 data state를 가지고 있으면 안된다.


2. specific activity or fragment에서 LiveData instances를 decouple, LiveData objects는 configuration changes에도 survive 가능

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...
}


Observe LiveData objects

- onCreate() method 가 LiveData를 begin observing  하기 좋은 위치이다.

1. onResume에서 redundant call 하지 않는다.

2. activity or fragment가 active되는 즉시, display가능한 data가 있는지 확인 가능


- 일반적으로 LiveData는 data changes and active observers일 때 update된다.

- 예외로 observers가 inactive -> active로 변경될 때 receive an update

- 또한, observer가 inactive -> active로 2번째 변경되면 last time value에서 값이 변경된 경우에만 receive한다.

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);
   
}
}

- observe()가 호출되어 parameter로 nameObserver가 전달되면 onChanged()가 즉시 invoked되어 most recent value stored in mCurrentName가 제공된다.

- LiveData object가 mCurrentName value를 setting 하지 않았으면 onChange()가 불리지 않는다.


Update LiveData objects

- LiveData에는 stored data를 update할 수 있는 publicly way는 없다

MutableLiveData는 setValue(T) and  postValue(T)를 expose하므로 LiveData object에 저장되어있는 값을 수정하려면 이 method를 사용해야 한다.

- 보통 MutableLiveData는 ViewModel에서 사용되고, ViewModel은 observers에게 immutable LiveData object만 expose한다.


- trigger all observers

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

- observer가 onChanged() calling.

- 위의 예제 뿐만아니라 response to network request or database load completing에도 사용 가능

※ main thread에서 LiveData를 update하려면 setValue(T)를 사용, worker thread에서 하려면 postValue(T) 사용


Use LiveData with Room

- Room persistence library는 LiveData object를 return하는 observable queries를 제공한다.

- observable queries는 DAO의 part로 작성된다

- Room은 db가 update될 때, LiveData를 update할 때 필요한 all code를 generates한다.

- generated code는 query를 asynchronously on background thread에서 run

Room persistent library guide.


Extend 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);
   
}
}
public class MyFragment extends Fragment {
   
@Override
   
public void onActivityCreated(Bundle savedInstanceState) {
       
super.onActivityCreated(savedInstanceState);
       
LiveData<BigDecimal> myPriceListener = ...;
        myPriceListener
.observe(this, price -> {
           
// Update the UI.
       
});
   
}
}

- first argument : LifecycleOwner

- LiveData object가 lifecycle-aware 한다는것은 multiple activities, fragments, services 사이에 이를 share 가능하다는 것을 의미한다.


- LiveData lass as a singleton

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);
   
}
}
public class MyFragment extends Fragment {
   
@Override
   
public void onActivityCreated(Bundle savedInstanceState) {
       
StockLiveData.get(getActivity()).observe(this, price -> {
           
// Update the UI.
       
});
   
}
}


Transform LiveData

- LiveData object를 observers dispatching 하기전에 저장된 값을 변경하거나, different LiveData instance another one을 return 해야할 때도 있다.

- Lifecycle은 이런 scenarios를 support하는 helper method인  Transformations 를 제공한다.

Transformations.map()

stored LiveData에 function를 적용하고 결과를 downstream에 전달

LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
    user
.name + " " + user.lastName
});

Transformations.switchMap()

stored LiveData에 function을 적용하고 unwraps and dispatchs한 결과를 downstream에 전달

must! LiveData를 return 해야 한다.

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

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


- transformations는 calculated lazily 이기 때문에 implicitly하게 동작된다.

- Lifecycle object를 ViewModel에 넣기를 원한다면 transformation이 좋은 solution

- 다음과 같이 naive 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 component는 getPostalCode()를 호출할 때 마다, 이전 LiveData object unregister and register new instance 

- UI component가 recreated되면 이전 call result 대신에 another call respository.getPostCode() method가 triggers된다.


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 field는 addressInput의 changes로 정의된다.

- 즉 addressInput이 변경되면 repository.getPostCode()가 call

- active인 observer가 없으면 calculations되지 않음


Create new transformations

- own transformations implement를 위해 other LiveData object를 listens하고 events emitted하는  MediatorLiveData  class를 사용할 수 있다.

- MediatorLiveData는 source LiveData state를 correctly propagates한다.

Transformations 


Merge multipel LiveData sources

- mediatorLiveData는 multiple LIveData source를 merge할 수 있는 LiveData의 subclass이다.

- original LiveData source가 변경될 때 마다 MediatorLiveData의 observers가 triggered 된다.

- 여러개의 sources update를 receive하려면 MediatorLiveData만 observe하면 된다.

Addendum: exposing network status

공유

댓글