Component LiveData - is designed to store the object and allows you to subscribe to its changes. The key feature is that the component is aware of the life cycle and allows you not to worry about at what stage the subscriber is now, if the subscriber is destroyed, the component will unsubscribe him. In order for LiveData to take into account the life cycle, the Lifecycle component is used, but it is also possible to use it without reference to the life cycle.
The component itself consists of the following classes:
LiveData, MutableLiveData, MediatorLiveData, LiveDataReactiveStreams, Transformations and the interface:
Observer .
')
The LiveData class is an abstract generic class and encapsulates the entire logic of the component. Accordingly, to create our LiveData Holder, it is necessary to inherit this class, specify the type that we plan to store in it as a typification, and also describe the logic of updating the stored object.
To update the value, we must pass it using the setValue (T) method, be careful because this method needs to be called from the main thread, otherwise we will get an
IllegalStateException , if we need to pass a value from another thread, we can use
postValue (T) , the method in turn updates the value in the main thread. An interesting feature of
postValue (T) is that, in the case of a multiple call, it will not create a queue of calls to the main thread, and when executing the code in the main thread, it will take the last value it received. Also, there are two kolbek in the class:
onActive () - will be called when the number of subscribers changes from 0 to 1.
onInactive () - will be called when the number of subscribers changes its value from 1 to 0.
Their purpose is to notify our class accordingly about whether it is necessary to update the data or not. By default, they have no implementation, and we need to override these methods to handle these events.
Let's take a look at how our LiveData class will look like, which will store the wife network name and update it if changed, for simplicity, it is implemented as a singleton.
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; } }
In general, the fragment logic is as follows: if someone signs up, we initialize the BroadcastReceiver, which will notify us of network changes, after the last subscriber unsubscribes, we stop tracking network changes.
In order to add a subscriber there are two methods:
observe (LifecycleOwner, Observer <T>) - to add a subscriber with respect to the life cycle and
observeForever (Observer <T>) - without considering. Data change notifications come through an implementation of the
Observer interface, which has one
onChanged (T) method.
It looks like this:
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);
Note: This fragment is for example purposes only; do not use this code in a real project. To work with LiveData, it is better to use the ViewModel (about this component in the next article) or take care of unsubscribing the observer manually.
If you use observe (this, this) when you rotate the screen, we will each time unsubscribe from our component and re-subscribe. And in the case of observeForever (this), we get a memory leak.In addition to the above methods, api LiveData also includes
getValue (), hasActiveObservers (), hasObservers (), removeObserver (Observer <T> observer), removeObservers (LifecycleOwner owner) do not need additional comments.
The MutableLiveData class is an extension of LiveData, with the difference that it is not an abstract class and the
setValue (T) and
postValue (T) methods are displayed in api, that is, public.
In fact, the class is a helper for those cases when we don’t want to put the value update logic in LiveData, but only want to use it as the Holder.
void update(String someText){ ourMutableLiveData.setValue(String); }
The MediatorLiveData class , as the name implies, is the implementation of the mediator pattern, just in case I remind you: the behavioral pattern defines an object that encapsulates how many objects interact, eliminating the need to explicitly refer to each other. The class itself extends
MutableLiveData and adds two methods to its API:
addSource (LiveData <T>, Observer <T>) and
removeSource (LiveData <T>) . The principle of working with a class is that we do not subscribe to a specific source, but to our MediatorLiveData, and add sources using
addSource (..) . MediatorLiveData in turn controls the subscription to the sources.
For example, create another class LiveData, which will store the name of our mobile network:
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; } }
And we will rewrite our application so that it displays the name of the wifi network, and if there is no connection to wifi, then the name of the mobile network, for this we change the 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); } }
As we can see, our UI is now subscribed to
MediatorLiveData and abstracted from a specific data source. It is worth paying attention to the fact that the values in our mediator do not depend directly on the sources and need to be set manually.
The class LiveDataReactiveStreams , the name misled me at first, thought it was an extension of LiveData using RX, in fact, the class is an adapter with two static methods:
fromPublisher (Publisher <T> publisher) , which returns a LiveData <T> object and
toPublisher (LifecycleOwner lifecycle, LiveData <T> liveData) , which returns a Publisher <T> object. To use this class, you need to import it separately:
compile "android.arch.lifecycle: reactivestreams: $ version"The Transformations class , which is a helper for changing the typing of LiveData, has two static methods:
map (LiveData <T>, Function <T, P>) - implements the implementation of the Function interface in the main thread and returns a LiveData <P> object, where T is the typing of the incoming LiveData, and P is the desired typing outgoing, in fact every time there will be changes in the incoming LiveData, it will notify our outgoing, and that in turn will notify subscribers after it converts the type using our Function implementation. This whole mechanism works due to the fact that outgoing LiveData is 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 >>) is similar to the map method with the difference that instead of changing the type in the function, we return the generated LiveData object.
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); } });
The basic example can be viewed in the repository:
gitAlso useful links:
one and
two .
Android Architecture Components. Part 1. IntroductionAndroid Architecture Components. Part 2. LifecycleAndroid Architecture Components. Part 3. LiveDataAndroid Architecture Components. Part 4. ViewModel