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
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 reporest
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
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
© 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.
Top comments (11)
A small suggestion for how you could avoid the tangle of keepResult:
Make onMethodCall an async function.
In onMethodCall's "connect" branch,
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. :)
Thanks! Really helpful
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
This is very useful, thanks!
already solved at here icetutor.com/question/how-do-i-run...
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?