How to create a mobile health app if you only know JavaScript

Alexander Baygeldin
8 min readJul 19, 2018

--

JavaScript is everywhere now: frontend, backend, IoT, cryptocurrency, machine learning, you name it. It’s not a perfect programming language, but I’m glad it’s not, because partly due to this it has such an amazing community that strives for perfection constantly improving the ecosystem. Curious and incredibly ingenious people is exactly what made JavaScript so popular in all corners of the IT world.

However, today I’m going to talk about a particular field where JavaScript is not so popular (yet) — mobile health. And there’s actually a reason to such low popularity. Let’s face it: JavaScript is not a go-to language for working with bare metal and developing mobile apps (although it’s changing with evolution of modern cross-platform frameworks such as React Native). The thing is, mobile health relies on both of those things. Obviously, mobile health should be mobile as the name implies, and the most reasonable way to achieve it is creating a mobile app (preferably native). But what does make an app a mobile health app? The answer is: connected medical sensors!

There are hundreds of different medical sensors on the market (and they not necessarily have to be wearable): spirometers, blood pressure monitors, heart rate monitors, ECGs, weight scales, sleep trackers, etc. And each one has its own pain points when it comes to actually supporting them in your app (both technical and organizational). Let alone the need to buy devices yourself, you have to manually parse the binary protocol to extract data and sign the non-disclosure agreement with the manufacturer. It’s true that there are standard protocols, but in real life implementations of those protocols are slightly or even completely non-standard. And don’t forget that there’s also Bluetooth stack that is different on each platform and tends to change as well. It’s hard to keep track of everything and simultaneously make progress in developing functionality specific to your project. No wonder that mobile health entry threshold is quite high!

But what if I told you that it’s possible to create a cross-platform app that supports a great variety of sensors without experiencing all that pain and using only JavaScript? ;)

Before we get started, though, I’d like to confess first. This article describes an approach that involves a proprietary library called MedM DeviceKit SDK. I work at MedM as a backend engineer (Ruby/Rails, JavaScript, Ansible, Docker, etc), but out of curiosity I decided to try myself in mobile app development in the spare time. So, I guess this article is a bit commercial, but the reason I wrote it is because I firmly believe in mobile health and what it can bring to people. I love being healthy and I want everyone around me to be this way. But in this age of urbanization living in a constant hurry and under constant stress we tend to forget about ourselves only to be reminded when some major disease (like cancer or stroke) come up on us as a wake-up call, but by that time it’s already too late. We’ve increased longevity of our lives, but what about quality? Each year billions of dollars are invested in treatment of health problems instead of preventing them! I believe that technology is our way out of this vicious circle, but we have to make it accessible to people so that they could embrace health monitoring in their daily life. This is our turn as developers to bring value and make this world a better and healthier place. And I want to contribute to this goal by connecting the most amazing community with one of the most exciting fields for those who want to make a difference.

Overview

At the core of this approach lies a combination of React Native with MedM DeviceKit SDK. It started as an experiment when I was trying to come up with a good subject for my bachelor thesis. I wanted it to be in the field of mobile health, so I chose to use MedM DeviceKit SDK right off the bat. But I also had long been interested in React Native, so I finally got around to giving it a try. And, as it turned out, the combination worked so well that I decided to extract a React Native adapter for DeviceKit to a separate module called react-native-device-kit, so that anyone could reuse it without writing a single line of Java or Objective-C code.

React Native is a framework for building native apps for iOS and Android that leverages JavaScript ecosystem (especially relevant to React parts of it). It has some really neat ideas behind it and, while still being a bit experimental, it’s maturing with every day. Flexibility and pragmatism make it a great choice for a new project. React Native’s team took on a courageous goal of reconciling three different ecosystems (Android, iOS and JavaScript) under one roof. Great things don’t come easily, so sometimes you may encounter some angry people in their GitHub issues threatening to switch back to native, but it’s just a matter of time when those issues will be resolved and we’ll all live happily ever after.

As for MedM DeviceKit SDK it’s not as popular as React Native and not as free. However, it’s definitely more crucial when it comes to developing a mobile health app and it just might become free for non-commercial use someday. MedM DeviceKit SDK provides a unified API for hundreds of medical sensors while solving all technical and organizational problems for you. It hides all nitty-gritty details behind a robust layer of abstraction and lets you spend more time on UX instead of going through the jungle of device support. And if you find yourself in a situation when the device you need is not supported you can always request it: since MedM team has a huge experience in supporting devices, they’ll do it much faster.

Overall, this approach is very viable. Now let’s move on to how you could actually make use of it!

How to use

First off you’ll have to go through official React Native’s Getting Started guide. Once your app is set up, install react-native-device-kit:

npm install react-native-device-kit --save

Then, if you’re not planning to use native Swift code in your app, install react-native-swift as well (it will create a placeholder Swift file so that your Xcode could compile Swift code in react-native-device-kit module):

npm install react-native-swift --save

After that you need to inject the original MedM DeviceKit SDK binaries into your app:

npx inject-device-kit --android /path/to/MedMDeviceKit.aar --ios /path/to/MedMDeviceKit.framework

