0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Flutter/Dart】情報管理アプリを作ろう

Last updated at Posted at 2022-08-15

1.はじめに

これまでに7つのAndroidStudio&Flutter&Dart記事を書いている。以下は内容、

今回はflutterアプリの入門編として情報管理アプリを作成する。
-ボタンを押すと登録画面が開く機能
-登録画面で記入したタスクがトップ画面でリスト表示される機能

2.情報管理アプリを作ろう

2-1.完成イメージ

アプリの完成イメージと操作方法を説明する。
①プロジェクトを実行するとタスクの描画画面が開く。タスクを追加するためにプラスボタンを押す。
task_app0.png

②テキストフィールドにタスク名称を書き、リスト追加ボタンを押す。
task_app1.png

③3回追加した後の画面が以下。タスクが追加した順番に表示されている。
task_app2.png

2-2.コードの説明

①まず、task_appという名前の新しいプロジェクトを作成する。
task_app3.png

②libフォルダにあるmain.dartファイルを開く。以下のコードを記述。これから作るアプリの全コードを含む。

main.dart
import 'package:flutter/material.dart';

void main() {
  // 最初に表示するWidget
  runApp(MyTodoApp());
}

class MyTodoApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // 右上に表示される"debug"ラベルを消す
      debugShowCheckedModeBanner: false,
      // アプリ名
      title: 'My Todo App',
      theme: ThemeData(
        // テーマカラー
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      // リスト一覧画面を表示
      home: TodoListPage(),
    );
  }
}

// リスト一覧画面用Widget
class TodoListPage extends StatefulWidget {
  @override
  _TodoListPageState createState() => _TodoListPageState();
}

