今回はFlutterで簡単なTimerアプリを作成してみたくて、その経過、結果、勉強したものを記録します。
アプリの機能要望
アプリの設計要望
Provider
を導入するDIと状態管理のパッケージです。
優れる状態管理の機能を持っているなので、最近の人気のパッケージであり、 Google I/O 2019でも推奨されています。
BLoCと比べるとより簡易な方法で状態管理ができますので、学習コストは低いです。
参考
導入
pubspec.yamlファイルにパッケージを追加する。
dependencies:
flutter:
sdk: flutter
//...
provider: 4.1.2
provider
には、UIとビジネスロジックの分離ができるメリットがあります。
今回のTimerアプリの核心機能となるカウントダウンと時間表示について、
provider
で分離して管理します。
手順としては、ビジネスロジックのクラスを作成して、Widgetに適用する。
class BasicTimer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("My Timer"),
),
body: Container(
child: ChangeNotifierProvider(
create: (context) => TimerStore(),
child: Center(
child: Clock(),
),
),
),
);
}
}
状態管理クラスとWidgetと連携するため、ChangeNotifierProvider
を使います。
import 'dart:async';
import 'package:flutter/material.dart';
class TimerStore with ChangeNotifier {
static const COUNTER_VALUE = 60; // TODO: ユーザーが設定できるように
var count = 0;
static const sec = const Duration(seconds: 1);
static const ms = const Duration(milliseconds: 1);
Timer _timer;
startTimer() {
if (_timer != null) {
_timer.cancel();
}
count = COUNTER_VALUE;
_timer = Timer.periodic(sec, (timer) {
updateCounter();
});
}
void updateCounter() {
if (count > 0)
count--;
else
_timer.cancel();
notifyListeners();
}
@override
void dispose() {
super.dispose();
_timer.cancel();
}
}
dart:async
のTimer.periodic()
関数を利用して、定時的にカウントダウンの処理を行う。
こちらの要点としては、
count
値で保持し、UI表示の時にも使われています。class Clock extends StatelessWidget {
@override
Widget build(BuildContext context) {
final countText = context.select((TimerStore store) => getCountText(store));
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("$countText", // <-- **カウンター時間の表示**
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 36,
)),
SizedBox(height: 50),
Container(
padding: const EdgeInsets.all(30.0),
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
colors: <Color>[
Colors.blue[300],
Colors.blue[500],
Colors.blue[700]
]
)
),
child: FlatButton(
onPressed: () {
context.read<TimerStore>().startTimer();
},
padding: EdgeInsets.all(20),
shape: CircleBorder(),
child: Text("Start", style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 16,
color: Colors.white
),),
),
)
],
);
}
String getCountText(store) {
var hour = (store.count / (60*60)).floor();
var mod = store.count % (60*60);
var min = (mod / 60).floor();
var sec = mod % 60;
return "${twoDigits(hour)}:${twoDigits(min)}:${twoDigits(sec)}";
}
String twoDigits(int n) {
if (n >= 10) {
return "$n";
} else {
return "0$n";
}
}
}
TimeStore
状態クラスにアクセスする時に、 context.select
(変更を監視します)とcontext.read
(変更を監視しません)を使います。
カウンター時間の表示をカウンター値によって変動するため、監視する必要がありますので、context.select
でTimeStore
状態クラスのcount
をアクセスし、表示時間の文字を作成する。
ボタンを押してカウントダウン開始する際に、TimeStore
状態クラスのstartTimer
関数を実行します。
監視する必要はありませんので、context.read
を使います。
Providerの使用は割とシンプルです。 少ないコードで状態管理ができて素敵な〜と思いました。 また、簡単なカウントダウン機能ができましたが、まだたくさんの機能を実装していません。 もう少し頑張って、機能を充実します。