#1. Flutter学習歴
Flutter/Dartは触り始めてまだ一か月くらいです。
早くもFlutterを愛し始めております。
- domainフォルダにはロジックを詰め込む用にtodo_domain.dartを用意しました。
- entityフォルダはinitialize用にファイルを用意しましたが、今回は使用しなかったので放置で。。(削除しておけばよかった)
- ui/todoフォルダには、以下のファイルを用意しました。
- list.dart(todo一覧表示用)
- complete.dart(todo完了表示用)
- incomplete.dart (todo未完了表示用)
#5. 各ファイルの説明
- main.dart
import 'package:flutter/material.dart';
import 'ui/todo/list.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Todoアプリ',
home: TodoList(),
);
}
}
- pubspec.yaml
今回のtodoアプリ作成では状態管理でproviderを使用しているので、pubspec.yamlにproviderを使用できるようにinstallします。
dependencies:
flutter:
sdk: flutter
provider: ^4.3.2+2
追加したらターミナルで以下のコマンドを打って使用できるようになります!
$ flutter pub get
- list.dart
この画面でCRUD処理とtodoを完了状態に更新ができます。
完了状態にするには、todo(ListTile)を長押しします!
AppBarの左上にdrawerを設置し、そこから完了ページと未完了ページに遷移できるように設定しています。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:todo_app/domain/todo_domain.dart';
import 'package:todo_app/ui/todo/complete.dart';
import 'package:todo_app/ui/todo/incomplete.dart';
class TodoList extends StatelessWidget {
final _formKey = GlobalKey<FormState>();
// ignore: missing_return
Color getColor(bool isCompleted) {
if (isCompleted) {
return Colors.amber;
}
}
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<TodoDomain>(
create: (_) => TodoDomain(),
child: Scaffold(
appBar: AppBar(
title: Text('TodoLists'),
actions: <Widget>[
Consumer<TodoDomain>(
builder: (context, model, child) {
return IconButton(
icon: Icon(Icons.add),
onPressed: () {
// todoの追加画面をダイアログで開く
showDialog(
context: context,
// 下記はダイアログの外側を押すとダイアログを閉じるようにするかの設定
// barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text(
'追加したいTodoを教えて!',
style: TextStyle(fontSize: 14),
),
content: Form(
key: _formKey,
child: TextFormField(
// 複数行入力できるようにしている
keyboardType: TextInputType.multiline,
maxLines: null,
// ignore: missing_return
validator: (value) {
if (value.isEmpty) {
return '今日する事教えてくれないの。。?';
}
},
onChanged: (String text) {
// TodoDomainのtodoフィールドに、TextFormFieldで打った文字を反映させている
model.todo = text;
},
),
),
// AlertDialogではボタン関係はactionsで定義しなくてはいけない
actions: <Widget>[
FlatButton(
child: Text('OK'),
onPressed: () {
if (_formKey.currentState.validate()) {
_formKey.currentState
.save(); // TextFormFieldのonSavedが呼び出される
}
try {
// todoの追加
model.addTodo();
// TodoListsページに戻って、dialogを閉じる
Navigator.pop(context);
} catch (e) {
print('今日する事が入力されていません');
}
},
),
],
);
},
);
},
);
},
)
],
),
body: Consumer<TodoDomain>(
builder: (context, model, child) {
return ListView.builder(
itemCount: model.todos.length,
// ignore: missing_return
itemBuilder: (context, index) {
return Card(
child: Container(
color: getColor(model.todos[index]['isCompleted']),
child: ListTile(
title: Text(
model.todos[index]['text'],
style: TextStyle(fontSize: 14),
),
contentPadding: EdgeInsets.all(8),
onLongPress: () {
if (!model.todos[index]['isCompleted']) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Align(
alignment: Alignment.center,
child: Text(
'完了しましたか?',
style: TextStyle(fontSize: 14),
textAlign: TextAlign.center,
),
),
// AlertDialogではボタン関係はactionsで定義しなくてはいけない
actions: [
FlatButton(
onPressed: () {
model.completeTodo(index);
Navigator.of(context).pop();
},
child: Text('OK'),
),
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('NG'),
),
],
);
},
);
}
},
// ListTileのtrailingに2つアイコンを並べたい時はWrapしてあげる
trailing: Wrap(
children: [
IconButton(
icon: Icon(Icons.edit),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(
'追加したTodo間違えちゃった?',
style: TextStyle(fontSize: 14),
),
content: Form(
key: _formKey,
child: TextFormField(
keyboardType: TextInputType.multiline,
maxLines: null,
initialValue: model.todos[index]
['text'],
// ignore: missing_return
validator: (value) {
if (value.isEmpty) {
return '今日する事教えてくれないの。。?';
}
},
onChanged: (String text) {
model.todo = text;
},
),
),
// AlertDialogではボタン関係はactionsで定義しなくてはいけない
actions: <Widget>[
FlatButton(
child: Text('OK'),
onPressed: () {
if (_formKey.currentState
.validate()) {
_formKey.currentState
.save(); // TextFormFieldのonSavedが呼び出される
}
try {
// todoの追加
model.editTodo(index);
// TodoListsページに戻って、dialogを閉じる
Navigator.pop(context);
} catch (e) {
print('今日する事が入力されていません');
if (model.todo == null) {
Navigator.of(context).pop();
}
}
},
),
],
);
},
);
},
),
IconButton(
icon: Icon(
Icons.delete,
color: Colors.redAccent,
),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Align(
alignment: Alignment.center,
child: Text(
'本当に削除しますか?',
style: TextStyle(fontSize: 16),
textAlign: TextAlign.center,
),
),
actions: [
FlatButton(
onPressed: () {
model.deleteTodo(index);
Navigator.of(context).pop();
},
child: Text('OK'),
),
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('NG'),
),
],
);
},
);
},
),
],
),
),
),
);
},
);
},
),
drawer: Consumer<TodoDomain>(
builder: (context, model, child) {
return Drawer(
child: ListView(
children: <Widget>[
SizedBox(
height: 80,
child: DrawerHeader(
child: Text(
'Menu',
style: TextStyle(fontSize: 36, color: Colors.white),
),
decoration: BoxDecoration(color: Colors.blue),
),
),
Card(
child: ListTile(
title: Text('Incomplete'),
onTap: () {
// 未完了ページに飛ぶ前にDrawerを閉じている
Navigator.of(context).pop();
List incompleteTodos = model.getIncompleteTodos();
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
TodoIncomplete(incompleteTodos),
),
);
},
),
),
Card(
child: ListTile(
title: Text('Complete'),
onTap: () {
// 完了ページに飛ぶ前にDrawerを閉じている
Navigator.of(context).pop();
List completeTodos = model.getCompleteTodo();
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => TodoComplete(completeTodos),
),
);
},
),
)
],
),
);
},
),
),
);
}
}
- complete.dart
完了しているtodoのみを表示するファイルになります。
import 'package:flutter/material.dart';
class TodoComplete extends StatelessWidget {
TodoComplete(this.completeTodo);
final List completeTodo;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TodoComplete'),
),
body: ListView.builder(
itemCount: completeTodo.length,
// ignore: missing_return
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(
completeTodo[index]['text'],
style: TextStyle(fontSize: 14),
),
contentPadding: EdgeInsets.all(8),
),
);
},
),
);
}
}
- incomplete.dart
未完了のtodoのみを表示するファイルになります。
import 'package:flutter/material.dart';
class TodoIncomplete extends StatelessWidget {
TodoIncomplete(this.incompleteTodo);
final List incompleteTodo;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TodoIncomplete'),
),
body: ListView.builder(
itemCount: incompleteTodo.length,
// ignore: missing_return
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(
incompleteTodo[index]['text'],
style: TextStyle(fontSize: 14),
),
contentPadding: EdgeInsets.all(8),
),
);
},
),
);
}
}
- todo_domain.dart
todoのCRUD処理等を記述したファイルです。
todoに対しての操作はこのファイルから行うようにしました!
import 'package:flutter/material.dart';
class TodoDomain extends ChangeNotifier {
String todo;
List todos = [
{
'isCompleted': false,
'text': '今日はFlutterのお勉強をします。目標はTodoアプリの完成です。頑張ります。応援して下さい。'
}
];
var completeTodos = [];
var incompleteTodos = [];
// todoの追加
void addTodo() {
if (todo.isEmpty) {
return;
}
// todosに連想配列を追加するため
Map todoData = {'isCompleted': false, 'text': todo};
// todosの配列にt連想配列のtodoDataを追加
todos.add(todoData);
// 状態をなくしている 無くさないと追加ボタンとか押した時にtodoに前に入力したデータが残ってちゃう
todo = null;
// notifyListeners()でTodoDomainが使用されているChangeNotifyProviderに変更を通知
notifyListeners();
}
// todoの編集
void editTodo(int index) {
if (todo.isEmpty) {
return;
}
// 編集されたtodosの中のtodoのindexを引数でもらって、todosの中のindex番号のtodoを編集したtodoに書き換えている
todos[index]['text'] = todo;
todo = null;
notifyListeners();
}
// todoの削除
void deleteTodo(int index) {
todos.removeAt(index);
notifyListeners();
}
// todoを完了状態に更新
void completeTodo(int index) {
todos[index]['isCompleted'] = true;
notifyListeners();
}
// 完了済みのtodoの取得
List getCompleteTodo() {
completeTodos.clear();
for (var i = 0; i < todos.length; i++) {
var data = todos[i];
if (data['isCompleted']) {
completeTodos.add(data);
}
}
return completeTodos;
}
// 未完了のtodoの取得
List getIncompleteTodos() {
incompleteTodos.clear();
for (var i = 0; i < todos.length; i++) {
var data = todos[i];
if (!data['isCompleted']) {
incompleteTodos.add(data);
}
}
return incompleteTodos;
}
}
#6. 改善点
- Firebase使用してtodoを永続的に保存
- 現時点では作成したtodoをそのまま配列に追加して状態を保持してるだけなので、デバッグし直したら消えちゃいます😇
- リファクタリング
- dataって変数名は直します。。
- list.dart内のコード分割(できると思う)
他にもまだまだ有ると思うので修正していきたいと思います!
#7. 作成した感想
状態管理がまだまだ理解し切れていないなぁと感じました。
一番の肝かなと思うんで深掘りしていきます。
あとはwidgetの分割とか出来ないのかなぁと思いました。
drawerとか別ファイルに分割して全画面に共通して表示とかできるのではと思うんですよね。。
今度調べて試してみよ。。