1.はじめに
これまでに7つのAndroidStudio&Flutter&Dart記事を書いている。以下は内容、
-
【Flutter/Dart】環境構築方法の紹介
https://qiita.com/my_programming/items/ad1493e3fef2fb63e036 -
【Flutter/Dart】簡単なプロジェクトの設定と共有方法の紹介
https://qiita.com/my_programming/items/cec09574a3b6e39be277 -
【Flutter/Dart】エミュレータの言語設定をかえよう
https://qiita.com/my_programming/items/6c67fed22c624fd9494b -
【Flutter/Dart】Dartの文法について学ぼう
https://qiita.com/my_programming/items/9ba25114ef217d077ca5 -
【Flutter/Dart】タスク管理アプリを作ろう(今回説明する内容)
-
【Flutter/Dart】Google地図アプリを作ろう
https://qiita.com/my_programming/items/26b9ac6f0d2b3d1bd766 -
【Flutter/Dart】情報管理アプリ+Google地図アプリを作ろう
https://qiita.com/my_programming/items/5bd1bb7b789b642635b4
今回はflutterアプリの入門編として情報管理アプリを作成する。
-ボタンを押すと登録画面が開く機能
-登録画面で記入したタスクがトップ画面でリスト表示される機能
2.情報管理アプリを作ろう
2-1.完成イメージ
アプリの完成イメージと操作方法を説明する。
①プロジェクトを実行するとタスクの描画画面が開く。タスクを追加するためにプラスボタンを押す。
②テキストフィールドにタスク名称を書き、リスト追加ボタンを押す。
③3回追加した後の画面が以下。タスクが追加した順番に表示されている。
2-2.コードの説明
①まず、task_appという名前の新しいプロジェクトを作成する。
②libフォルダにある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の両方を使っている。
StatelessWidgetはアプリで最初に1度だけ実行され、1度構築されると状態を変更できない。つまり、変数、アイコン、ボタンを変更したり、データを取得したりしても、アプリの状態を変更することはできない。StatefulWidgetはアプリで何度でも実行される。データを受信したときに外観を変更したり、トリガーされたイベントに応じて外観を変更したりできることを意味する。
最初に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を呼び出す。
TodoListPageはScaffold Widgetを呼び出した後、タイトルバーであるAppBar Widgetとタスクをリスト表示するListView Widgetとタスク追加画面を表示するためのFloatingActionButton Widgetを呼び出す。ListView Widgetではリスト表示に適したCard Widget とListTile Widgetを使って簡単に整ったUIを作成する。FloatingActionButton Widgetはもう一つのStatefulWeidgetであるTodoAddPage Widgetを呼び出す。
TodoAddPage WidgetではScaffold Widgetを呼び出した後、タイトルバーであるAppBar Widgetとタスク追加を行うためのUIのレイアウトを縦に連ねる表示形式とするためColumn Widgetを呼び出す。Column WidgetはText, SizeBo, TextField, Containerが図のように複数並べて描かれる。
それでは情報管理アプリを作るためにStatelessWidgetを使ってホーム画面を作成しよう。最初のコードの実行結果は以下。
最初に必要なコードは以下。StatelessWidgetを使ってMyTodoAppという名前のクラスを作成。戻り値にMaterialApp Widgetを書き内部でリスト一覧という文字を表示。MaterialApp Widgetの内部はテスト用なので後で削除する。
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のクラスを作成する。新たに追加したコードを含むアプリの実行結果は以下。
MyTodoApp Widgetの中身をStatefulWidgetのTodoListPageを呼び出せるように書き換える。また、アプリ画面右上のdebug表記をなくすためにdebugShowCheckedModeBanner: false
を入れておく。
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を押しても何も実行されない。
// 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クラスを作成する。新たに追加したコードを含むアプリの実行結果は以下。
まずList<String> todoList = []
となるように、前述のtodoListリストの右辺を空値[]
で初期化するように書き直す。次にFloatingActionButton Widget内部にあるコメントアウトについて、関数の説明文を除いて全て外す。ボタンが押されるとonPressed Widgetの内部が実行される。TodoAddPage Widgetから追加するタスクのテキスト情報を取得し、todoList.add(newListText)
でnewListTextをtodoList変数に追加する。
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変数に対して出力される。
//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.スマホアプリ開発コースの概要