Real life examples with RxJava 2 - Observable.concatArrayDelayError()

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?

Like
Reply

To view or add a comment, sign in

More articles by Ahmed Adel Ismail

  • Sharing data across multiple Mobile Squads - with examples

    Earlier I shared an article suggesting a solution to a common problem with teams following the "Spotify Model", which…

  • SDD - Squad Driven Design

    Working in multiple big teams I've found that we are always trying to apply our known best practices in software, but…

    4 Comments
  • Easier Testing with MVVM, MVI, MVP and Kotlin Multiplatform

    Before we start, this article requires basic knowledge about the following topics : Clean Architecture Unit Testing…

    9 Comments
  • Android - A Cleaner Clean Architecture

    It has been a while now since Clean Architecture was out, and even many of us started embracing hexagonal (ports and…

    9 Comments
  • Beyond Functional Programming

    In the Android industry, lately functional programming was the all new stuff to learn, RxJava, Kotlin, and the whole…

    7 Comments
  • Dependency Injection in Clean Architecture

    After Google's Opinionated Guide to Dependency Injection Video, Google made a clear statement that they want developers…

    18 Comments
  • Meta Programming in Android

    Year after year we are getting rid of the boilerplate code that we need to write for small and simple tasks in Android,…

    2 Comments
  • MVI Pattern For Android In 4 Steps

    Lately I wrote an article about MVI pattern, but as we are facing new problems every day and face more use-cases, we…

    7 Comments
  • Agile - Moving Fast

    We always here about Agile, and think about which methodology do we use, what practices do we have, team velocity…

    1 Comment
  • Kotlin Unit Testing with Mockito

    I've always believed that, if the code is designed to be tested, we wont need any testing framework or library ..

    17 Comments

Others also viewed

Explore content categories