And, finally, link it:

react-native link

You’re good to go!

Exploring the API

The most straightforward way is to just dive into documentation, but here are some basic examples.

In order to unlock functionality of DeviceKit you have to register it first:

import DeviceKit from 'react-native-device-kit';

const sdk = new DeviceKit();
sdk.register(/* License Key */).then(() => { /* Let's go! */ });

Then find some devices around you, connect to them and collect some data:

sdk.on('DEVICE_FOUND', (device) => {
sdk.addDevice(device).then(() => sdk.startCollection());
});
sdk.on('DATA', (data) => console.log(data));sdk.startScan();

Yep, it’s that simple.

Data format

If you’re worried about what data do you actually get from medical sensors, just check out the MedM backend API. DeviceKit returns data in the exact same format and that means XML. I understand that XML is not a popular choice among JavaScript developers, but there’s a great library called xmldom that lets you explore any XML document as it was a DOM tree:

import { DOMParser } from 'xmldom';const doc = new DOMParser().parseFromString(measurement);
const value = doc.getElementsByTagName('value')[0].textContent;

Background mode

There are two types of measurements: stream (i.e. continuous) and scalar (i.e. discrete). Scalar measurements include glucose, weight and other one-off measurements and there are no problems with them. However, stream measurements like heart rate monitoring and ECG usually require apps to work and collect data in the background. On iOS it’s just a matter of adding “bluetooth-central” to “UIBackgroundModes” in “Info.plist” file. On Android, however, you’ll have to create a foreground service. I like Kotlin, so here’s my version:

// android/app/src/main/java/com/world/hello/ForegroundService.ktpackage com.world.hello

import android.app.*
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.graphics.Color
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import android.support.annotation.RequiresApi
import android.support.v4.app.NotificationCompat

class ForegroundService : Service() {
private val wakeLock by lazy {
val pm = getSystemService(POWER_SERVICE) as PowerManager
pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WakeLock")
}

private val compatNotification by lazy {
val builder =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationCompat.Builder(this, createNotificationChannel())
} else {
NotificationCompat.Builder(this)
}
val content = resources.getString(R.string.notification_content)
builder
.setSmallIcon(R.drawable.splash_image)
.setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher))
.setContentTitle(resources.getString(R.string.app_name))
.setContentText(content).setTicker(content)
.setWhen(System.currentTimeMillis())
val startIntent = Intent(applicationContext, MainActivity::class.java)
val contentIntent = PendingIntent.getActivity(this, 1000, startIntent, 0)
builder.setContentIntent(contentIntent)
builder.build()
}

override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val NOTIFICATION_ID = 42
startForeground(NOTIFICATION_ID, compatNotification)
return START_STICKY
}

override fun onBind(intent: Intent): IBinder? {
return null
}

override fun onCreate() {
super.onCreate()
wakeLock.acquire()
}

override fun onDestroy() {
super.onDestroy()
wakeLock.release()
}

@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(): String {
val channelId = "hello_world_service"
val channelName = "ForegroundService"
val channel = NotificationChannel(channelId, channelName,
NotificationManager.IMPORTANCE_NONE)
channel.lightColor = Color.BLUE
channel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(channel)
return channelId
}
}

Then you’ll need to create a React Native module for it:

// android/app/src/main/java/com/world/hello/ForegroundModule.ktpackage com.world.hello

import android.content.Intent
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod

class ForegroundModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
override fun getName() = "Foreground"

@ReactMethod
fun startService(promise: Promise) {
try {
val intent = Intent(reactApplicationContext, ForegroundService::class.java)
reactApplicationContext.startService(intent)
} catch (e: Exception) {
promise.reject(e)
return
}
promise.resolve(null)
}

@ReactMethod
fun stopService(promise: Promise) {
try {
val intent = Intent(reactApplicationContext, ForegroundService::class.java)
reactApplicationContext.stopService(intent)
} catch (e: Exception) {
promise.reject(e)
return
}
promise.resolve(null)
}
}

And a package as well:

// android/app/src/main/java/com/world/hello/ForegroundPackage.javapackage com.world.hello;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ForegroundPackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}

@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();

modules.add(new ForegroundModule(reactContext));

return modules;
}
}

Don’t forget to add “new ForegroundPackage()” to MainApplication.java.

Then you should add “ <service android:name=”.ForegroundService” />” to “AndroidManifest.xml” and we’re almost ready to go:

import { NativeModules, Platform } from 'react-native';

// This module helps to persist application even
// when the screen is locked and the app is hidden.

export function startForegroundService() {
if (Platform.OS === 'ios') return;
NativeModules.Foreground.startService();
}

export function stopForegroundService() {
if (Platform.OS === 'ios') return;
NativeModules.Foreground.stopService();
}

Now whenever you call “startForegroundService” a notification will pop up that will prevent the app from being killed.

Example

And, finally, if you’re still not convinced that it’s possible to create a decent looking app with this, check out the Stress Detection Kit (this is the app from which react-native-device-kit was extracted). Feel free to reach out to MedM team if you have any questions and don’t stress it! ;)

--

--