⏱️

[Flutter] タイマーの実装方法

2022/10/06に公開3

前回はRiverpodを使用して、BottomNavigationBarの実装を行いました。
https://zenn.dev/hikaru24/articles/ccfbd2674a587d
そして今回はこのBottomNavigationBarを実装をしたプロジェクトにて、「タイマー」 の実装を行います。

完成図

開発version

Flutter version 3.3.2
Dart version 2.18.1
使用するプロバイダー Riverpod

全体コード

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:prefectures47/riverpod/cooperation_provider.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class PageWidget1 extends ConsumerStatefulWidget {
  const PageWidget1({Key? key}) : super(key: key);

  
  _PageWidget1State createState() => _PageWidget1State();
}

class _PageWidget1State extends ConsumerState<PageWidget1> {
  DateTime? _createTime;
  Timer? _timer;

  
  void initState() {
    super.initState(); // initState 関数の実装は、super.initState を呼び出して開始する必要があります。
    _createTime = DateTime.now(); //変数_createTimeにDateTime.nowを代入
    _startTimer();
  }

  // タイマーのスタート
  void _startTimer() {
    final createTime = _createTime!.add(const Duration(minutes: 1)); // タイマーの時間
    _timer = Timer.periodic(const Duration(milliseconds: 10), (timer) {
      final remain = createTime.difference(DateTime.now());
      if (remain > Duration.zero) {
        ref.read(cooperationTimerProvider.state).state =
            remain.inMilliseconds; //タイマーが動く
      }
    });
  }
  
  Widget build(BuildContext context) {
    final timer = ref.watch(cooperationTimerProvider);
    final displayTime =
        Duration(milliseconds: timer).toString().substring(2, 10); // ミリ秒設定

    // 作成した時間
    final createTime = DateFormat('yyyy/MM/dd/HH:mm').format(_createTime!);
    return Center(
        child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('残り時間'),
            SizedBox(width: 10),
            Text('$displayTime'),
          ],
        ),
        SizedBox(height: 20),
        Text('$createTime'),
      ],
    ));
  }
}

型、変数

DateTime? _createTime;   
Timer? _timer;

DateTime型に_createTime変数を、Timer型には_timer変数と宣言します。

関数


  void initState() {
    super.initState(); 
    _createTime = DateTime.now();
    _startTimer();
  }

initState の実装は、super.initState を呼び出して開始する必要があります。
なぜsuper.initStateが必要かどうか下記のリンクに書いてあります(正直私は理解できておりません)
https://stackoverflow.com/questions/52295949/what-is-initstate-and-super-initstate-in-flutter
_createTimeにDateTime.nowを代入します。
最後にタイマーをスタートさせる関数、_startTimerを用意します。

タイマーの関数

void _startTimer() {
    final createTime = _createTime!.add(const Duration(minutes: 1)); // タイマーの時間
    _timer = Timer.periodic(const Duration(milliseconds: 10), (timer) {
      final remain = createTime.difference(DateTime.now());
      if (remain > Duration.zero) {
        ref.read(cooperationTimerProvider.state).state =
            remain.inMilliseconds; //タイマーが動く
      }
    });
  }

_startTimer() と言う関数用意しました。
さてここから長くなりますが、自分なりに解説していこうと思います。

タイマーの時間

final createTime = _createTime!.add(const Duration(minutes: 1)); // タイマーの時間

_createTime!にadd.メソッドを追加します。これは後に続く(const Duration(minutes: 1));と関わがあります。
次にDurationクラスを用意します。
Durationクラスには時間単位を表示させるプロパティが存在します、詳しくは下記の記事を読んでみてください。(今回はminutesと言う「分」を取得できるプロパティを使用します)
https://api.flutter.dev/flutter/dart-core/Duration-class.html
https://flutterzero.com/duration/

ミリ秒を取得する(milliseconds)

_timer = Timer.periodic(const Duration(milliseconds: 10), (timer)

続いては変数で用意した_timer変数を使います。
_timer変数にTimer.periodic(const Duration(milliseconds: 10)を代入させます。このperiodic関数は毎x秒後に繰り返し実行するメソッドです。詳しくは下記の記事に書いてあるので読んでみてください。
https://tamappe.com/2020/10/10/flutter-watch-app/

DateTimeから差分を時間で取得する(differenceメソッド)

final remain = createTime.difference(DateTime.now());

remain変数に、DateTimeから差分を代入します。
DateTimeから差分を時間で取得するには、differenceメソッドを使用します。下記公式記事を参考にしてください。
https://api.flutter.dev/flutter/dart-core/DateTime/difference.html
differenceメソッドの引数にはDateTime.now()を入れます。

タイマーが動く箇所

 if (remain > Duration.zero) {
        ref.read(cooperationTimerProvider.state).state =
            remain.inMilliseconds; //タイマーが動く
      }

if文を使用し「remain変数がDuration.zeroより大きいならタイマーが動く」と記述します。
必然的にゼロより大きい数になるのでRiverpodの中身が動きます。

final cooperationTimerProvider = StateProvider<int>((ref) => 0);

View側の全体コード


  Widget build(BuildContext context) {
    final timer = ref.watch(cooperationTimerProvider);
    final displayTime =
        Duration(milliseconds: timer).toString().substring(2, 10); // ミリ秒設定

    // 作成した時間
    final createTime = DateFormat('yyyy/MM/dd/HH:mm').format(_createTime!);
    return Center(
        child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('残り時間'),
            SizedBox(width: 10),
            Text('$displayTime'),
          ],
        ),
        SizedBox(height: 20),
        Text('$createTime'),
      ],
    ));
  }
}

残り時間を取得する

final timer = ref.watch(cooperationTimerProvider);
final displayTime =
        Duration(milliseconds: timer).toString().substring(2, 10); // ミリ秒設定

プロバイダーから時間を取得し、取得したtimerをDurationの引数に当てます。
substring(2, 10)にはミリ秒設定を行います。

作成した時間設定

final createTime = DateFormat('yyyy/MM/dd/HH:mm').format(_createTime!);

残り時間と作成した時間を配置

return Center(
        child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('残り時間'),
            SizedBox(width: 10),
            Text('$displayTime'),
          ],
        ),
        SizedBox(height: 20),
        Text('$createTime'),
      ],
    ));

さて最後です。
変数を配置したら、タイマーの完成です。お疲れ様でした!

終わりに

いかがでしょうか?
本業にてクーポンを使用する挙動の処理に携わったのて、「クーポンを使用したら、タイマーのカウントが始まる」
と言う少しマイナーなところを記事にしてみました(笑)

Discussion

JboyJboy

残り時間と作成した時間を配置しましょう!の下の

eturn Center(
        child: Column(

が、r抜けてた!
return になるはず!
僕もよくタイポしますけどね😅

JboyJboy

私が、意見して良いものかと思いましたが(^^!)
チケットのロジック面白かったです!

ログインするとコメントできます