Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Flutterで記念日を調べるアプリを作った際の10の小技

はじめに

概要

  • アイデアマキネッタが楽しかったので、2つ目のアプリをリリースしました。
  • 引き続き、完全独学できれいな実装でないかもしれませんが、誰かの役に立てばと思い、使ったWidgetやtipsなどを公開したいと思います

    • アプリ「この日何の日??」で使っているものを10個紹介します
      • 前作の「アイデアマキネッタ」についてはこちらです
      • 前作に含まれているものは除いて紹介します
  • やっぱりアプリの難易度は低いと思います。

    • 画面数は10弱程度です
    • DBを利用しています
    • バックエンドサーバはありませんが、httpsで外部との通信があります。
    • 技術レベルとしては、公式ドキュメントの例+α程度のレベルだと思います
  • 是非、DLしてご意見/ご感想を頂ければと思います

    • 本アプリはアイデア創出を目的にしていますが、使い方によっては、優先度に応じた抽選ツールとしても利用できます

対象読者

  • これからFlutterでアプリを作ってみたいと思っている方
  • Flutterの実アプリでどんな技術が使われているか知りたい方

関連記事

ダウンロード先

スタート(前提条件)

  • Flutterの概念やDartの基本構文を知っていること

ゴール(達成できること)

  • この日何の日??で使われている技術が理解できる

スコープ外(ふれないもの)

  • リリースの仕方や審査を通るためのノウハウ

開発環境

  • OSのバージョン
macOS Catalina バージョン10.15.7
  • Flutterのバージョン
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 1.22.2, on Mac OS X 10.15.7 19H2, locale ja-JP)

[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.1)
[✓] Xcode - develop for iOS and macOS (Xcode 11.7)
[✓] Android Studio (version 4.0)
[✓] IntelliJ IDEA Community Edition (version 2017.1.2)
[✓] VS Code (version 1.50.1)
[!] Connected device
    ! No devices available
  • アプリバージョン
今日は何の日??:1.0.1

本編

全体像

  • まずどんなアプリかを簡単に紹介し、その後使われている技術を紹介します。
  • 実際にアプリをダウンロードして、動作と下記の解説を対比させながらご覧頂くと分かりやすいかと思います。

アプリ紹介

pics1.png

  • トップ画面では、本日の日付と今年の残り日数が表示されます
  • 探したい日付を選び、ボタンを押すとその日について調べられます
  • データは自動で取得されます。データは西暦の降順で表示されます
  • 誕生日を入れると、リスト画面で何才のときか表示されます

pics2.png

  • 右側のアイコンで用語をgoogle/wikipediaで確認できます

pics3.png

  • 画面右上のメニューから、項目の表示/非表示を変更できます
  • 画面上部のアイコンから、30日前/1日前/任意選択/1日後/30日後を選べます

pics4.png

  • ロングタップで内容をコピーできます(Androidのみ)

使われている機能

1. タップでブラウザを開く

  • タップで目的のURLをブラウザで開きます
  • urlLauncherのパッケージを使います
    • このパッケージはメールや電話など他にもいろいろ使えるようです

イメージ

pic1.png

ソース

pubspec.yaml
url_launcher: ^5.7.8
import 'package:url_launcher/url_launcher.dart';
/* 中略 */
IconButton(
    icon: /*リンクを貼るアイコン*/,
    onPressed: () async {
        // cdata.linkには検索対象の文字列が入っています
        if (cdata.link.isNotEmpty) {
            String url = Uri.encodeFull(
                "https://www.google.co.jp/search?q=${cdata.link}");
            if (await canLaunch(url)) {
                await launch(url);
            }
        }
    }),

2. 多様なアイコンを使う

  • AwesomeFontのパッケージを使います

イメージ

pic2.png

ソース

