Компонент LiveData — предназначен для хранения объекта и разрешает подписаться на его изменения. Ключевой особенностью является то, что компонент осведомлен о жизненном цикле и позволяет не беспокоится о том, на каком этапе сейчас находиться подписчик, в случае уничтожения подписчика, компонент отпишет его от себя. Для того, чтобы LiveData учитывала жизненный цикл используется компонент Lifecycle, но также есть возможность использовать без привязки к жизненному циклу.
Сам компонент состоит из классов: LiveData, MutableLiveData, MediatorLiveData, LiveDataReactiveStreams, Transformations и интерфейса: Observer.
Класс LiveData, являет собой абстрактный дженериковый класс и инкапсулирует всю логику работы компонента. Соответственно для создания нашего LiveData холдера, необходимо наследовать этот класс, в качестве типизации указать тип, который мы планируем в нем хранить, а также описать логику обновления хранимого объекта.
Для обновления значения мы должны передать его с помощью метода setValue(T), будьте внимательны поскольку этот метод нужно вызывать с main треда, в противном случае мы получим IllegalStateException, если же нам нужно передать значение из другого потока можно использовать postValue(T), этот метод в свою очередь обновит значение в main треде. Интересной особенностью postValue(T) является еще то, что он в случае множественного вызова, не будет создавать очередь вызовов на main тред, а при исполнении кода в main треде возьмет последнее полученное им значение. Также, в классе присутствует два колбека:
onActive() — будет вызван когда количество подписчиков изменит свое значение с 0 на 1.
onInactive() — будет вызван когда количество подписчиков изменит свое значение с 1 на 0.
Их назначение соответственно уведомить наш класс про то, что нужно обновлять даные или нет. По умолчанию они не имеют реализации, и для обработки этих событий мы должны переопределить эти методы.
Давайте рассмотрим, как будет выглядеть наш LiveData класс, который будет хранить wife network name и в случае изменения будет его обновлять, для упрощения он реализован как синглтон.
public class NetworkLiveData extends LiveData<String> {
private Context context;
private BroadcastReceiver broadcastReceiver;
private static NetworkLiveData instance;
public static NetworkLiveData getInstance(Context context){
if (instance==null){
instance = new NetworkLiveData(context.getApplicationContext());
}
return instance;
}
private NetworkLiveData(Context context) {
this.context = context;
}
private void prepareReceiver(Context context) {
IntentFilter filter = new IntentFilter();
filter.addAction("android.net.wifi.supplicant.CONNECTION_CHANGE");
filter.addAction("android.net.wifi.STATE_CHANGE");
broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = wifiMgr.getConnectionInfo();
String name = wifiInfo.getSSID();
if (name.isEmpty()) {
setValue(null);
} else {
setValue(name);
}
}
};
context.registerReceiver(broadcastReceiver, filter);
}
@Override
protected void onActive() {
prepareReceiver(context);
}
@Override
protected void onInactive() {
context.unregisterReceiver(broadcastReceiver);
broadcastReceiver = null;
}
}
В целом логика фрагмента следующая, если кто-то подписывается, мы инициализируем BroadcastReceiver, который будет нас уведомлять об изменении сети, после того как отписывается последний подписчик мы перестаем отслеживать изменения сети.
Для того чтобы добавить подписчика есть два метода: observe(LifecycleOwner, Observer<T>) — для добавления подписчика с учетом жизненного цикла и observeForever(Observer<T>) — без учета. Уведомления об изменении данных приходят с помощью реализации интерфейса Observer, который имеет один метод onChanged(T).
Выглядит это приблизительно так:
public class MainActivity extends LifecycleActivity implements Observer<String> {
private TextView networkName;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
networkName = (TextView) findViewById(R.id.network_name);
NetworkLiveData.getInstance(this).observe(this,this);
//NetworkLiveData.getInstance(this).observeForever(this);
}
@Override
public void onChanged(@Nullable String s) {
networkName.setText(s);
}
}
Примечание: Этот фрагмент только для примера, не используйте этот код в реальном проекте. Для работы с LiveData лучше использовать ViewModel(про этот компонент в следующей статье) или позаботиться про отписку обсервера вручную.
В случае использования observe(this,this) при повороте экрана мы будем каждый раз отписываться от нашего компонента и заново подписываться. А в случае использование observeForever(this) мы получим memory leak.
Помимо вышеупомянутых методов в api LiveData также входит getValue(), hasActiveObservers(), hasObservers(), removeObserver(Observer<T> observer), removeObservers(LifecycleOwner owner) в дополнительных комментариях не нуждаются.
Класс MutableLiveData, является расширением LiveData, с отличием в том что это не абстрактный класс и методы setValue(T) и postValue(T) выведены в api, то есть публичные.
По факту класс является хелпером для тех случаев когда мы не хотим помещать логику обновления значения в LiveData, а лишь хотим использовать его как Holder.
void update(String someText){
ourMutableLiveData.setValue(String);
}
Класс MediatorLiveData, как понятно из названия это реализация паттерна медиатор, на всякий случай напомню: поведенческий паттерн, определяет объект, инкапсулирующий способ взаимодействия множества объектов, избавляя их от необходимости явно ссылаться друг на друга. Сам же класс расширяет MutableLiveData и добавляет к его API два метода: addSource(LiveData<T>, Observer<T>) и removeSource(LiveData<T>). Принцип работы с классом заключается в том что мы не подписываемся на конкретный источник, а на наш MediatorLiveData, а источники добавляем с помощью addSource(..). MediatorLiveData в свою очередь сам управляет подпиской на источники.
Для примера создадим еще один класс LiveData, который будет хранить название нашей мобильной сети:
public class MobileNetworkLiveData extends LiveData<String> {
private static MobileNetworkLiveData instance;
private Context context;
private MobileNetworkLiveData(Context context) {
this.context = context;
}
private MobileNetworkLiveData() {
}
@Override
protected void onActive() {
TelephonyManager telephonyManager = (TelephonyManager) context
.getSystemService(Context.TELEPHONY_SERVICE);
String networkOperator = telephonyManager.getNetworkOperatorName();
setValue(networkOperator);
}
public static MobileNetworkLiveData getInstance(Context context) {
if (instance == null) {
instance = new MobileNetworkLiveData(context);
}
return instance;
}
}
И перепишем наше приложение так чтоб оно отображало название wifi сети, а если подключения к wifi нет, тогда название мобильной сети, для этого изменим MainActivity:
public class MainActivity extends LifecycleActivity implements Observer<String> {
private MediatorLiveData<String> mediatorLiveData;
private TextView networkName;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
networkName = (TextView) findViewById(R.id.network_name);
mediatorLiveData = new MediatorLiveData<>();
init();
}
private void init() {
final LiveData<String> network = NetworkLiveData.getInstance(this);
final LiveData<String> mobileNetwork = MobileNetworkLiveData.getInstance(this);
Observer<String> networkObserver = new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
if (!TextUtils.isEmpty(s))
mediatorLiveData.setValue(s);
else
mediatorLiveData.setValue(mobileNetwork.getValue());
}
};
Observer<String> mobileNetworkObserver = new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
if (TextUtils.isEmpty(network.getValue())){
mediatorLiveData.setValue(s);
}
}
};
mediatorLiveData.addSource(network, networkObserver);
mediatorLiveData.addSource(mobileNetwork,mobileNetworkObserver);
mediatorLiveData.observe(this, this);
}
@Override
public void onChanged(@Nullable String s) {
networkName.setText(s);
}
}
Как мы можем заметить, теперь наш UI подписан на MediatorLiveData и абстрагирован от конкретного источника данных. Стоит обратить внимание на то что значения в нашем медиаторе не зависят напрямую от источников и устанавливать его нужно в ручную.
Класс LiveDataReactiveStreams, название ввело меня в заблуждение поначалу, подумал что это расширение LiveData с помощью RX, по факту же, класс являет собой адаптер с двумя static методами: fromPublisher(Publisher<T> publisher), который возвращает объект LiveData<T> и toPublisher(LifecycleOwner lifecycle, LiveData<T> liveData), который возвращает объект Publisher<T>. Для использования этого класса, его нужно импортировать отдельно:
compile «android.arch.lifecycle:reactivestreams:$version»
Класс Transformations, являет собой хелпер для смены типизации LiveData, имеет два static метода:
map(LiveData<T>, Function<T,P>) - применяет в main треде реализацию интерфейса Function и возвращает объект LiveData<P>, где T — это типизация входящей LiveData, а P желаемая типизация исходящей, по факту же каждый раз когда будет происходить изменения в входящей LiveData она будет нотифицировать нашу исходящую, а та в свою очередь будет нотифицировать подписчиков после того как переконвертирует тип с помощью нашей реализации Function. Весь этот механизм работает за счет того что по факту исходящая LiveData, является MediatiorLiveData.
LiveData<Location> location = ...;
LiveData<String> locationString = Transformations.map(location, new Function<Location, String>() {
@Override
public String apply(Location input) {
return input.toString;
}
});
switchMap(LiveData<T>, Function<T, LiveData<P>>) - похож к методу map с отличием в том, что вместо смены типа в функции мы возвращаем сформированный объект LiveData.
LiveData<Location> location = ...;
LiveData<Place> getPlace(Location location) = ...;
LiveData<Place> userName = Transformations.switchMap(location, new Function<Location, LiveData<Place>>() {
@Override
public LiveData<Place> apply(Location input) {
return getPlace(input);
}
});
Базовый пример можно посмотреть в репозитории: git
Также полезные ссылки: раз и два.
Android Architecture Components. Часть 1. Введение
Android Architecture Components. Часть 2. Lifecycle
Android Architecture Components. Часть 3. LiveData
Android Architecture Components. Часть 4. ViewModel
Поделиться с друзьями
Комментарии (6)
tundrawolf_kiba
22.07.2017 21:17+2Если не сложно — можете добавить в статьи оглавление по текущим статьям?
Siemargl
Отличная идея в статье про мобильную разработку, приклеить титульную бесполезную картинку на 5Мб.
Класс виден сразу, молодцы!
kb2fty7
Исправлено, текущий размер 155кб.
Спасибо за отзыв