Flutter: Background services
Tags: flutter, android, career, motivation
Published: 2019-06-17 22:23:58

So, it's time for me to actually try Flutter. I've watched the last dev conferences and I'm amazed of what Flutter is able to do and the future Flutter (Web, Windows, Linux). If you want to take a look to that conference, here the [youtube link](https://www.youtube.com/watch?v=IyFZznAk69U).

## Background

I never try something with a "hello world", I just go with the **"heavy"** stuff so I can see the **power**, **cons and pros** of the thing I'm trying. So, I grabbed an idea from my *"millionaire ideas"* list and started my journey.

## The app: Rest

I'll build a really simple app that for me is super useful. I have **tinnitus** produced by **Menier's Disease** (BTW if you also have that disease, please leave a comment), because of that some times I like to hear some background music on my phone when I go to sleep. **But** I don't want to leave the phone playing music all night long. 

## Researching

Every time I have an awesome idea I google it. I don't want to reinvent the wheel. This time I found an application that does the job. The problem is that it tends to hang or freeze really often. Sometimes the app renders all its buttons really bad. So I decided to continue by making a simple and functional app.

## Let's start

Before any code, I warn you: **I'm as new in Flutter as you may be**. Please do your own research. These series of post it's really awesome
{% link https://dev.to/abraham/a-month-of-flutter-47c4 %}  

Continue reading once you have the basic knowledge of Flutter and Dart.

## MVP

For my app, and its first release, I want a pretty **minimalist UI** and simple functionality. I decided to go with these specs:

- The timer duration goes from 5 to 60 minutes
- The user can add or subtract time by 5 minutes  
- It has an start/stop button
  
This is the result
  <img src="https://thepracticaldev.s3.amazonaws.com/i/pkvf4zdpzu9mmogfak0t.png" width="300">

## App Service: Flutter Method Call

The app has a countdown timer. I need a service to keep that timer running and eventually turn the music off when it finishes.
But, and here is were the other app failed, when you close the app and open again, you need to know what is the elapsed time of the timer running in the service, so you can update the state and start showing the current time from there.

I already knew that I had to code an Android service, so I did a little research and got what I needed. [Flutter Method Call Docs](https://flutter.dev/docs/development/platform-integration/platform-channels)

## Building the Android Service

First we need to declare the service in our android manifest (*android/app/src/main/AndroidManifest.xml*)

```xml
<service android:enabled="true" android:exported="true" android:name="dev.protium.rest.AppService" />
```

Now we need to write the service. The service needs to fulfill these specs:

### 1) It should be able to bind a connection with the main activity

For that lets use a **Binder**

```java
private final IBinder binder = new AppServiceBinder();
public class AppServiceBinder extends Binder {
        AppService getService() {
            return  AppService.this;
        }
    }

@Override
public IBinder onBind(Intent intent) {
     return binder;
}
```

### 2) It should start, stop and get current seconds on demand

This code is not relevant to this post but you can see the repo at the end of the article.

### 3) It should pause music if any is being played

If you feel curious about how you can pause the music on and android device, this is the trick

```java
AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
if (am.isMusicActive()) {
  long eventtime = SystemClock.uptimeMillis();
  KeyEvent downEvent = new KeyEvent(eventtime, eventtime, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PAUSE, 0);
  am.dispatchMediaKeyEvent(downEvent);
  KeyEvent upEvent = new KeyEvent(eventtime, eventtime, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PAUSE, 0);
  am.dispatchMediaKeyEvent(upEvent);
}
```

Magic, right?

![](https://media.giphy.com/media/12NUbkX6p4xOO4/giphy.gif)

## Connecting to the service

At this point you've may noticed that the communication goes like this

```
Android Service <-> Android Activity <-> Flutter App
```

### Connecting from the Activity

This is pretty straightforward

```java
private void connectToService() {
        if (!serviceConnected) {
            Intent service = new Intent(this, AppService.class);
            startService(service);
            bindService(service, connection, Context.BIND_AUTO_CREATE);
        } else {
            Log.i(TAG, "Service already connected");
            if (keepResult != null) {
                keepResult.success(null);
                keepResult = null;
            }
        }
    }

private ServiceConnection connection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className,
                                       IBinder service) {
            AppService.AppServiceBinder binder = (AppService.AppServiceBinder) service;
            appService = binder.getService();
            serviceConnected = true;
            Log.i(TAG, "Service connected");
            if (keepResult != null) {
                keepResult.success(null);
                keepResult = null;
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            serviceConnected = false;
            Log.i(TAG, "Service disconnected");
        }
    };
```

Did you notice that `keepResult` variable? More on that later.

### Connecting Activity to Flutter

```java
static final String CHANNEL = "dev.protium.rest/service";
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GeneratedPluginRegistrant.registerWith(this);

        new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(this::onMethodCall);
    }
```

I decided to implement the interface on the activity itself so you have to change the class declaration

```java
public class MainActivity extends FlutterActivity implements MethodChannel.MethodCallHandler {
```

And now the implementation

```java
@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
        try {
            if (call.method.equals("connect")) {
                connectToService();
                keepResult = result;
            } else if (serviceConnected) {
                if (call.method.equals("start")) {
                    appService.startTimer(call.argument("duration"));
                    result.success(null);
                } else if (call.method.equals("stop")) {
                    appService.stopTimer();
                    result.success(null);
                } else if (call.method.equals("getCurrentSeconds")) {
                    int sec = appService.getCurrentSeconds();
                    result.success(sec);
                }
            } else {
                result.error(null, "App not connected to service", null);
            }
        } catch (Exception e) {
            result.error(null, e.getMessage(), null);
        }
    }
```

We are keeping the `MethodChannel.Result` in the variable `keepResult`. Why? When we bind the service with the service connection, **we will get the `onServiceConnected` listener called.** There we know we are connected to the service. The flutter app will wait for this callback to success or fail. **Don't forget to call it.**

### Connecting Flutter to Activity

First we need to import packages in lib/main.dart

```dart
import 'dart:async'
import 'package:flutter/services.dart';
```

Inside our state widget we need this

```dart
static const MethodChannel platform = 
MethodChannel('dev.protium.rest/service');

Future<void> connectToService() async {
    try {
      await platform.invokeMethod<void>('connect');
      print('Connected to service');
    } on Exception catch (e) {
      print(e.toString());
    }
}

```

**Note:** the **channel** is the same as in the **MainActivity**, as well as the **method names**. Sounds familiar? If you've developed some **Apache Cordova plugin**, this should be really familiar to you. 

Now we are done with this **3-components-connection** thing. Once the flutter app is connected it can call the **start/stop** methods or get the current seconds from the service timer.

```dart
final int serviceCurrentSeconds = await getServiceCurrentSeconds();
setState(() {
    _currentSeconds = serviceCurrentSeconds;
});
```

Mission accomplished.

## What I've learned from Flutter

As I mentioned early, I don't like "hello wold" apps. This app is pretty simple but it has some tricks to be done. I think I'm in love with Flutter and Dart's **type inference**. This whole process took me exactly **2 days**, I'm not boasting. I sit down with my laptop yesterday. I finished the app at night. Today I've published it on the [Google Play Store](https://play.google.com/store/apps/details?id=dev.protium.rest) and wrote this article. (**BTW First time publishing an app**)

I've learned a lot of tricks, to list some:

- How to run widget tests without calling the service
- How to run test drive by setting the app's permissions first
- How important and useful is `flutter analyze`
  You can see all of that in the repo 
  {% github https://github.com/protium-dev/rest %} 

I'll post more articles about other tricks I've learned with this little project. I encourage you to grab some idea from your personal "millionaire ideas" list and make one with Flutter.

## Conclusion 

I'm happy that it took me **some minutes** to get a nice UI without much of a pain. And I'm really eager to play around with **Flutter Web and Desktop**. I see so much potential on this that I told the CTO of the company I work for that we must migrate some products to flutter. The benefits are high since we have multi-platform apps (TV, smartphones, desktop, anything with a webview).

Give a try to the **app** and feel free to contribute with more features. I plan to add more features but for now I'm okay to have the basic functionality. 
And If you want to compile and publish it for **iOS**, please let me know.