pubspec.yaml
font_awesome_flutter: "^8.8.1"
Icon(FontAwesomeIcons.gift, color: Colors.teal) // プレゼントマーク
Icon(FontAwesomeIcons.birthdayCake, color: Colors.pinkAccent) // 誕生日ケーキ
Icon(FontAwesomeIcons.bible, color: Colors.brown) // 聖書
Icon(FontAwesomeIcons.wikipediaW, color: Colors.lime) // Wikipedia
Icon(FontAwesomeIcons.globeAsia, color: Colors.cyan) // 地球儀

3. データを保存する

  • SharedPreferenceのパッケージを使います

イメージ

pic3.png

ソース

pubspec.yaml
shared_preferences: ^0.5.12+4
import 'package:shared_preferences/shared_preferences.dart';
class SaveData {
  // 保存
  static saveBarth(int year, int month, int day) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setInt('BARTH_YEAR', year);
    prefs.setInt('BARTH_MONTH', month);
    prefs.setInt('BARTH_DAY', day);
  }
  // 読み出し
  static loadBarth() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    int year = prefs.getInt('BARTH_YEAR') ?? 2000;
    int month = prefs.getInt('BARTH_MONTH') ?? 1;
    int day = prefs.getInt('BARTH_DAY') ?? 1;
    return [year, month, day];
  }
  • 保存できる型がある程度決まっているので注意
  • 暗号化などはされず、直接見ることもできるとのことなので、セキュリティ的な意味でも注意

4. DBデータを使う

  • SqlLiteのパッケージを使います

イメージ

pic4.png

ソース

pubspec.yaml
sqflite:
path:
  • 公式サイトの例でバージョン指定がないため、あえて空欄にしていますが、使うバージョンに合わせて指定をするべきかとおもいます。
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

class DbProvider {
  Database db;

  //DBの初期化
  Future<void> init() async {
    final path = join(await getDatabasesPath(), "database.db");
    db = await openDatabase(
      path,
      version: 1,
      onCreate: (Database newDb, int version) {
        // DBがpathに存在しなかった場合に onCreateメソッドが呼ばれます。
        newDb.execute(/* SQL分 */);
        },
    );
  }
  //DBの削除
  deleteDB() async {
    final path = join(await getDatabasesPath(), "idea.db");
    await deleteDatabase(path);
  }

    // DBアクセス
  Future<List<Contents>> selectContents(categoryId) async {
      // contentsテーブルをcategoryIdで取得する例
      // メソッドも準備されているが、複雑なものを書きたい時は生のSQL指定も可能
    List<Map<String, dynamic>> maps;
      maps = await db.query('contents',
          orderBy: "id desc", where: 'categoryId = ?', whereArgs: [categoryId]);
    }
}

5. プログレスバーで進捗率を表す

  • Slackを使って、プログレスと文字を重ねて表現します

イメージ

pic5.png

ソース

Stack(children: [
    // プログレスバー
    SizedBox(
        child: LinearProgressIndicator(
        minHeight: 30.0,
        backgroundColor: Colors.blue,
        value: remainingDays,
    )),
    // 進捗率の文字
    Align(
        child: Text(
            "残り${((1 - remainingDays) * 100).toStringAsFixed(1)}%",
            style: TextStyle(
                fontSize: 20, color: Colors.white)),
        alignment: Alignment.center),
]),

6. サイドバーでスイッチリストを作る

  • endDrawerにSwitchListTileを持ったListViewを設定します

イメージ

picx.png

ソース

endDrawer: Drawer(
  child: ListView(children: <Widget>[
    /* コンテナでヘッダ部分を作る */
    SwitchListTile(
    title: Text('記念日'),
    value: ifl.anivFlag,
    onChanged: (bool value) {
        setState(() {
        ifl.anivFlag = value;
        });
    },
    secondary: Icon(FontAwesomeIcons.gift, color: Colors.teal),
    ),
    /* SwitichListTileが続く */
    /* Dividerなど他のものが含まれてもOK */
    ]),
),

