
Компонент 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
