share

ENGINEERING

5min read

Maintaining Open-Source RxAndroidBle Library (Introduction)

The User, the Developer and the Maintainer

The end of 2015 was the moment when we started to give back to the Open Source what we had learned during several BLE related projects. We have released the RxAndroidBle library that has wrapped a cumbersome native Android BLE API into handy RxJava Observables. This library has proven to dramatically decrease the time needed for implementing the typical usage of this communication channel.

Maintaining a non-trivial Open Source library for an extended amount of time is quite similar to developing an application module in a long-lasting enterprise project:

  • Users of the API may come with little to no domain knowledge needed to work robustly
  • Developers are expected to support the already developed code for a long time
  • Maintainers, who will eventually come, will also lack the domain and codebase knowledge

During the last eighteen months we have tried different techniques of efficiently sharing information with all the interested parties. The following series of posts will be an account of our experiences. We will discuss what have proved to work best so you could have a better start (and waste less time)!

Two words about RxJava (and other Reactive frameworks)

RxJava is a library coming from the family of several other Functional-Reactive frameworks (RxSwift, RxScala, RxJs—just to name a few) centered around Observables. If you have not heard about the Observable pattern, you can read more about it here. The concept of Observables is very similar to streams—a feature introduced in Java 8. Unfortunately, the streams are not available for Android development.

The most important thing from the developer’s point of view is that the Observable pattern allows for an extremely easy orchestration of work—or to describe it better—designing the flow of data.

Imagine that you have to perform a server API call using an access token in your application. If the access token is rejected then you would need to refresh it and retry the operation.

Using old fashioned callbacks it could look similar to the code below:

class CallbackImplementation {
    void performCall() {
        performServerApiCall(getCurrentAccessToken(), response -> {
            // handle the response
        }, initialThrowable -> {
            if (isAccessDenied(initialThrowable)) {
                performTokenRefresh(newAccessToken -> {
                    // retry the initial call with a new access token
                    performServerApiCall(newAccessToken, response -> {
                        // handle the response
                    }, retryThrowable -> {
                        // handle the retried call error
                    });
                }, refreshThrowable -> {
                    // handle the refresh throwable
                });
            } else {
                // handle the initial throwable
            }
      });
    }
}

As the first example shows, in order to understand the flow, the reader needs to hop several times back and forth to grasp all the relevant information. Whereas when using the RxJava it could look like this (assuming that both performServerApiCall() and performTokenRefresh() are returning Observables):

class RxJavaImplementation {
    void performCall() {
        Observable.defer(() -> performServerApiCall(getCurrentAccessToken()))
            .retryWhen(throwableObservable -> throwableObservable.take(1).flatMap(throwable -> {
                if (isAccessDenied(throwable)) {
                    return performTokenRefresh();
                } else {
                    return Observable.error(throwable);
                }
            }))
            .subscribe(response -> {
                // handle the response
            }, throwable -> {
                // handle the throwable
            });
    }
}

The second fragment of the code has less indents and is quite easy to comprehend thanks to the linear relation between actions. Of course, the above-mentioned fragment could be easier to read thanks to extracting the .retryWhen() parameter and naming it for instance tokenIsRefreshedAfterFirstAccessDenied.

As the complexity grows, the advantage of Reactive-Functional approach is getting more and more visible.

Perspective: the User

When it comes to learning a new library / framework there are two types of users:

  • those who start with reading the documentation
  • those who start with using the API

To be honest, most of the developers I know tend not to dive deep into the documentation when learning the new framework. Usually, when new users approach a library, it is because they want to save the time on learning how to properly use a new API.

rx_screen.png

The question is: how to make it easier for them?

During the development of the Bluetooth libraries we have tested some of the approaches that were presented during conferences all around the world.

A right abstraction

Users who approach the new topic usually try to create a mental model that will fit well into their minds. When creating an RxJava library you sometimes need to change the way you think about a problem to get the best interaction possible.

Raw Android BLE API abstraction looks like this:

  • BluetoothAdapter—the main entry point for all Bluetooth interactions. This is the place to check if the specific Android device is Bluetooth-capable and able to start scanning for nearby Bluetooth devices.
  • BluetoothDevice—an abstraction of a Bluetooth device to connect to.
  • BluetoothGatt—the attribute table of a specific Bluetooth device. GATT stands for Generic Attribute Profile and may contain different services (a.k.a functionalities) which in turn consist of characteristics which may be considered “shelves” for actual data. But let’s not dig into this too deep—this is all we need to know right now.

One of the issues of the native API is that it is inherently stateful. When trying to use the device, one need to scan it first. But if, for some reason, the Bluetooth adapter (radio) gets disabled during that time, the user will not get any information about it, unless they know what they should look for. Similar problems appear during establishing a connection to an already scanned device. When the radio goes off, there will be no information where one would expect it to be—no callback will get called. Even when a connection is already established and the user tries to read or write a particular characteristic still if the radio gets turned off—the error callback does not get called. The same happens if the connection gets broken in the middle of a read operation.

It was obvious that to make the native API work in a user-friendly manner, there is a need to wrap it and route all potential errors to a place where the user expects them to be reported. At the beginning of our work on theRxAndroidBle, we made 3 attempts to design the right abstraction to interact with.

