Flutterでタスク管理アプリを作ろう!~アプリ開発編~
はじめに
この記事では、Flutter を使ってシンプルなタスク管理アプリを開発する方法について解説します。Flutter は、Google が提供するオープンソースの UI フレームワークで、美しく動作するクロスプラットフォームアプリを簡単に構築できます。この記事を通じて、Flutter の基本的な使い方やアプリ開発の流れを学んでいきましょう。
ChatGPT の使い方について
開発中に疑問が生じた場合には、ChatGPT を利用して質問することをお勧めします。具体的なコードの書き方から、Flutter のベストプラクティスまで幅広くサポートを受けられるでしょう。
具体的な質問例
- 「Flutter での状態管理のベストプラクティスは何ですか?」
- 「ListView.builder の使い方を教えてください。」
- 「shared_preferences パッケージのインストール方法を教えてください。」
Flutter プロジェクトの作成
まずは、Flutter プロジェクトを作成してみましょう。
プロジェクトフォルダを作成し、VSCode で開く
-
自分の PC 上の任意のディレクトリに
flutter-workshop
という名前のフォルダを作成します。(フォルダ名は何でも構いません) -
Visual Studio Code(以下、VSCode)でそのフォルダを開きます。VSCode を開いてから、「開く」で対象のフォルダを選択することもできます。
flutter create コマンド
-
VSCode でプロジェクトフォルダを開いたら、ターミナルを開きます。左から 2 番目のボタンを押すことで、VSCode 内でターミナルを開くことができます。
-
以下のコマンドを実行して、新しい Flutter プロジェクトを作成します。
flutter create --org app.web --project-name flutter_workshop --description "Flutter講習会" --platforms=web ./
--org app.web
:アプリの組織名を指定します。--project-name flutter_workshop
:プロジェクト名を指定します。--description "Flutter 講習会"
:プロジェクトの説明を設定します。--platforms=web
:ターゲットプラットフォームを Web に指定します。./
:現在のディレクトリにプロジェクトを作成します。
コマンドが完了すると、多数のファイルやフォルダが作成されます。これでプロジェクトの初期設定は成功です!
アプリの実行方法
-
アプリ開発を始める準備が整いました!主に編集するのは、
lib/main.dart
ファイルのコードです。このファイルには、デフォルトで「ボタンを押すと数字が増えるアプリ」が作成されています。 -
試しにこのアプリを実行してみましょう。VSCode のターミナルで以下のコマンドを実行します。
flutter run -d chrome
すると、アプリが
Google Chrome
上で立ち上がります!補足として、-d chrome
は、Chrome ブラウザ上でアプリを実行するための指定です。
これで、アプリの作成準備と起動方法が理解できました。次からは、実際に ToDo アプリを作成していきます!
基本的なアプリ構造の設定
lib/main.dart
ファイルを開き、以下のコードを追加します。ここでは、アプリの基本的な構造を設定します。
import 'package:flutter/material.dart';
void main() {
runApp(TaskManagerApp());
}
class TaskManagerApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'タスク管理アプリ',
home: TaskManagerHomePage(),
);
}
}
class TaskManagerHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('タスク管理アプリ'),
),
body: Center(
child: Text('ここにタスク一覧が表示されます'),
),
);
}
}
説明:
runApp
: Flutterアプリケーションを起動するための関数です。ここではTaskManagerApp
ウィジェットをルートとして設定しています。MaterialApp
: Flutterのアプリケーションを構築するためのウィジェットで、Materialデザインを適用します。title
はアプリのタイトルを指定します。home
: アプリのホーム画面を指定します。ここではTaskManagerHomePage
を設定しています。ListView.builder
: リストを効率的に表示するためのウィジェットで、スクロール可能なリストを作成します。itemCount
でリストのアイテム数を指定し、itemBuilder
で各アイテムのビルド方法を指定します。ListTile
: リストの各アイテムを表示するためのウィジェットで、タイトルやサブタイトル、アイコンなどを簡単に配置できます。
TODOアプリの見た目を作る
ここでは、Flutterを使って基本的なUI(ユーザーインターフェース)を構築します。UIとは、ユーザーがアプリとやり取りするための画面やボタンなどの要素のことです。
ステップ1: ホームページの作成
まずは、アプリのホームページとなるTaskManagerHomePage
クラスを編集します。このクラスでは、TODOリストを表示するための基本的なレイアウトを設定します。
TaskManagerHomePage
クラスの作成
lib/main.dart
ファイルに以下のコードを追加します。
class TaskManagerHomePage extends StatefulWidget {
@override
_TaskManagerHomePageState createState() => _TaskManagerHomePageState();
}
class _TaskManagerHomePageState extends State<TaskManagerHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('タスク管理'),
),
body: Center(
child: Text('ここにタスク一覧が表示されます'),
),
);
}
}
説明:
StatefulWidget
: 状態を持つウィジェットです。ユーザーの操作によって変化するUIを作るときに使います。Scaffold
: アプリの基本的なレイアウトを提供するウィジェットで、AppBarやDrawer、FloatingActionButtonなどを簡単に配置できます。AppBar
: アプリの上部に表示されるバーで、タイトルやアクションボタンを配置できます。Center
: 子ウィジェットを中央に配置するためのウィジェットです。
ステップ2: TODOリストの表示
次に、TODOリストを表示するための基本的なUIを作成します。ここでは、ListView
を使ってリスト形式でタスクを表示します。
ListView
の追加
_TaskManagerHomePageState
クラスのbuild
メソッドを以下のように修正します。
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('タスク管理'),
),
body: ListView(
children: <Widget>[
ListTile(
leading: Icon(Icons.check_box_outline_blank),
title: Text('タスク1'),
),
ListTile(
leading: Icon(Icons.check_box_outline_blank),
title: Text('タスク2'),
),
],
),
);
}
説明:
ListView
: 複数のウィジェットを縦方向にスクロール可能なリストとして表示するウィジェットです。ListTile
: リスト内の各項目を表現するためのウィジェットで、タイトルやアイコンなどを簡単に配置できます。Icon
: アイコンを表示するためのウィジェットです。ここでは、チェックボックスのアイコンを使用しています。
ステップ3: タスクの追加ボタン
最後に、タスクを追加するためのボタンを画面に追加します。FloatingActionButton
を使って、画面の右下にボタンを配置します。
FloatingActionButton
の追加
Scaffold
ウィジェットのプロパティにfloatingActionButton
を追加します。
floatingActionButton: FloatingActionButton(
onPressed: () {
// タスク追加の処理をここに書きます
},
child: Icon(Icons.add),
tooltip: 'タスクを追加',
),
説明:
FloatingActionButton
: 画面の上に浮かぶように表示されるボタンで、主にアクションを促すために使用されます。onPressed
: ボタンが押されたときに実行される関数を指定します。tooltip
: ボタンにカーソルを合わせたときに表示されるテキストです。
ステップ4: タスク追加機能の実装
次に、タスクを追加できる機能を実装していきましょう。ここでは、ユーザーが新しいタスクを追加できるように、ダイアログを表示してタスク名を入力する方法を紹介します。
1. タスクを保持するリストを作成
まず、タスクを保持するためのリストを_TaskManagerHomePageState
クラスに追加します。このリストは、ユーザーが追加したタスクを保持します。
class _TaskManagerHomePageState extends State<TaskManagerHomePage> {
List<String> _tasks = [];
@override
Widget build(BuildContext context) {
// 省略
}
}
2. タスク追加用のダイアログを表示
FloatingActionButton
のonPressed
メソッドに、タスクを追加するためのダイアログを表示する処理を追加します。
floatingActionButton: FloatingActionButton(
onPressed: () {
_displayAddTaskDialog(context);
},
child: Icon(Icons.add),
tooltip: 'タスクを追加',
),
次に、_displayAddTaskDialog
メソッドを定義します。このメソッドでは、ユーザーがタスク名を入力できるダイアログを表示します。
void _displayAddTaskDialog(BuildContext context) {
TextEditingController taskController = TextEditingController();
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('新しいタスクを追加'),
content: TextField(
controller: taskController,
decoration: InputDecoration(hintText: "タスク名を入力"),
),
actions: <Widget>[
TextButton(
child: Text('キャンセル'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text('追加'),
onPressed: () {
setState(() {
_tasks.add(taskController.text);
});
Navigator.of(context).pop();
},
),
],
);
},
);
}
説明:
TextEditingController
: テキストフィールドのテキストを管理するためのコントローラです。ユーザーが入力したテキストを取得するのに使います。showDialog
: ダイアログを表示するための関数です。builder
でダイアログの内容を指定します。AlertDialog
: アラートダイアログを表示するためのウィジェットで、タイトル、コンテンツ、アクションボタンを指定できます。TextField
: ユーザーがテキストを入力できるフィールドです。TextButton
: テキストのみのボタンを表示するためのウィジェットです。
3. タスクリストの更新
ListView
を_tasks
リストの内容に基づいて動的に生成するように変更します。
body: ListView.builder(
itemCount: _tasks.length,
itemBuilder: (context, index) {
return ListTile(
leading: Icon(Icons.check_box_outline_blank),
title: Text(_tasks[index]),
);
},
),
説明:
ListView.builder
: リストのアイテムを動的に生成するためのウィジェットです。itemCount
でアイテム数を指定し、itemBuilder
で各アイテムのビルド方法を指定します。_tasks[index]
: リストの各アイテムのタイトルとして、_tasks
リストの要素を使用します。
これで、ユーザーが「追加」ボタンを押すと、ダイアログが表示され、タスク名を入力して追加できるようになります。タスクはリストビューに表示され、追加されるたびにリストが更新されます。
現時点での全体のコード
以下は、これまでに作成したタスク管理アプリのコードです。
import 'package:flutter/material.dart';
void main() {
runApp(TaskManagerApp());
}
class TaskManagerApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'タスク管理アプリ',
home: TaskManagerHomePage(),
);
}
}
class TaskManagerHomePage extends StatefulWidget {
@override
_TaskManagerHomePageState createState() => _TaskManagerHomePageState();
}
class _TaskManagerHomePageState extends State<TaskManagerHomePage> {
List<String> _tasks = [];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('タスク管理'),
),
body: ListView.builder(
itemCount: _tasks.length,
itemBuilder: (context, index) {
return ListTile(
leading: Icon(Icons.check_box_outline_blank),
title: Text(_tasks[index]),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_displayAddTaskDialog(context);
},
child: Icon(Icons.add),
tooltip: 'タスクを追加',
),
);
}
void _displayAddTaskDialog(BuildContext context) {
TextEditingController taskController = TextEditingController();
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('新しいタスクを追加'),
content: TextField(
controller: taskController,
decoration: InputDecoration(hintText: "タスク名を入力"),
),
actions: <Widget>[
TextButton(
child: Text('キャンセル'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text('追加'),
onPressed: () {
setState(() {
_tasks.add(taskController.text);
});
Navigator.of(context).pop();
},
),
],
);
},
);
}
}
タスク削除機能の実装
次に、ユーザーがタスクを削除できる機能を追加していきましょう。これにより、完了したタスクや不要になったタスクをリストから取り除くことができます。
ステップ1: タスク削除のためのUIの追加
まずは、各タスクに削除ボタンを追加します。ListTile
ウィジェットに削除アイコンを追加し、タスクを削除するためのアクションを設定します。
ListTile
に削除アイコンを追加
_TaskManagerHomePageState
クラスのbuild
メソッド内のListView.builder
のitemBuilder
を以下のように修正します。
body: ListView.builder(
itemCount: _tasks.length,
itemBuilder: (context, index) {
return ListTile(
leading: Icon(Icons.check_box_outline_blank),
title: Text(_tasks[index]),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
setState(() {
_tasks.removeAt(index);
});
},
),
);
},
),
説明:
trailing
:ListTile
の右端に表示されるウィジェットを指定します。ここでは削除アイコンを表示しています。IconButton
: アイコンをボタンとして表示するウィジェットです。onPressed
でボタンが押されたときの処理を指定します。_tasks.removeAt(index)
: 指定したインデックスのタスクをリストから削除します。
ステップ2: 削除確認ダイアログの追加(オプション)
タスクを削除する前に、確認のダイアログを表示することで、誤ってタスクを削除してしまうことを防ぐことができます。
削除確認ダイアログの実装
削除アイコンのonPressed
メソッドを以下のように修正し、削除前に確認ダイアログを表示します。
onPressed: () {
_showDeleteConfirmationDialog(context, index);
},
次に、_showDeleteConfirmationDialog
メソッドを定義します。
void _showDeleteConfirmationDialog(BuildContext context, int index) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('タスクを削除しますか?'),
content: Text('このタスクを削除してもよろしいですか?'),
actions: <Widget>[
TextButton(
child: Text('キャンセル'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text('削除'),
onPressed: () {
setState(() {
_tasks.removeAt(index);
});
Navigator.of(context).pop();
},
),
],
);
},
);
}
説明:
showDialog
: ダイアログを表示するための関数です。AlertDialog
: 削除確認用のダイアログを表示します。TextButton
: ダイアログのアクションボタンを表示します。削除をキャンセルするか、実行するかを選択できます。
現時点での全体のコード
以下は、タスク削除機能を追加したタスク管理アプリのコードです。
import 'package:flutter/material.dart';
void main() {
runApp(TaskManagerApp());
}
class TaskManagerApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'タスク管理アプリ',
home: TaskManagerHomePage(),
);
}
}
class TaskManagerHomePage extends StatefulWidget {
@override
_TaskManagerHomePageState createState() => _TaskManagerHomePageState();
}
class _TaskManagerHomePageState extends State<TaskManagerHomePage> {
List<String> _tasks = [];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('タスク管理'),
),
body: ListView.builder(
itemCount: _tasks.length,
itemBuilder: (context, index) {
return ListTile(
leading: Icon(Icons.check_box_outline_blank),
title: Text(_tasks[index]),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
_showDeleteConfirmationDialog(context, index);
},
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_displayAddTaskDialog(context);
},
child: Icon(Icons.add),
tooltip: 'タスクを追加',
),
);
}
void _displayAddTaskDialog(BuildContext context) {
TextEditingController taskController = TextEditingController();
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('新しいタスクを追加'),
content: TextField(
controller: taskController,
decoration: InputDecoration(hintText: "タスク名を入力"),
),
actions: <Widget>[
TextButton(
child: Text('キャンセル'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text('追加'),
onPressed: () {
setState(() {
_tasks.add(taskController.text);
});
Navigator.of(context).pop();
},
),
],
);
},
);
}
void _showDeleteConfirmationDialog(BuildContext context, int index) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('タスクを削除しますか?'),
content: Text('このタスクを削除してもよろしいですか?'),
actions: <Widget>[
TextButton(
child: Text('キャンセル'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text('削除'),
onPressed: () {
setState(() {
_tasks.removeAt(index);
});
Navigator.of(context).pop();
},
),
],
);
},
);
}
}
これで、タスクを削除する機能が実装されました。ユーザーはタスクリストの各項目に表示される削除ボタンを押すことで、タスクを削除することができます。また、削除前に確認ダイアログを表示することで、誤操作を防ぐことができます。
データの永続化
続きとして、タスク管理アプリにデータの永続化機能を追加していきましょう。データの永続化とは、アプリを終了してもデータが失われないように保存することです。これにより、アプリを再起動してもタスクが保持されます。Flutterでは、shared_preferences
というパッケージを使って簡単にデータを永続化できます。
ステップ1: shared_preferences
パッケージのインストール
まずは、shared_preferences
パッケージをプロジェクトに追加します。このパッケージは、キーと値のペアを保存するためのシンプルな方法を提供します。
1. pubspec.yaml
ファイルを編集
pubspec.yaml
ファイルを開き、dependencies
セクションにshared_preferences
を追加します。
dependencies:
flutter:
sdk: flutter
shared_preferences: ^2.3.2 # 最新バージョンを確認してください
shared_preferences
: Flutterでデータを永続化するためのパッケージです。バージョンは最新のものを指定してください。
2. パッケージをインストール
ターミナルで以下のコマンドを実行して、パッケージをインストールします。
flutter pub get
ステップ2: タスクの保存と読み込み
次に、アプリを起動するたびにタスクを保存し、再度読み込む機能を実装します。
1. shared_preferences
をインポート
lib/main.dart
ファイルの先頭にshared_preferences
をインポートします。
import 'package:shared_preferences/shared_preferences.dart';
2. タスクの保存
タスクを追加したときに、shared_preferences
を使ってタスクを保存します。
void _saveTasks() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setStringList('tasks', _tasks);
}
SharedPreferences.getInstance()
:SharedPreferences
のインスタンスを取得します。setStringList
: 文字列のリストを保存するためのメソッドです。
タスクを追加する際に、このメソッドを呼び出します。
TextButton(
child: Text('追加'),
onPressed: () {
setState(() {
_tasks.add(taskController.text);
_saveTasks();
});
Navigator.of(context).pop();
},
),
3. タスクの読み込み
アプリ起動時に保存されたタスクを読み込みます。_TaskManagerHomePageState
のinitState
メソッドをオーバーライドして、タスクを読み込む処理を追加します。
@override
void initState() {
super.initState();
_loadTasks();
}
void _loadTasks() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_tasks = prefs.getStringList('tasks') ?? [];
});
}
initState
: ウィジェットが初めて作成されたときに呼び出されるメソッドです。getStringList
: 保存された文字列のリストを取得するためのメソッドです。データが存在しない場合は、空のリストを返します。
ステップ3: タスク削除時の保存
タスクを削除したときにも、変更を保存するようにします。
void _showDeleteConfirmationDialog(BuildContext context, int index) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('タスクを削除しますか?'),
content: Text('このタスクを削除してもよろしいですか?'),
actions: <Widget>[
TextButton(
child: Text('キャンセル'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text('削除'),
onPressed: () {
setState(() {
_tasks.removeAt(index);
_saveTasks();
});
Navigator.of(context).pop();
},
),
],
);
},
);
}
これで、タスクを追加したり削除したりするたびに、データが保存されるようになりました。アプリを再起動しても、タスクが保持されることを確認できます。
現時点での全体のコード
以下は、データの永続化機能を追加したタスク管理アプリのコードです。
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(TaskManagerApp());
}
class TaskManagerApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'タスク管理アプリ',
home: TaskManagerHomePage(),
);
}
}
class TaskManagerHomePage extends StatefulWidget {
@override
_TaskManagerHomePageState createState() => _TaskManagerHomePageState();
}
class _TaskManagerHomePageState extends State<TaskManagerHomePage> {
List<String> _tasks = [];
@override
void initState() {
super.initState();
_loadTasks();
}
void _loadTasks() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_tasks = prefs.getStringList('tasks') ?? [];
});
}
void _saveTasks() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setStringList('tasks', _tasks);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('タスク管理'),
),
body: ListView.builder(
itemCount: _tasks.length,
itemBuilder: (context, index) {
return ListTile(
leading: Icon(Icons.check_box_outline_blank),
title: Text(_tasks[index]),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
_showDeleteConfirmationDialog(context, index);
},
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_displayAddTaskDialog(context);
},
child: Icon(Icons.add),
tooltip: 'タスクを追加',
),
);
}
void _displayAddTaskDialog(BuildContext context) {
TextEditingController taskController = TextEditingController();
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('新しいタスクを追加'),
content: TextField(
controller: taskController,
decoration: InputDecoration(hintText: "タスク名を入力"),
),
actions: <Widget>[
TextButton(
child: Text('キャンセル'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text('追加'),
onPressed: () {
setState(() {
_tasks.add(taskController.text);
_saveTasks();
});
Navigator.of(context).pop();
},
),
],
);
},
);
}
void _showDeleteConfirmationDialog(BuildContext context, int index) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('タスクを削除しますか?'),
content: Text('このタスクを削除してもよろしいですか?'),
actions: <Widget>[
TextButton(
child: Text('キャンセル'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text('削除'),
onPressed: () {
setState(() {
_tasks.removeAt(index);
_saveTasks();
});
Navigator.of(context).pop();
},
),
],
);
},
);
}
}
これで、タスク管理アプリにデータの永続化機能が追加されました。
おわりに
学んだことのまとめ
このワークショップを通じて、Flutter の基本的な使い方や、UI の設計、データの管理方法について学びました。
今回学んだ機能が含まれるデモアプリのソースコードはこちらのGitHubのリンクから見ることができます。
今後の追加機能について
このアプリにさらに機能を追加することができます。例えば、タスクの詳細、見た目の調整、編集機能、期限の設定、通知機能などです。