loading...
Cover image for Flutter: Background services

Flutter: Background services

protium profile image Brian Mayo Updated on ・6 min read

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.

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

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

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

Building the Android Service

First we need to declare the service in our android manifest (android/app/src/main/AndroidManifest.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

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

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?

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

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

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

public class MainActivity extends FlutterActivity implements MethodChannel.MethodCallHandler {

And now the implementation

@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

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

Inside our state widget we need this

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.

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 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 logo protium-dev / rest

    Turn music off after a period off time and rest

    rest

    NOTICE: This repo is not being maintained.

    A simple app that will pause music after a period of time. This app was developed with Flutter

    Install from Google Play

    Get it on Google Play

    Development

    Dependencies:

    • Android SDK
    • Android Studio
    • Flutter SDK

    Check your dependencies with flutter doctor

    Testing

    flutter test
    flutter drive --target=test_driver/home_page.dart 
    

    Running

    flutter run

    Screenshots

    TODOs

    • Add sticky notification
    • Show toast message when music is paused

    Donate

    If you liked this app feel free to buy me a coffee

    Buy Me A Coffee

    © Brian Mayo - 2019

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.

Posted on by:

protium profile

Brian Mayo

@protium

autodidact dev, open source enthusiast and photographer. I always try to improve/hack everything.

Discussion

markdown guide
 

A small suggestion for how you could avoid the tangle of keepResult:

  1. Make onMethodCall an async function.

    public void onMethodCall(MethodCall call, MethodChannel.Result result) async {
    
  2. In onMethodCall's "connect" branch,

    await connectToService();
    result.success(null);
    
  3. Make connectToService return a Future that gets completed by the onServiceConnected callback. (Likely using a Completer.)

Not going into too much more detail because I see in the repository that all the code is completely different already, this comment is more to try to provide other passers-by with some tools to avoid the same structural issues. :)

 

Oh, my mistake, I was missing that that entire section isn't Flutter/Dart code at all! Found it further into the repository. Turns out nearly all of the post's code is Java!

The same premise still applies, but now it all makes more sense to me as well. :)

 
 

greate example. very helpful. How about implementation in iOS ?

 

Thanks! Well I got a Mac few weeks ago, so now I can finally give it a try. Will update soon :)

 

please implementation in IOS for background service...

 

Thanks Brian Mayo,
It's very nice app.
Could you please make the app run as services?
The music is still off after 60 minutes even though Rest app closed

 

Hey, thanks!
The app is using a service to pause the music. Maybe you found a bug, please feel free to submit an issue with some details on how to replicate the issue
github.com/protium-dev/rest

 
 
 

Where does each snippet of code go into the project?

What goes into the Service project, what goes into the app? Or is it just one project?