Real life examples with RxJava 2 - Observable.concatArrayDelayError()
This is an example for a class that retrieves Mac-address for an Android device, it attempts to get the Mac-address from 3 different sources ... Shared-Preferences, WiFi-Manager and Network-interfaces.
After the first source returns a value, we will save it to the Shared Preferences, then return it ... if no source could manage to come with the Mac-address ... nothing happens
Dependencies : Rx Java 2, Retro lambda, Guava, J-Curry, and a custom Preferences class that loads or saves data regardless of there types
/**
* a {@link Maybe} that retrieves the Mac address from any of the following sources :
* <p>
* {@code Shared Preference}<br>
* {@code Wifi}<br>
* {@code Network interfaces}
* <p>
* then saves it to {@code Shared preferences} if found
* <p>
* Created by Ahmed Adel Ismail on 7/10/2017.
*/
public class MacAddressScanner extends Maybe<String>{
public static final String INVALID_MAC_ADDRESS = "02:00:00:00:00:00";
private static final String PREFERENCES_KEY = "com.commons.system.MacAddressScanner";
private final Context context;
public MacAddressScanner(Context context) {
this.context = context.getApplicationContext();
}
@Override
@SuppressWarnings("unchecked")
protected void subscribeActual(MaybeObserver<? super String> observer) {
Observable.concatArrayDelayError(preferences(), wifi(), networkInterface())
.firstElement()
.doOnSuccess(Curry.apply(new PreferenceSaver<>(), PREFERENCES_KEY))
.subscribe(observer::onSuccess, observer::onError, observer::onComplete);
}
private Observable<String> preferences() {
return Observable.just(PREFERENCES_KEY)
.map(Curry.apply(new PreferenceLoader<>(), INVALID_MAC_ADDRESS))
.filter(MacAddressScanner::isValid);
}
@SuppressLint("WifiManagerPotentialLeak")
private Observable<String> wifi() {
return Observable.just((WifiManager) context.getSystemService(Context.WIFI_SERVICE))
.filter(WifiManager::isWifiEnabled)
.map(WifiManager::getConnectionInfo)
.map(WifiInfo::getMacAddress)
.filter(MacAddressScanner::isValid);
}
private Observable<String> networkInterface() {
return Observable.fromIterable(networkInterfacesList())
.filter(MacAddressScanner::isWlan0)
.firstElement()
.map(NetworkInterface::getHardwareAddress)
.map(Bytes::asList)
.flatMapObservable(Observable::fromIterable)
.map(MacAddressScanner::toFormattedByte)
.reduce(MacAddressScanner::concatenate)
.map(String::toLowerCase)
.flatMapObservable(Observable::just)
.filter(MacAddressScanner::isValid);
}
@NonNull
private List<NetworkInterface> networkInterfacesList() {
try {
return Collections.list(NetworkInterface.getNetworkInterfaces());
}catch (SocketException e) {
L.x(e);
return new ArrayList<>();
}
}
private static boolean isWlan0(NetworkInterface networkInterface) {
return networkInterface.getName().equalsIgnoreCase("wlan0");
}
private static String toFormattedByte(Byte b) {
return String.format("%02X", b);
}
private static String concatenate(String firstString, String secondString) {
return Joiner.on(":").join(firstString, secondString);
}
private static boolean isValid(String macAddress) {
return !INVALID_MAC_ADDRESS.equals(macAddress) && !TextUtils.isEmpty(macAddress);
}
}
/**
* a function that saves data in Preferences
*
* Created by Ahmed Adel Ismail on 7/11/2017.
*/
public class PreferenceSaver<T> implements BiConsumer<String, T>{
@Override
public void accept(String key, T value){
Preferences.getInstance().save(key, value);
}
}
/**
* a function that loads a value from preference, and if it failed, it returns a default value
* passed as the first parameter
* <p>
* Created by Ahmed Adel Ismail on 7/11/2017.
*/
public class PreferenceLoader<T> implements BiFunction<T, String, T>{
@Override
public T apply(@NonNull T defaultValue, @NonNull String key) {
return Preferences.getInstance().load(key, defaultValue);
}
}
Imagine this code with Imperative style, like the for loops, and doing every detail , step by step ... managing 3 sources, which one will get the result first, the tons of if/else statements, counters and Boolean fields
the best thing about functional programming and it's declarative style is that it is very readable, easy to track the flow, and very hard to have bugs, as soon as it runs once correctly, it will always run correctly (no state, every variable is immutable / final) ... although this function has a side-effect (writing to preferences), but even this side effect is done every time in a systematic manner
** If you do not know Rx-Java 2, do not panic if you did not understand the code , it is just a matter of time ... you can ask me if you like
great article ya Ahmed, Is creating static vars and context variable breaks the functional programming style?