Dart

FlutterでTimerとDateTimeを使ったシンプルな時計アプリ

こんにちは、Tamappeです。

今回はFlutterでシンプルな時計アプリを作りました。今作っているアプリでタイマー機能を実装する必要がありますのでiOSでいうところのNSTimer(Timer)の部分の実装になります。

出来上がりのアプリを先に公開しておきます。

スクリーンショット gif動画

 

 

先に簡単な比較表を作っておきます。

Flutter Swift
タイマークラス Timer Timer (NSTimer)
日付クラス DateTime Date (NSDate)
フォーマットクラス DateFormat DateFormatter

時計のロジックについて

まずは時計のロジックについて説明します。

  1. 現在時刻を取得する
  2. DateFormatなどのフォーマッターと呼ばれる機能で現在時刻を文字列に変換させる
  3. Timerを使って1秒刻みの更新処理を行う(これを実務ではポーリング処理と呼んでます)
  4. 1秒ごとに1で取得した値に1秒を追加していく
  5. 3で処理した値を画面に表示させる
  6. 1から4をループさせる

だいたいはこの順番でロジックを作っていきます。

1. 現在時刻を取得する

それではFlutterで現在時刻を取得することに調整してみます。

iOSではDate()クラスが存在していました。それに対してFlutterではDateTimeクラスが存在しています。

DateTimeクラスを使ってこのように取得してみます。

// 現在時刻を取得する
var now = DateTime.now();

しかし、ここで格納したnowはDateTime型という文字列表示ではない型でこれを画面に表示させることができません。

2020-10-10 15:02:37.388077

そこでDateTime型からString型に変換する必要がでてきます。Flutterではこの変換処理のクラスをDateFormatが担ってくれています。

2. DateFormatなどのフォーマッターと呼ばれる機能で現在時刻を文字列に変換させる

iOSアプリ開発をするときもだいたい躓くポイントですが、DateからStringに変換やStringからDateへの変換作業は結構面倒くさい要素があります。

とりあえず、FlutterではDateFormatというクラスを使って変換してみます。

こいつを使うためにはFlutterプロジェクトにintlパッケージをインストールしてやる必要があるらしいのでpubspec.yamlからintlをインストールしましょう。

dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.0
  intl: ^0.15.8

これで右上の「Pub get」のボタンをタップすればFlutterがパッケージを更新してくれてインストールできます。

それでは前項で取得した現在時刻を試しに文字列に変換してみましょう。DateFormatの簡単な使い方はドキュメントを確認してみます。

/// 現在時刻を取得する
    var now = DateTime.now();
    /// 「時:分:秒」表記に文字列を変換するdateFormatを宣言する
    var dateFormat = DateFormat('HH:mm:ss');
    /// nowをdateFormatでstringに変換する
    var timeString = dateFormat.format(now);
    setState(() => _time = timeString);

これで現在時刻が「時:分:秒」という文字列に変換されます。

3. Timerを使って1秒刻みの更新処理を行う

現在時刻を取得して文字列として画面に表示できる状態までロジックが出来上がりました。そこでタイマーアプリらしくこの現在時刻を「ポーリング処理」という1秒置きにとある処理を実行するロジックを組み立てます。

Flutterでこういった毎秒単位で処理を更新させる上で便利なTimerクラスがあります。Dartライブラリにdart:asyncというライブラリが存在して、Timer クラスはその一部として存在しています。つまり、dart:asyncをインポートする必要があります。

ということで、Timerクラスを使うファイルでは’dart:async’をインポートする必要があります。main.dartのクラスファイルの一番上に次の1行を書いてみましょう。

import 'dart:async';

これでTimerクラスが使えるようになります。今回Timerクラスを使いますがTimerクラスを使う場合はinitStateメソッドで宣言しましょう。

@override
  void initState() {
    super.initState();
    /// Timer.periodic は繰り返し実行する時に使うメソッド
    Timer.periodic(Duration(seconds: 1), _onTimer);
  }

Timerには次のような処理を行えるメソッドがあります。

  1. x秒後に実行して終わりになるメソッド
  2. 毎x秒後に繰り返し実行するメソッド

今回は2番の繰り返し実行するTimer.periodicメソッドを使いました。

あとはonTimer()メソッドで1秒おきに更新したい処理を書きます。

@override
  void initState() {
    super.initState();
    /// Timer.periodic は繰り返し実行する時に使うメソッド
    Timer.periodic(Duration(seconds: 1), _onTimer);
  }

  void _onTimer(Timer timer) {
    /// 繰り返し実行する処理
  }

onTimerを使うのはそのクラス内だけでいいのでDartのプライベートメソッドとしてアンダーバー(_)を使って_onTimerとしています。これで1秒おきに処理を実行するポーリング処理が出来上がりました。

4. 1秒ごとに1で取得した値に1秒を追加していく

そして次に前項のポーリング処理を使って、1秒おきに取得した現在時刻を文字列に変換させる処理を書いていきます。目新しいことは特になく、_onTimerの関数内で処理を書きます。

