A non-trivial Bluetooth LE app using Kable and app architecture best practices

Overview

kable_mvvm_demo

The intention of this project is to demonstrate a non-trivial Bluetooth LE app using Kable and app architecture best practices.

⚠️ There are problems with it so I am open sourcing it "early" in the hopes others might contribute fixes and enhancements. I do not claim to be even a decent Android developer. This is really my first actual Android project, so expect problems other than what I've identified below 🙂

Motivations

You can find a decent number of open-source projects on GitHub and elsewhere that implement BLE functionality. So far I have not found one that I am completely happy with. In my experience, there is always at least one thing wrong with each project. Some common issues I have seen:

  • Lack of any structure whatsoever (e.g. all the logic in the Activity). Common in examples from chip vendors.
  • Callback hell (instead of using something like coroutines)
  • "Only" implementing scanning - it is relatively straightforward to structure an app that just scans for BLE peripherals. There are many examples on GitHub. Much harder (and from my searching, all but non-existent on GitHub) are apps that connect to those peripherals and actually do something1, 2.
  • BLE functionality in the ViewModel which goes against best practices

1 Bonus points if you can find an example of an app that demonstrates maintaining a connection to a peripheral across multiple fragments. I haven't found any.

2 Yes, I realize this app currently doesn't do much more than scanning. I want to fix the fundamental issues listed below before I put effort into other areas.

Current status

Currently the app implements scanning and displaying the results. If you click on a result, a connection will be initiated and you'll switch to a different fragment.

Big to-dos and issues

  • Something is wrong the view model lifecycle. If you rotate the screen while scanning, the screen will be cleared and the scan will no longer be running. Fixed by this commit
  • I'm not entirely sure if the service binding strategy I have chosen is sound/optimal.

Basic design

Scanning takes place in BluetoothLeService which is a relatively straightforward LifecycleService. Detected devices are exposed by the service as a StateFlow. The service also exposes a scanStatus StateFlow.

Connecting the service to the view model (BleViewModel) is a little tricky. I wanted to avoid having to make BluetoothLeService a singleton. Instead, I wrote a small annotation processor (the processor module) which takes the service class and generates a wrapper class like below.

The wrapper acts as a proxy around the service and forwards state from the service (when it is bound). The view model can observe the wrapper's flows, regardless of whether the underlying service is bound or not. This makes the lifecycles easier.

