前回はこちら
次回はこちら
1日遅れてすいません。しかも解説は追記予定です。
ソースコードだけは置くので参考になれば幸いです。
(追記2020/2/16)
githubにプロジェクトコードを置きました。
Firebaseでデモを置きました。
最近Dartを使っているので、Flutterを再び勉強するモチベーションが出てまいりました。Flutterは以前少しだけ触って放置していたので、改めてキャッチアップしてみると、なんとFlutter for Webというのがあったので興味本位で触ってみることにしました。
そういうわけでFlutter for Webで簡単なTodoリストを作ります。
やること
開発するときにみた情報源の話。作り始めのころの雰囲気を記録したいと思います。なので読みにくい点もあると思いますが、その時はもし良ければ「読みにくい」ってコメントをお願いします🙇♂️
やらないこと
設計周りの話はしません。今はクリーンなアーキテクチャよりもプログラミングを楽しむことに価値を置きたいと思います。
どういうのを作るのか
仕様を決めます。
- ユーザーはタスクリストを見ることができる。
- ユーザーはタスク名を入力し、タスクリストに追加できる。
- ユーザーはタスクリストから終わったタスクをワンクリックで消すことができる。
- ユーザーはタスクリストに書いたタスクを次回も見ることができる。
足りなければ追加するということで、とりあえずこれで行きます。
あとは画面のモックを作っておきます。
ハイクオリティな画面モック |
環境構築
公式の方法に従って導入します。
現在はbetaチャンネルなので今後stableに変わると思います。
というわけでプロジェクトを作ります。
flutter create --org com.akior todolist_app
cd todolist_app
code .
VSCodeであらかじめ設定しておけばこれでプロジェクトで開けるはずです。
あと、VSCodeはFlutterの拡張設定は必要です。
flutter run -d chrome
これでweb画面が起動します。
ちなみにVSCodeからも起動できます。
Touch Barの実行ボタンでも起動できます。白い三角が実行、中抜き三角がデバッグ実行。 |
完成
タスクを追加する処理です。
![]() |
ユーザーはタスク名を入力し、タスクリストに追加できる |
タスクを削除する処理です。
![]() |
ユーザーはタスクリストから終わったタスクをワンクリックで消すことができる |
更新しても画面は保持されたままです。
![]() |
ユーザーはタスクリストに書いたタスクを次回も見ることができる |
ここにソースコードを示します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
import 'dart:html'; | |
void main() => runApp(TodoApp()); | |
class TodoApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Flutter Demo', | |
theme: ThemeData( | |
primarySwatch: Colors.blue, | |
), | |
home: Scaffold( | |
appBar: AppBar( | |
title: Text('ToDoリスト'), | |
), | |
body: TodoList(), | |
), | |
); | |
} | |
} | |
class TodoList extends StatefulWidget { | |
@override | |
_TodoListState createState() => _TodoListState(); | |
} | |
class _TodoListState extends State<TodoList> { | |
final GlobalKey<AnimatedListState> _listKey = GlobalKey(); | |
List<TodoTask> todoList = List(); | |
final myController = TextEditingController(); | |
WebStrage strage = WebStrage(); | |
@override | |
void initState() { | |
super.initState(); | |
if (strage.getTitleList() == null || strage.getTitleList().isEmpty) { | |
return; | |
} | |
List<String> titleList = strage.getTitleList().split(','); | |
titleList.forEach((f) => todoList.add(TodoTask.initialize(f))); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Container( | |
child: Column( | |
children: <Widget>[ | |
Card( | |
child: ListTile( | |
title: TextField( | |
controller: myController, | |
), | |
trailing: IconButton( | |
onPressed: () { | |
_addItems( | |
myController.text != '' ? myController.text : 'no title'); | |
_saveToLocalStrage(); | |
myController.clear(); | |
}, | |
icon: Icon(Icons.add_circle), | |
), | |
), | |
), | |
Expanded( | |
child: AnimatedList( | |
key: _listKey, | |
initialItemCount: todoList.length, | |
itemBuilder: (context, index, animation) { | |
return _buildItem(todoList[index], animation, index); | |
}, | |
), | |
), | |
], | |
), | |
); | |
} | |
@override | |
void dispose() { | |
myController.dispose(); | |
super.dispose(); | |
} | |
Widget _buildItem(TodoTask todoTask, Animation animation, num index) { | |
return SizeTransition( | |
sizeFactor: animation, | |
child: _createCard(todoTask, index), | |
); | |
} | |
Widget _createCard(TodoTask todoTask, num index) { | |
return Card( | |
child: ListTile( | |
leading: InkWell( | |
splashColor: Colors.greenAccent, | |
borderRadius: BorderRadius.circular(45.0), | |
onTap: () { | |
todoTask.checkIn(); | |
_removeItems(todoTask, index); | |
_saveToLocalStrage(); | |
}, | |
child: todoTask.isDone | |
? Icon( | |
Icons.check_circle, | |
color: Colors.greenAccent, | |
) | |
: Icon(Icons.check_circle), | |
), | |
title: Text(todoTask._title), | |
), | |
); | |
} | |
void _saveToLocalStrage() { | |
List<String> titleList = []; | |
todoList.forEach((f) => titleList.add(f._title)); | |
strage.save(titleList); | |
} | |
void _addItems(String title) { | |
todoList.insert(0, TodoTask.initialize(title)); | |
_listKey.currentState.insertItem(0); | |
} | |
void _removeItems(TodoTask todoTask, num index) { | |
TodoTask removeItem = todoList.removeAt(index); | |
AnimatedListRemovedItemBuilder builder = (context, animation) { | |
return _buildItem(removeItem, animation, index); | |
}; | |
_listKey.currentState.removeItem(index, builder); | |
} | |
} | |
class TodoTask { | |
String _title; | |
bool _isDone; | |
TodoTask.initialize(this._title) { | |
_isDone = false; | |
} | |
String get title => _title; | |
bool get isDone => _isDone; | |
void checkIn() { | |
_isDone = true; | |
} | |
} | |
class WebStrage { | |
final Storage _storage = window.localStorage; | |
void save(List<String> titleList) { | |
_storage['todo'] = titleList.join(','); | |
} | |
String getTitleList() => _storage['todo']; | |
void invalidate() { | |
_storage.remove('todo'); | |
} | |
} |
解説
使ったのはAnimatedListとWebStorageです。アプリならSharedPreferencesを使うところですが、WebなのでWebStorageを使うことができました。使うにはdart:htmlをインポートする必要があります。
おわりに
Flutter for Webで簡単なToDo リストを作成しました🎉
Flutterのお作法で悩みながら楽しく作れたと思います。Widgetツリーを意識しながら構造的に書くことは勉強になりました。
課題として、
- ネットワークの要素を取り入れる(Firebaseなどと通信する)
- PWAのように使えるのか調査する(hi fiveみたいにキャッシュにためたあと通信する機構など)
- オリジナルのデザインをつかう
があげられそうです。
余談
最初にFlutter for Webを書き始めたときは12月始めで、そのときはdevチャンネルでした。
なので12日のFlutter Interactでbetaチャンネルに移行したときは飲み会で一芸を楽しみにしてる感じの声が出ました。
Flutterにsearch.array().
🙌Web support for Flutter goes beta!— Flutter (@FlutterDev) December 11, 2019
It’s important to us that you can design and build what you want, and know that with Flutter it will work beautifully... anywhere. #FlutterInteract
Learn more about web support in beta → https://t.co/Urz6kX8Cye pic.twitter.com/EdP1M4ff11
0 件のコメント:
コメントを投稿