Stay tuned for the next blogpost on designing the abstraction!

share


DarekSenior Software Engineer

LEARN MORE

Contact us if you have any questions regarding the article or just want to chat about technology, our services, job offers and more!

POLIDEA NEWSLETTER

Sign in and expect sharp insights, recommendations, ebooks and fascinating project stories delivered to your inbox

The controller of the personal data that you are about to provide in the above form will be Polidea sp. z o.o. with its registered office in Warsaw at ul. Przeskok 2, 00-032 Warsaw, KRS number: 0000330954, tel.: 0048 795 536 436, email: hello@polidea.com (“Polidea”). We will process your personal data based on our legitimate interest and/or your consent. Providing your personal data is not obligatory, but necessary for Polidea to respond to you in relation to your question and/or request. If you gave us consent to call you on the telephone, you may revoke the consent at any time by contacting Polidea via telephone or email. You can find detailed information about the processing of your personal data in relation to the above contact form, including your rights relating to the processing, HERE.

Data controller:

The controller of your personal data is Polidea sp. z o.o. with its registered office in Warsaw at ul. Przeskok 2, 00-032 Warsaw, KRS number: 0000330954, tel.: [0048795536436], email: [hello@polidea.com] (“Polidea”)

Purpose and legal bases for processing:

 

Used abbreviations:

GDPR – Regulation (EU) 2016/679 of the European Parliament and of the Council of 27 April 2016
on the protection of natural persons with regard to the processing of personal data and on the free movement
of such data, and repealing Directive 95/46/EC (General Data Protection Regulation)

ARES – Polish Act on Rendering Electronic Services dated 18 July 2002

TL – Polish Telecommunications Law dated 16 July 2004

1)        sending to the given email address a newsletter including information on Polidea’s new projects, products, services, organised events and/or general insights from the mobile app business world |art. 6.1 a) GDPR, art. 10.2 ARES and art. 172.1 TL (upon your consent)

Personal data:name, email address

2)       statistical, analytical and reporting purposes |art. 6. 1 f) GDPR (based on legitimate interests pursued by Polidea, consisting in analysing the way our services are used and adjusting them to our clients’ needs, as well as developing new services)

Personal data:name, email address

Withdrawal of consent:

You may withdraw your consent to process your personal data at any time.

Withdrawal of the consent is possible solely in the scope of processing performed based on the consent. Polidea is authorised to process your personal data after you withdraw your consent if it has another legal basis for the processing, for the purposes covered by that legal basis.

Categories of recipients:

Your personal data may be shared with:

1)       authorised employees and/or contractors of Polidea

2)       persons or entities providing particular services to Polidea (accounting, legal, IT, marketing and advertising services) – in the scope required for those persons or entities to provide those services to Polidea

 

Retention period:

1)       For the purpose of sending newsletter to the given email address – for as long as the relevant consent is not withdrawn

2)       For statistical, analytical and reporting purposes – for as long as the relevant consent is not withdrawn

Your rights:

 

Used abbreviation:

GDPR – Regulation (EU) 2016/679 of the European Parliament and of the Council of 27 April 2016
on the protection of natural persons with regard to the processing of personal data and on the free movement
of such data, and repealing Directive 95/46/EC (General Data Protection Regulation)

According to GDPR, you have the following rights relating to the processing of your personal data, exercised by contacting Polidea via [e-mail, phone].

1)       to access to your personal data (art. 15 GDPR) by requesting sharing and/or sending a copy of all your personal data processed by Polidea

2)       to request rectification of inaccurate personal data
(art. 16 GDPR) by indicating the data requiring rectification

3)       to request erasure of your persona data (art. 17 GDPR); Polidea has the rights to refuse erasing the personal data in specific circumstances provided by law

4)       to request restriction of processing of your personal data (art. 18 GDPR) by indicating the data which should be restricted

5)       to move your personal data (art. 20 GDPR) by requesting preparation and transfer by Polidea of the personal data that you provided to Polidea to you or another controller in a structured, commonly used machine-readable format

6)       to object to processing your personal data conducted based on art. 6.1 e) or f) GDPR, on grounds relating to your particular situation (art. 21 GDPR)

7)       to lodge a complaint with a supervisory authority,
in particular in the EU member state of your habitual residence, place of work or place of the alleged infringement if you consider that the processing
of personal data relating to you infringes the GDPR
(art. 77.1 GDPR)

No obligation to provide data:

Providing your personal data is not obligatory, but necessary for Polidea to provide you the newsletter service

Refusal to provide the above data will result in inability to receive the newsletter service.

Profiling

In the process of providing the newsletter service, we make decisions in an automated way, including profiling, based on the data you provide.

 

“Profiling” means automated processing of personal data consisting of the use of your personal data to evaluate certain personal aspects relating to you, in particular to analyze or predict aspects concerning your personal preferences and interests.

 

The automated decisions are taken based on the analysis of clicked and viewed content. They affect the targeting of specific newsletter content to selected users registered to receive the newsletter service, based on the anticipated interests of the recipient.