0
Help us understand the problem. What are the problem?

posted at

updated at

FlutterにてAndroid SDKの"Service"で1秒ごとにUIに反映させる方法について考える

1.はじめに

僕はFlutterを触るようになって1か月のものです。参考にならない記事を上げるなと思われるかもしれませんが、僕個人の記録として書いている部分の方が大きいのご了承ください。「こうしたら楽に実装できるよ~」とか、「こうした方がいい!」とか教えていただけると幸いです。

今回のゴール

AndroidのServiceと言えばバックグラウンドで動作し、ファイルのダウンロードやTimerを動かし定期的にUIに反映させたりなどそうゆう用途で用いられることが多いと思います。(これも素人目なので適切な使い方かどうかは確証がないです)それを踏まえて、今回のゴールは「Timerで1000ms毎にUIに表示している整数をカウントアップさせる」にします。
Animation2.gif

2.方法

とりあえず方法を検討してみます。Flutter初心者なので「そもそもFlutterはServiceもサポートするのではないか?」「誰かがパッケージを公開しているのではないか?」と色々探してみた結果以下のパッケージがありました。

日本語で書かれているブログではsmtCreateさんが紹介していました。

しかし、色々見ていくとsmtCreateさんのソースコードをサポートしているのはVersion 0.2.6までのようで情報も少なく今回は使わないことにしました。
結果的に、MethodChannelをつかいAndroid SDKのServiceはおとなしくJavaで書くことに...

3.実装

調べてみるとFlutterのMethodChannelはメソッド名とデータの2つの引数でメッセージパッシングするそうです。詳しくは"5.参考"にあるkurun_panさんの記事にて解説されています。
んで、このMethodChannelはどうやらActivityを継承したFlutterActivityを継承しているクラスで使えるそうで、Android SDKのServiceを継承したクラスからは使えないみたいです。となると、
Flutter APP ⇔ FlutterActivity ⇔ Service
  (MethodChannel)  (Handler)
といったやり取りを行うことになると思います。
ソースコードは以下

dart main.dart
class _MyHomePageState extends State<MyHomePage> {

  int _counter = 0;
  String _serviceStatus = "";

  static const MethodChannel _channel = MethodChannel('com.example.methodchannel/interop');
  static const String startService = "startService";

  String text = "";

  Future<void> _startService() async{
    await _channel.invokeMethod(startService);
  }

  static Future<Null> _onServiceStart() async{
    await _channel.invokeMethod('_onServiceStart');
  }

  static Future<Null> _onTick() async{
    await _channel.invokeMethod('onTick');
  }

  Future<dynamic> _platformCallHandler(MethodCall call) async{
    switch(call.method){
      case 'onStart':
        setState((){
          _serviceStatus = call.method;
        });
        return Future.value('ok');
      case 'onTick':
        setState((){
          _counter ++;
        });
        return Future.value('ok');
      default:
        print('unknown method ${call.method}');
        throw MissingPluginException();
        break;
    }
  }

  @override
  initState(){
    super.initState();
    _channel.setMethodCallHandler(_platformCallHandler);

    _onServiceStart();
    _onTick();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Response:$_serviceStatus',
            ),
            Text(
              'Timer:$_counter',
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _startService,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}
java MainActivity.java
public class MainActivity extends FlutterActivity {
    private final String CHANNEL = "com.example.methodchannel/interop";
    private final String START_SERVICE = "startService";
    MethodChannel channel;

    private MyBroadcastReceiver mReceiver;
    private IntentFilter intentFilter;

    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        super.configureFlutterEngine(flutterEngine);
        GeneratedPluginRegistrant.registerWith(flutterEngine);
        channel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(),CHANNEL);
        channel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
                if(call.method.equals(START_SERVICE)){
                    mReceiver = new MyBroadcastReceiver();
                    intentFilter = new IntentFilter();
                    intentFilter.addAction("UPDATE_ACTION");
                    registerReceiver(mReceiver,intentFilter);

                    mReceiver.registerHandler(updateHandler);

                    startService(new Intent(MainActivity.this,TimeService.class));
                }
            }
        });
    }


    @SuppressLint("HandlerLeak")
    private Handler updateHandler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            Bundle bundle = msg.getData();
            String status = bundle.getString("status");
            channel.invokeMethod(status, null, new MethodChannel.Result() {
                @Override
                public void success(@Nullable Object result) {
                    Log.d("MAIN_ACTIVITY","success");
                }

                @Override
                public void error(@NonNull String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
                    Log.d("MAIN_ACTIVITY",errorMessage);
                }

                @Override
                public void notImplemented() {
                    Log.d("MAIN_ACTIVITY","notImplemented(status:"+status+")");
                }
            });
        }
    };

}
java MyBroadcastReceiver.java
public class MainActivity extends FlutterActivity {
    private final String CHANNEL = "com.example.methodchannel/interop";
    private final String START_SERVICE = "startService";
    MethodChannel channel;

    private MyBroadcastReceiver mReceiver;
    private IntentFilter intentFilter;

    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        super.configureFlutterEngine(flutterEngine);
        GeneratedPluginRegistrant.registerWith(flutterEngine);
        channel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(),CHANNEL);
        channel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
                if(call.method.equals(START_SERVICE)){
                    mReceiver = new MyBroadcastReceiver();
                    intentFilter = new IntentFilter();
                    intentFilter.addAction("UPDATE_ACTION");
                    registerReceiver(mReceiver,intentFilter);

                    mReceiver.registerHandler(updateHandler);

                    startService(new Intent(MainActivity.this,TimeService.class));
                }
            }
        });
    }


    @SuppressLint("HandlerLeak")
    private Handler updateHandler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            Bundle bundle = msg.getData();
            String status = bundle.getString("status");
            channel.invokeMethod(status, null, new MethodChannel.Result() {
                @Override
                public void success(@Nullable Object result) {
                    Log.d("MAIN_ACTIVITY","success");
                }

                @Override
                public void error(@NonNull String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
                    Log.d("MAIN_ACTIVITY",errorMessage);
                }

                @Override
                public void notImplemented() {
                    Log.d("MAIN_ACTIVITY","notImplemented(status:"+status+")");
                }
            });
        }
    };

}
java TimeService.java
public class TimeService extends Service {

    private final String CHANNEL = "com.example.methodchannel/interop";

    final int INTERVAL_PERIOD = 1000;
    Timer timer = new Timer();
    MethodChannel channel;

    public TimeService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        sendStatus("onStart");
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                sendStatus("onTick");
            }
        },0,INTERVAL_PERIOD);
        return super.onStartCommand(intent, flags, startId);
    }

    void sendStatus(String message){
        Intent broadcastIntent = new Intent();
        broadcastIntent.putExtra("status",message);
        broadcastIntent.setAction("UPDATE_ACTION");
        getBaseContext().sendBroadcast(broadcastIntent);
    }
}

4.最後に

Flutterはやっぱりよくわかりません。もっと楽な実装。確実な方法ありましたら教えてください。
今回のソースコードのすべてはGitHubに上げてますので以下を参照ください。

5.参考

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
0
Help us understand the problem. What are the problem?