String _time = '';

  @override
  void initState() {
    super.initState();
    /// Timer.periodic は繰り返し実行する時に使うメソッド
    Timer.periodic(Duration(seconds: 1), _onTimer);
  }

  void _onTimer(Timer timer) {
    /// 現在時刻を取得する
    var now = DateTime.now();
    /// 「時:分:秒」表記に文字列を変換するdateFormatを宣言する
    var dateFormat = DateFormat('HH:mm:ss');
    /// nowをdateFormatでstringに変換する
    var timeString = dateFormat.format(now);
    setState(() => {
      _time = timeString
    });
  }

こんな感じに書いてみました。onTimerの関数の最後にsetStateを使っているのが分かります。setStateを使っているため動的に変更されるStatefulWidgetを使う必要が出てきます。クラス名はClockTimerとでもしておきましょうか。

class ClockTimer extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _ClockTimerState();
  }
}

class _ClockTimerState extends State<ClockTimer> {
  String _time = '';

  @override
  void initState() {
    super.initState();
    /// Timer.periodic は繰り返し実行する時に使うメソッド
    Timer.periodic(Duration(seconds: 1), _onTimer);
  }

  void _onTimer(Timer timer) {
    /// 現在時刻を取得する
    var now = DateTime.now();
    /// 「時:分:秒」表記に文字列を変換するdateFormatを宣言する
    var dateFormat = DateFormat('HH:mm:ss');
    /// nowをdateFormatでstringに変換する
    var timeString = dateFormat.format(now);
    setState(() => {
      _time = timeString
    });
  }
}

これでいい感じに1秒おきに現在時刻を取得して文字列に変換されるロジックが出来上がりました。

5. 3で処理した値を画面に表示させる

もうほとんど仕上げみたいな感じになりますが、取得した現在時刻を文字列に変換しましたのでこの文字列を画面に表示させたいと思います。

画面に文字を表示させるためにTextウィジェットを使います。前項のClockTimerクラスを修正します。

class ClockTimer extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _ClockTimerState();
  }
}

class _ClockTimerState extends State<ClockTimer> {
  String _time = '';

  @override
  void initState() {
    super.initState();
    /// Timer.periodic は繰り返し実行する時に使うメソッド
    Timer.periodic(Duration(seconds: 1), _onTimer);
  }

  void _onTimer(Timer timer) {
    /// 現在時刻を取得する
    var now = DateTime.now();
    /// 「時:分:秒」表記に文字列を変換するdateFormatを宣言する
    var dateFormat = DateFormat('HH:mm:ss');
    /// nowをdateFormatでstringに変換する
    var timeString = dateFormat.format(now);
    setState(() => {
      _time = timeString
    });
  }

  @override
  Widget build(BuildContext context) {
    return Text(_time);
  }
}

注目すべきポイントはStateクラスの中のbuildメソッドを追加しています。

@override
  Widget build(BuildContext context) {
    return Text(_time);
  }

これでString型のtimeを画面に表示させました。念の為、main関数の部分は次のようになります。

void main() {
  runApp(MyApp());
}


class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Timer App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: ClockTimer(),
    );
  }
}

homeのところにClockTimerクラスを宣言したらOKです。これでアプリをビルドするとつぎのようになるのが分かります。

時計アプリらしくなってますね。

画面が真っ暗なので最後にScaffoldを使ってAppBarとbodyを追加してみましょう!

@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('カウントダウンタイマー')
      ),
      body: Text(_time, style: TextStyle(fontSize: 60)),
    );
  }

こちらをビルドするとナビゲーションバーが追加され、画面の背景色が真っ白に変更されているのがわかります。

これで時計アプリが完成しました。

でも今回はFlutterでも面倒くさい日付の取り扱いや日付から文字列変換、ポーリング処理の方法が学びましたので練習としては良かったと思います。

最後に簡単に今日学んだことをおさらいをします。

Flutter Swift
タイマークラス Timer Timer (NSTimer)
日付クラス DateTime Date (NSDate)
フォーマットクラス DateFormat DateFormatter

今日はこれまでにします。

それでは、バイバイ。

補足: 全体のソースコード

補足情報として今回のアプリの全体のソースコードを貼っておきます。

main.dart

import 'package:flutter/material.dart';
import 'dart:async';
import 'package:intl/intl.dart';

void main() {
  runApp(MyApp());
}


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Timer App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: ClockTimer(),
    );
  }
}

class ClockTimer extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _ClockTimerState();
  }
}

class _ClockTimerState extends State<ClockTimer> {
  /// タイマー文字列用
  String _time = '';

  @override
  void initState() {
    super.initState();
    /// Timer.periodic は繰り返し実行する時に使うメソッド
    Timer.periodic(Duration(seconds: 1), _onTimer);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('カウントダウンタイマー')
      ),
      body: Text(_time, style: TextStyle(fontSize: 60)),
    );
  }

  void _onTimer(Timer timer) {
    /// 現在時刻を取得する
    var now = DateTime.now();
    /// 「時:分:秒」表記に文字列を変換するdateFormatを宣言する
    var dateFormat = DateFormat('HH:mm:ss');
    /// nowをdateFormatでstringに変換する
    var timeString = dateFormat.format(now);
    setState(() => {
      _time = timeString
    });
  }
}
ABOUT ME
tamappe
都内で働くiOSアプリエンジニアのTamappeです。 当ブログではモバイルアプリの開発手法について紹介しています。メインはiOS、サブでFlutter, Android も対応できます。 執筆・講演のご相談は tamapppe@gmail.com までお問い合わせください。