@Singleton
public open class BluetoothLeServiceWrapperBase(
    private val applicationContext: Context
) : LifecycleObserver {
    private val _advertisements: MutableStateFlow<List<Advertisement>> =
        MutableStateFlow(emptyList())
    public val advertisements: StateFlow<List<Advertisement>> = _advertisements.asStateFlow()

    private val _connectState: MutableStateFlow<ConnectState?> = MutableStateFlow(null)
    public val connectState: StateFlow<ConnectState?> = _connectState.asStateFlow()

    private val _scanStatus: MutableStateFlow<ScanStatus?> = MutableStateFlow(null)
    public val scanStatus: StateFlow<ScanStatus?> = _scanStatus.asStateFlow()

    protected lateinit var _service: BluetoothLeService
    private var _bound: Boolean = false
    public val _connection: ServiceConnection = object : ServiceConnection {
        public override fun onServiceConnected(className: ComponentName, service: IBinder) {
            val binder = service as BluetoothLeService.LocalBinder
            _service = binder.getService()
            _bound = true
            this@BluetoothLeServiceWrapperBase.onServiceConnected(_service)
            _service.lifecycleScope.launch {
                launch {
                    _advertisements.emitAll(_service.advertisements)
                }
                launch {
                    _connectState.emitAll(_service.connectState)
                }
                launch {
                    _scanStatus.emitAll(_service.scanStatus)
                }
            }
        }

        public override fun onServiceDisconnected(className: ComponentName) {
            _bound = false
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    public fun handleLifecycleStart() {
        Intent(applicationContext, BluetoothLeService::class.java).also { intent ->
            applicationContext.bindService(intent, _connection, Context.BIND_AUTO_CREATE)
        }
    }


    public open fun onServiceConnected(service: BluetoothLeService) { }
}
You might also like...
Kotlin Asynchronous Bluetooth Low-Energy
Kotlin Asynchronous Bluetooth Low-Energy

Kable Kotlin Asynchronous Bluetooth Low Energy provides a simple Coroutines-powered API for interacting with Bluetooth Low Energy devices. Usage is de

An Android library that solves a lot of Android's Bluetooth Low Energy problems

A library that makes working with Bluetooth LE on Android a pleasure. Seriously.

Simple bluetooth flutter project

bluetooth_simple Simple bluetooth implementation. Getting Started This project is a starting point for a Flutter application. A few resources to get y

The purpose is to share the Internet capability of one device to the entire Bluetooth LAN.
The purpose is to share the Internet capability of one device to the entire Bluetooth LAN.

bluenet The purpose is to share the Internet capability of one device to the entire Bluetooth LAN. To make a prototype of a soft bus, or actually, I w

User-friendly Lightweight TPM Remote Attestation over Bluetooth

Ultrablue Ultrablue (User-friendly Lightweight TPM Remote Attestation over Bluetooth) is a solution to allow individual users to perform boot state at

RxBle: Use Android Bluetooth API in Rx way
RxBle: Use Android Bluetooth API in Rx way

RxBle: Use Android Bluetooth API in Rx way A lightweight encapsulation of Android Bluetooth API. Use Android Bluetooth API in Rx way. Support multiple

🍔 Meals is a small demo app based on modern Android technologies and MVVM architecture
🍔 Meals is a small demo app based on modern Android technologies and MVVM architecture

Meals 🍔 Meals is a small demo app based on modern Android technologies and MVVM architecture. built-in Kotlin, Coroutine, Flow, Retrofit, and Jetpack

A simple, lightweight library intended to take away some of the cruft and tediousness of using the Android BLE.

Blueteeth What Is Blueteeth? Blueteeth is a simple, lightweight library intended to take away some of the cruft and tediousness of using the Android B

RTranslator is the world's first open source real-time translation app
RTranslator is the world's first open source real-time translation app

RTranslator is the world's first open source real-time translation app. Connect to someone who has the app, connect Bluetooth headphones, put the phon

Owner
Chris Laplante
Chris Laplante
Android Bluetooth Helper Library, Bluetooth Device Finder

Bluetooth Helper Allows you to access the Bluetooth of your mobile device, manage turn-on - turn off, and discover bluetooth devices around you. Getti

Tolga Bolatcan 44 Jul 15, 2022
Kotlin Asynchronous Bluetooth Low Energy provides a simple Coroutines-powered API for interacting with Bluetooth Low Energy devices.

Kotlin Asynchronous Bluetooth Low Energy provides a simple Coroutines-powered API for interacting with Bluetooth Low Energy devices.

JUUL Labs 275 Sep 14, 2021
BLESSED Coroutines, a Bluetooth Low Energy (BLE) library for Android using Kotlin Coroutines

BLESSED for Android with Coroutines - BLE made easy BLESSED is a very compact Bluetooth Low Energy (BLE) library for Android 8 and higher, that makes

Martijn van Welie 82 Jan 1, 2023
This library allows for easy access to a Bluetooth LE device's AdRecord and RSSI value. It offers additional functionality for iBeacons.

Bluetooth LE Library for Android This library allows for easy access to a Bluetooth LE device's Advertisement Records. It also offers: A simple runnin

Alexandros Schillings 843 Dec 13, 2022
A Bluetooth kotlin multiplatform "Cross-Platform" library for iOS and Android

Blue-Falcon A Bluetooth "Cross Platform" Kotlin Multiplatform library for iOS, Android, MacOS, Raspberry Pi and Javascript. Bluetooth in general has t

Andrew Reed 220 Dec 28, 2022
BluePass extracts two factor authentication codes (2FA) from SMS and sends them to a paired device via Bluetooth RFCOMM.

BluePass extracts two factor authentication codes (2FA) from SMS and sends them to a paired device via Bluetooth RFCOMM.

Manuel Huber 15 Dec 4, 2022
Open-source weight and body metrics tracker, with support for Bluetooth scales

Open-source weight and body metrics tracker, with support for Bluetooth scales

OliE 1.3k Jan 4, 2023
Smooth communication via bluetooth with other android devices or microcontrollers such as Arduino.

Android Smooth Bluetooth Smooth communication via bluetooth with other android devices or microcontrollers such as Arduino. Getting Started Add Gradle

Mantas Palaima 191 Nov 28, 2022
A reactive, interface-driven central role Bluetooth LE library for Android

RxCentralBle RxCentralBle provides a simple reactive paradigm for connecting to and communicating with Bluetooth LE peripherals from the central role.

Uber Open Source 198 Nov 29, 2022
An Android Library for handling Bluetooth Low Energy on Android Easy

An Android Library for handling Bluetooth Low Energy on Android Easy

Leandro SQ 42 Jan 3, 2023