Android continuous background services with NativeScript

So I set out to make specialized voice assistant on Android. As I have strenuous relationship with Java to say the least, and I haven’t yet had time to play with Kotlin, NativeScript seemed like the obvious choice.

Now this is a work in progress, but I already learned a lot about Android and I want to share some of my findings with you.

First for this task I need to constantly listen for the wake word and react accordingly. Obvious choice to implement this on any platform would be some sort of background service or daemon.

When googling nativescript and background services an excellent tutorial and an example repo come up on the top ( I’m talking about this).

Alas, this is using IntentService which only runs on a schedule and exits once it’s tasks are complete. Creating a continuous background service though is pretty easy, there’s just a lack of examples on this topic (which this article aims to fix).

You can find comeplete working example repo here.

For this article I’ll assume that we’re working with typescript hello_world template: tns create ServiceExample --ts --appid tk.ozymandias.ServiceExample

It shouldn’t be difficult to adapt to other templates/technologies.

First create a new subfolder under app/, lets call it service. This is purely to keep your project structure clean and tidy. Now create a new file under app/service/continuous_service.android.ts with these contents

Now this is a very basic service, it just runs in the background and prints “PING” to the console every second.

At the top we export service name as a constant, will be using this in a few places later. Alas you need to specify the service name as a string literal in at least two more places.

First one is obvious here: the @JavaProxy annotation. Using a variable here will throw errors abouts existing extends and rather than the variable value it will be undefined.

Second will be in the manifest. More on that later.

onCreate is called once when the service is instanciated, onStartCommand is called everytime the service is started and onDestroy is called when the service exits.

How the service is started and restarted depends on what you return from onStartCommand. You may be tempted to return START_STICKY here, but that will cause crashes when your app is killed because the system will try to restart your service with null intent.

So far we have a functional service that starts with your app! But how do we keep it running when the app exits or is killed?

Let’s start by making a broadcast receiver.

Then let’s modify our service a bit to invoke the broadcast receiver on exit so it can restart our service.

You should also implement onTaskRemoved method in our service. It is called when user swipes away your app from the recents view. In this situation (and probably others) onDestroy isn't called by default. So let's invoke onDestroy by calling stopSelf!

Now we have a continuously running service! When the app exits or is killed, we invoke our broadcast receiver, which in turn restarts our service.

Unfortunately in newer versions of Android when system kills your app because of low memory or due to battery optimizations, onDestroy isn't guaranteed to be called.

Fortunately there’s an official way to work around that. What we need is to make our service a Foreground Service. The downside is that we must present a persistent notification, however starting with Oreo this notification can be hidden from the system settings without impacting our service.

We need to modify our service yet again, this time the onCreate method:

And this makes a continuous foreground service with a persistent notification that will keep running pretty much no matter what (it can still be forced stopped from the settings).

Now if you try the code so far it will crash. That’s because we haven’t declared anything in the AndroidManifest.xml! What we need to declare is the permissions we need (only on latest versions of Android), the service and the receiver.

Without further ado, here’s the manifest:

You may have noticed that the notification we get is generic “app is running” notification that goes to settings when tapped. We can do better!

You may need to declare const com: any; somewhere near the top of the file or typescript might throw a fit.

So what have we done here?

We created a pending intent pointing to the main activity of our app, so now when notification is tapped it will open your app. As for notification options, the important bits are setContentText and setSmallIcon. If both of these aren't present at the minimum, you'll still get a generic notification.

This has been my first article, please be gentle.

Eternal geek, coder, occasionally an unwilling sysadmin.