7. 日付選択をカレンダーではなく、ドラムで行う

  • datetime_pickerのライブラリを使います
    • 本来は日付と時間を同時に設定するものですが、通常のdate_pickerはカレンダー形式でドラムのUIを使いたいため、あえて日付だけを使っています

イメージ

picy.png

ソース

pubspec.yaml
flutter_datetime_picker: ^1.4.0
import 'package:flutter_datetime_picker/flutter_datetime_picker.dart';

IconButton(
  icon: Icon(FontAwesomeIcons.calendarAlt,
  color: Colors.white, size: 30.0),
  onPressed: () {
    DatePicker.showDatePicker(context,
      showTitleActions: true,
      minTime: DateTime(1900, 1, 1),
      maxTime: DateTime(2049, 12, 31), 
      onConfirm: (date) {
        setState(() {
            targetday = date;
        });
        initData(); //新しい日付でデータを再取得する
      },
      currentTime: targetday, locale: LocaleType.jp
    );
  },
)

8. NowLoading画面を表示する

  • データを読み込むまで、読み込み中を表すWidgetを設定し、データの準備が整ったらデータ本体(記念日のリスト)を描画します

イメージ

pic8.png

ソース

// 定義
static Widget getNowLoading(context) {
  return Column(children: [
    // CircularProgressIndicatorを使って読み出し中を表す
    Center(
      child: Container(
        padding: EdgeInsets.all(10.0),
        child: SizedBox(
          width: 100,
          height: 100,
          child: CircularProgressIndicator(
            strokeWidth: 10.0,
          )
        )
      )
    ),
    /* 以後文字などを追加 */
  ]);
}

//使う部分
initData() async {
  // 画面ロード時にまずNowLoadingをセット
  setState(() {
    dataViewList = Util.getNowLoading(context);
  });

  /* データを取得する */

  setState(() {
    dataViewList = /* 取得したデータを入れる(=NowLoadingを上書き) */
  });
}


9.ロングタップで文字をコピーする

  • ロングタップでクリップボードに文字列をコピーし、flushbarを1秒だけ表示します
  • flushbarはパッケージで実現します
    • SnackBarだと画面下部になり、広告と重なってしまうため、利用を見送りました

イメージ

pic9.png

ソース

pubspec.yaml
flushbar: ^1.10.4 
// listViewのonLongPressイベント
onLongPress: () async {
  // ライブラリがAndroidしか動かない(iOSだとフリーズする)ので条件分岐で避ける
  if (Platform.isAndroid) { 
    // クリップボードに入れる
    final data = ClipboardData(text: cdata.content);
    await Clipboard.setData(data);
    // フラッシューバーを表示する
    Flushbar(
      message: "コピーしたよ",
      flushbarPosition: FlushbarPosition.TOP,
      duration: Duration(seconds: 1),
    )..show(context);
}

10.ColumnとListを同時に使う

  • Listは無限の高さを持ちますが、Listを子に持つColumnはサイズを知らないとエラーとなるため、Expandedで回避しています

イメージ

pic10.png

何もしない場合のエラー

The following assertion was thrown during performResize():
Vertical viewport was given unbounded height.
// 以下省略

ソース

dataViewList = Column(children: [
/* Widgetのリスト */
]),
Expanded(child: createViewList()),

所感

  • ちょっと調べると、色々できてFlutterの生産性や機能追加はかなり良さそうです。

補足

備考

  • ソースコードは可能な限り主要な要素を残すように記載しておりますが、単純コピーでは動作しないものが多いと思いますので、ご自身のコードに合わせてご活用ください
  • 自分も勉強とTry&Errorをしながらなので、もっと良い実装方法があるかもしれません。
    • 一例として見ていただけると幸いです

参考文献

追記

  • 2020/11/12 初稿
kazutxt
パソコン大好きの30代。 最近はflutterにはまりアプリをリリースしています
https://smart-simple-tools.kazutxt.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away