class _TodoListPageState extends State<TodoListPage> {
  // Todoリストのデータ
  List<String> todoList = [];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // AppBarを表示し、タイトルも設定
      appBar: AppBar(
        title: Text('リスト一覧'),
      ),
      // データを元にListViewを作成
      body: ListView.builder(
        itemCount: todoList.length,
        itemBuilder: (context, index) {
          return Card(
            child: ListTile(
              title: Text(todoList[index]),
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          // "push"で新規画面に遷移
          // リスト追加画面から渡される値を受け取る
          final newListText = await Navigator.of(context).push(
            MaterialPageRoute(builder: (context) {
              // 遷移先の画面としてリスト追加画面を指定
              return TodoAddPage();
            }),
          );
          if (newListText != null) {
            // キャンセルした場合は newListText が null となるので注意
            setState(() {
              // リスト追加
              todoList.add(newListText);
            });
          }
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

class TodoAddPage extends StatefulWidget {
  @override
  _TodoAddPageState createState() => _TodoAddPageState();
}

class _TodoAddPageState extends State<TodoAddPage> {
  // 入力されたテキストをデータとして持つ
  String _text = '';

  // データを元に表示するWidget
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('リスト追加'),
      ),
      body: Container(
        // 余白を付ける
        padding: EdgeInsets.all(64),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            // 入力されたテキストを表示
            Text(_text, style: TextStyle(color: Colors.blue)),
            const SizedBox(height: 8),
            // テキスト入力
            TextField(
              // 入力されたテキストの値を受け取る(valueが入力されたテキスト)
              onChanged: (String value) {
                // データが変更したことを知らせる(画面を更新する)
                setState(() {
                  // データを変更
                  _text = value;
                });
              },
            ),
            const SizedBox(height: 8),
            Container(
              // 横幅いっぱいに広げる
              width: double.infinity,
              // リスト追加ボタン
              child: ElevatedButton(
                onPressed: () {
                  // "pop"で前の画面に戻る
                  // "pop"の引数から前の画面にデータを渡す
                  Navigator.of(context).pop(_text);
                },
                child: Text('リスト追加', style: TextStyle(color: Colors.white)),
              ),
            ),
            const SizedBox(height: 8),
            Container(
              // 横幅いっぱいに広げる
              width: double.infinity,
              // キャンセルボタン
              child: TextButton(
                // ボタンをクリックした時の処理
                onPressed: () {
                  // "pop"で前の画面に戻る
                  Navigator.of(context).pop();
                },
                child: Text('キャンセル'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

2-2.main.dartのコード解説

本アプリではStatelessWidgetとStatefulWidgetの両方を使っている。
task_app4.png
StatelessWidgetはアプリで最初に1度だけ実行され、1度構築されると状態を変更できない。つまり、変数、アイコン、ボタンを変更したり、データを取得したりしても、アプリの状態を変更することはできない。StatefulWidgetはアプリで何度でも実行される。データを受信したときに外観を変更したり、トリガーされたイベントに応じて外観を変更したりできることを意味する。

task_app5.png

最初にStatelessWidgetで初期画面を描画した後、タスクの追加と画面への反映などにはStatefulWidgetで処理を実行する。本アプリでは1つのStatelessWidgetのクラスと4つのStatefulWidgetのクラスを利用する。StatelessWidgetは呼び出されるとbuild()が実行されてStatelessWidgetの内容が1度だけ処理される。StatefulWidgetは呼び出されるとcreateState()がまず実行された後、build()が実行されてStatefulWidgetの内容が処理される。

参考)StatefulWidgetが2つに分かれている理由をしっかりと理解したい方はこちらを一読!

実行単位であるWidgetをツリー構造で示すと以下の様になる。MyTodoApp WidgetはMaterialApp Widgetを呼び出しThemeData WidgetとStatefulWeidgetであるTodoListPage Widgetを呼び出す。
task_app6.png

TodoListPageはScaffold Widgetを呼び出した後、タイトルバーであるAppBar Widgetとタスクをリスト表示するListView Widgetとタスク追加画面を表示するためのFloatingActionButton Widgetを呼び出す。ListView Widgetではリスト表示に適したCard Widget とListTile Widgetを使って簡単に整ったUIを作成する。FloatingActionButton Widgetはもう一つのStatefulWeidgetであるTodoAddPage Widgetを呼び出す。
task_app7.png

TodoAddPage WidgetではScaffold Widgetを呼び出した後、タイトルバーであるAppBar Widgetとタスク追加を行うためのUIのレイアウトを縦に連ねる表示形式とするためColumn Widgetを呼び出す。Column WidgetはText, SizeBo, TextField, Containerが図のように複数並べて描かれる。
task_app8.png

それでは情報管理アプリを作るためにStatelessWidgetを使ってホーム画面を作成しよう。最初のコードの実行結果は以下。
task_app9.png
最初に必要なコードは以下。StatelessWidgetを使ってMyTodoAppという名前のクラスを作成。戻り値にMaterialApp Widgetを書き内部でリスト一覧という文字を表示。MaterialApp Widgetの内部はテスト用なので後で削除する。

main.dart
import 'package:flutter/material.dart';

void main() {
  // 最初に表示するWidget
  runApp(MyTodoApp());
}

class MyTodoApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // ここは後で消す
      home: Scaffold(
        appBar: AppBar(
          title: Text('リスト一覧'),
        ),
      )
      // 右上に表示される"debug"ラベルを消す
      // debugShowCheckedModeBanner: false,
      // アプリ名
      // title: 'My Todo App',
      // theme: ThemeData(
      //   // テーマカラー
      //   primarySwatch: Colors.blue,
      //   visualDensity: VisualDensity.adaptivePlatformDensity,
      // ),
      // リスト一覧画面を表示
      // home: TodoListPage(),
    );
  }
}

次にStatefulWidgetを使ってTodoListPageのクラスを作成する。新たに追加したコードを含むアプリの実行結果は以下。
task_app10.png
MyTodoApp Widgetの中身をStatefulWidgetのTodoListPageを呼び出せるように書き換える。また、アプリ画面右上のdebug表記をなくすためにdebugShowCheckedModeBanner: falseを入れておく。

main.dart
import 'package:flutter/material.dart';

void main() {
  // 最初に表示するWidget
  runApp(MyTodoApp());
}

class MyTodoApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // 右上に表示される"debug"ラベルを消す
      debugShowCheckedModeBanner: false,
      // アプリ名
      title: 'My Todo App',
      theme: ThemeData(
        // テーマカラー
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      // リスト一覧画面を表示
      home: TodoListPage(),
    );
  }
}

StatefulWidgetを使ってcreateStateするためのTodoListPageクラスおよびbuildするための_TodoListPageStateクラスの二つを作成。_TodoListPageStateクラスの最初で画面に表示するタスクとして'テスト'と'flutter'と'アンドロイド'を含むtodoList変数を定義後、Scaffold Widget内部でAppBar WidgetとListView Widgetを呼び出し、ListView Widgetの中でCard WidgetとListTile Widgetを使ってtodoList変数を表示。itemCount: todoList.lengthの長さだけ、itemBuilder: (context, index)のindexがインクリメントされてtodoListの中身がひとつずつ画面にTextとして表示される。アプリ画面右下にはFloatingActionButton Widgetがあるものの内容はすべてコメントアウトされている。現在は、FloatingActionButtonを押しても何も実行されない。

main.dart
// class MyTodoApp extends StatelessWidget {
// ・・・
// }の続く位置に以下をコピー

// リスト一覧画面用Widget
class TodoListPage extends StatefulWidget {
  @override
  _TodoListPageState createState() => _TodoListPageState();
}

class _TodoListPageState extends State<TodoListPage> {
  // Todoリストのデータ
  List<String> todoList = ['テスト', 'flutter', 'アンドロイド'];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // AppBarを表示し、タイトルも設定
      appBar: AppBar(
        title: Text('リスト一覧'),
      ),
      // データを元にListViewを作成
      body: ListView.builder(
        itemCount: todoList.length,
        itemBuilder: (context, index) {
          return Card(
            child: ListTile(
              title: Text(todoList[index]),
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
        //   // "push"で新規画面に遷移
        //   // リスト追加画面から渡される値を受け取る
        //   final newListText = await Navigator.of(context).push(
        //     MaterialPageRoute(builder: (context) {
        //       // 遷移先の画面としてリスト追加画面を指定
        //       return TodoAddPage();
        //     }),
        //   );
        //   if (newListText != null) {
        //     // キャンセルした場合は newListText が null となるので注意
        //     setState(() {
        //       // リスト追加
        //       todoList.add(newListText);
        //     });
        //   }
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

最後にStatefulWidgetを使ってTodoAddPageクラスを作成する。新たに追加したコードを含むアプリの実行結果は以下。
task_app11.png
まずList<String> todoList = []となるように、前述のtodoListリストの右辺を空値[]で初期化するように書き直す。次にFloatingActionButton Widget内部にあるコメントアウトについて、関数の説明文を除いて全て外す。ボタンが押されるとonPressed Widgetの内部が実行される。TodoAddPage Widgetから追加するタスクのテキスト情報を取得し、todoList.add(newListText)でnewListTextをtodoList変数に追加する。

main.dart
class _TodoListPageState extends State<TodoListPage> {
  // Todoリストのデータ
  List<String> todoList = [];
  
  //・・・

      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          // "push"で新規画面に遷移
          // リスト追加画面から渡される値を受け取る
          final newListText = await Navigator.of(context).push(
            MaterialPageRoute(builder: (context) {
              // 遷移先の画面としてリスト追加画面を指定
              return TodoAddPage();
            }),
          );
          if (newListText != null) {
            // キャンセルした場合は newListText が null となるので注意
            setState(() {
              // リスト追加
              todoList.add(newListText);
            });
          }
        },
        child: Icon(Icons.add),
      ),

// ・・・

StatefulWidgetを使ってcreateStateするためのTodoAddPageクラスおよびbuildするための_TodoAddPageStateクラスの二つを作成。_TodoAddPageStateクラスの最初で_text変数を空値''で初期化。Scaffold WidgetにてAppbar WidgetとContainer Widgetを呼び出す。Container WidgetではColumn Widgetを呼び出し、Widgetを記述した順番にUIに反映される用にする。ここではText、SizedBox、TextField、SizedBox、Container(追加ボタン用)、SizedBox、Container(キャンセルボタン用)の各Widgetを縦に並べてUIを表示する。SizedBox Widgetには数字を引数にとることができ、ここでは8ピクセルの隙間を作るために使われる。まずTextField Widgetにて_text変数にテキストを格納し、次にText Widgetに渡されて入力内容がテキストフィールドの上部に表示される。さらに追加ボタンが押されるとElevatedButton WidgetのonPressed内部で_textがFloatingActionButton WidgetのnewListText変数に対して出力される。

main.dart
//class _TodoListPageState extends State<TodoListPage> {
// ・・・
// }の続く位置に以下をコピー

class TodoAddPage extends StatefulWidget {
  @override
  _TodoAddPageState createState() => _TodoAddPageState();
}

class _TodoAddPageState extends State<TodoAddPage> {
  // 入力されたテキストをデータとして持つ
  String _text = '';

  // データを元に表示するWidget
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('リスト追加'),
      ),
      body: Container(
        // 余白を付ける
        padding: EdgeInsets.all(64),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            // 入力されたテキストを表示
            Text(_text, style: TextStyle(color: Colors.blue)),
            const SizedBox(height: 8),
            // テキスト入力
            TextField(
              // 入力されたテキストの値を受け取る(valueが入力されたテキスト)
              onChanged: (String value) {
                // データが変更したことを知らせる(画面を更新する)
                setState(() {
                  // データを変更
                  _text = value;
                });
              },
            ),
            const SizedBox(height: 8),
            Container(
              // 横幅いっぱいに広げる
              width: double.infinity,
              // リスト追加ボタン
              child: ElevatedButton(
                onPressed: () {
                  // "pop"で前の画面に戻る
                  // "pop"の引数から前の画面にデータを渡す
                  Navigator.of(context).pop(_text);
                },
                child: Text('リスト追加', style: TextStyle(color: Colors.white)),
              ),
            ),
            const SizedBox(height: 8),
            Container(
              // 横幅いっぱいに広げる
              width: double.infinity,
              // キャンセルボタン
              child: TextButton(
                // ボタンをクリックした時の処理
                onPressed: () {
                  // "pop"で前の画面に戻る
                  Navigator.of(context).pop();
                },
                child: Text('キャンセル'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

以上で情報管理アプリは完成。

発展として、上記のコードにタスクを編集&削除する機能をつけてみるとより実用的になるだろう。興味のある人は試してみよう。

ここまでに使ったコードをgithubに置いたのでリンクを紹介
https://github.com/MY-CODE-1981/task_app

3.まとめ

flutterアプリの入門編として情報管理アプリを作成した。

4.スマホアプリ開発コースの概要

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?