Android Architecture Components meets AndroidThings

Android Architecture Components are a new set of opinionated guidelines written up by Google on how to structure your code when writing an Android application.

From the official documentation:

The most important thing you should focus on is the separation of concerns in your app. It is a common mistake to write all your code in an Activity or a Fragment. Any code that does not handle a UI or operating system interaction should not be in these classes. Keeping them as lean as possible will allow you to avoid many lifecycle related problems. Don’t forget that you don’t own those classes, they are just glue classes that embody the contract between the OS and your app. The Android OS may destroy them at any time based on user interactions or other factors like low memory. It is best to minimize your dependency on them to provide a solid user experience.

A separation of concerns is a must in all software engineering projects, and this applies across all applications for Android, even those running on AndroidThings!

The awesome people at Google have come up with an AndroidThings repository for demonstrating how to create an application and take it to production. It’s aim was not to display best practices in architecture and so this is a good example of how we could say an non-architectured “unloved” application would look. So I thought I’d take this example and refactor it to the recommended Android Architecture Components way of doing things keeping all existing functionality.

Here is the original:

https://github.com/androidthings/edison-candle

and here is the Architecture components refactoring:

https://github.com/blundell/edison-candle

This application shows how to create a flickering candle on AndroidThings. It controls two peripheral LEDs using the PWM protocol and animates the luminosity of the LEDs using an Android ObjectAnimator and the duty cycle api of the PWM LEDs.

The original architecture did everything in the application. It was responsible for connecting to the LEDs, setting up the object animators, tearing down the animators, controlling the LEDs luminosity, defining the rate the luminosity would change and disconnecting from the LEDs when the app is finished with.

Going back to the original quote from Android, it would be nice if we could separate these concerns into separate classes to improve our app and our codebase. The new Android Architecture Components can help here.

Let’s step through each refactoring.

ViewModel

A ViewModel provides the data for a specific UI component, such as a fragment or activity, and handles the communication with the business part of data handling, such as calling other components to load the data or forwarding user modifications. The ViewModel does not know about the View and is not affected by configuration changes such as recreating an activity due to rotation.

Ok so the ViewModel doesn’t know about the View (which in our case is the LEDs) but should know about loading data, and the loading we need to do is to load the luminosity and flickering of light.

Alongside the ViewModel it is recommended to use LiveData.

LiveData is a data holder class which keeps a value and allows this value to be observed. Unlike a regular observable, LiveData respects the lifecycle of app components, such that the Observer can specify a Lifecycle in which it should observe.

LiveData describes the values for the luminosity of our LEDs.

Using these two together you end up with a ViewModel that looks like this:

class FlameViewModel extends ViewModel {

    // mediator for the LED flickering flames data source
    private final FlameRepository repository;

    //observable data holder for the LED flickering flames
    private LiveData<Flames> flames;

    public FlameViewModel(FlameRepository repository) {
        this.repository = repository;
    }

    public LiveData<Flames> getFlames() {
        // ViewModel is created per Activity so we only
        // need to instantiate this once
        if (flames == null) {
            flames = repository.getFlames();
        }
        return flames;
    }

    @Override
    protected void onCleared() {
        repository.tearDown();
    }
}

The LiveData will hold our LED values and the repository will be used for retrieving these values (which in turn are calculated). The interesting method here is onCleared, this allows us to perform any teardown procedures when the Activity is no longer interested in our ViewModel. In our case we stop the loop that was changing the luminosity value over time.

The other concern in this project is connecting and disconnecting from the LED peripherals. This usually involves the android lifecycle and can be done in onCreate/onDestroy or onStart/onStop. The Architecture Components has created a LifecycleObserver class which you can now use to be informed when the Activity is transitioning between these states. Lets use that to connect and disconnect our LEDs.


LedCandleLight extends LifecycleObserver {

        // PWM output pin names
        private static final String YELLOW_PWM = BoardDefaults.getYellowPwmPort();
        private static final String ORANGE_PWM = BoardDefaults.getOrangePwmPort();
        // Brightness values
        private static final float BRIGHTNESS_START = 25f;

        private final String ledPinName;

        private Pwm led;

        public static Led yellow() {
            return new Led(YELLOW_PWM);
        }

        public static Led orange() {
            return new Led(ORANGE_PWM);
        }

        public Led(String ledPinName) {
            this.ledPinName = ledPinName;
        }

        @Override
        @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
        public void onCreate() {
            try {
                led = openLed(ledPinName);
            } catch (IOException e) {
                throw new IllegalStateException("Unable to open LED connections", e);
            }
        }

        /**
         * Open a new LED connection to the provided port name
         *
         * @throws IOException
         */
        private Pwm openLed(String name) throws IOException {
            PeripheralManagerService service = new PeripheralManagerService();
            Pwm led = service.openPwm(name);
            led.setPwmFrequencyHz(60.0f);
            led.setPwmDutyCycle(BRIGHTNESS_START);
            led.setEnabled(true);

            return led;
        }

        @Override
        public void burn(Flame flame) {
            try {
                led.setPwmDutyCycle(flame.getHeight());
            } catch (IOException e) {
                Log.w(TAG, "Unable to set led duty cycle", e);
            }
        }

        @Override
        @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
        public void onDestroy() {
            try {
                closeLed(led);
            } catch (IOException e) {
                Log.w(TAG, "Unable to close LED connections", e);
            }
        }

        /**
         * Close the provided PWM connection
         *
         * @throws IOException
         */
        private void closeLed(Pwm pwm) throws IOException {
            if (pwm != null) {
                pwm.setEnabled(false);
                pwm.close();
            }
        }
    }

Now we can use the @OnLifecycleEvent() annotation with wither Lifecycle.Event.ON_CREATE or Lifecycle.Event.ON_DESTROY to allow us to subscribe to the lifecycle events. This is a further separation of concerns and the LEDs can look after themselves rather than the activity having to do it.

This leaves our activity in a very different state that the original code. Its only responsibility now is to observe the ViewModel.

public class HomeActivity extends LifecycleActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Create and manage the connections to the peripheral LEDs
        final CandleLight yellowCandleLight = CandleLight.Led.yellow();
        final CandleLight orangeCandleLight = CandleLight.Led.orange();
        getLifecycle().addObserver(yellowCandleLight);
        getLifecycle().addObserver(orangeCandleLight);

        // Load our ViewModel
        FlameViewModel viewModel = ViewModelProviders.of(this, new ViewModelFactory())
                .get(FlameViewModel.class);

        // Observe the flickering flame data and update the peripheral LEDs
        viewModel.getFlames()
                .observe(this, new Observer<Flames>() {
                    @Override
                    public void onChanged(Flames flames) {
                        yellowCandleLight.burn(flames.getYellowFlame());
                        orangeCandleLight.burn(flames.getOrangeFlame());
                    }
                });
    }
}

I hope you can see the power & benefit for this separation. There is plenty more in depth explanation inside the official documentation https://developer.android.com/topic/libraries/architecture/guide.html and I would always recommend learning by example.

Take an existing application, write down what you think the responsibilities this application has and then attempt to refactor it into these layers. Go and have a try!

As always, I’m open to questions and discussion on twitter @blundell_apps

Leave a Reply

Your email address will not be published. Required fields are marked *