はじめまして! 今年の6月からエキサイトのアプリエンジニアとして内定者インターンをしております、藤崎裕樹( @ex_naty44 )と申します!
今回はインターンのトレーニングでMemoアプリを作ってみたので、 その過程をまとめてみようと思います。
エキサイトでインターンをする前は他社でweb開発のインターンをしていましたが、モバイルアプリに関する知識はほとんどない状態でした。
そんな僕でもこのメモアプリを作ることが出来たので、今回の記事が初学者の方の参考になれば幸いです。
よろしくお願いします!
目次
- メモアプリを作成した理由
- 完成品
- 要件
- Riverpodでリプレイス
- 振り返り
メモアプリを作った理由
Flutterを触ったことがなかったので、まずはUIと基本的な機能についての理解を深めることが主な目的でした。 ゆくゆくはこのエキサイトに入ってくるアプリエンジニアのための参考にしていただければと思い、完成させてみようと考えました。
完成品
UIとしてはこんな感じです。 右下の+ボタンを押すとメモが投稿出来ます。 右上の選択を押すと、チェックボックスが表示され、 選択したメモを−ボタンで削除できます。検索フィールドからはメモを検索することができます。
完成品の全機能については以下のリンクから動画で確認できます。
メモアプリ作って実機でデバッグした pic.twitter.com/oyQbSes1Gh
— ねぃてぃ@flutter (@naty_flutter) 2022年7月22日
要件
- 投稿機能
- 一覧表示
- 削除機能
- 投稿日時の保存
- ローカルストレージでの永続化
- 編集機能
- 編集日時の保存
- 編集日時でのソート
- 投稿日時でのソート
- 選択したメモの一括削除
- 検索機能
これらの機能初めはStatefulwidgetで実装しました。 その後、今後関わりうるプロジェクトでRiverpodが採用されるとのことで、 メモアプリでも導入してみようと思いました。
Riverpodでリプレイス
Riverpodは状態管理パッケージです。 Riverpodではrefオブジェクトを使ってグローバルに定義した様々な種類のProviderを利用することが可能になります。
refオブジェクトを用いるために、 StatefulwidgetとStateをConsumerStateful widgetとConsumerStateに切り替えて、 ProviderとStateNotifireProviderを用いてリプレイスしました(例ではStateProvideを使用しています)。
Stateの変更を検知してUIを再描画する際、今までsetStateで囲んでいましたが、 Providerをref.watchで監視し、変更を検知できるようにしました。 Providerが変更を感知すると、関わっているUIを再描画してくれます。
class MyHomePage extends StatefulWidget { const MyHomePage({Key? key, required this.title}) : super(key: key); final String title; @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { // テキストフィールドの値を受け取る final _myController = TextEditingController(); // データ格納用リスト final List<String> _items = []; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Memo App'), ), body: Column( children: [ // テキストフィールド Padding( padding: const EdgeInsets.all(16.0), child: TextField( controller: _myController, ), ), // リストビュー Expanded( child: ListView.builder( itemCount: _items.length, itemBuilder: (_, int index) { return Card( child: ListTile( title: Text(_items[index]), ), ); }, ), ), ], ), floatingActionButton: FloatingActionButton( onPressed: () { setState(() { _items.add(_myController.text); }); // テキストフィールドの値をクリア _myController.clear(); }, child: const Icon(Icons.add), ), ); } }
上記を以下のように書き変えました。
// ConsumerStatefulWidgetに変更 class MyHomePage extends ConsumerStatefulWidget { const MyHomePage({Key? key, required this.title}) : super(key: key); final String title; @override ConsumerState<MyHomePage> createState() => _MyHomePageState(); } // StateProviderを定義 final memosProvider = StateProvider.autoDispose((ref) => []); // ConsumerStateに変更 class _MyHomePageState extends ConsumerState<MyHomePage> { final _myController = TextEditingController(); @override Widget build(BuildContext context) { // Providerを監視 final memoList = ref.watch(memosProvider); return Scaffold( appBar: AppBar( title: const Text('Memo App'), ), body: Column( children: [ // テキストフィールド Padding( padding: const EdgeInsets.all(16.0), child: TextField( controller: _myController, ), ), Expanded( child: ListView.builder( itemCount: memoList.length, itemBuilder: (_, int index) { return Card( child: ListTile( title: Text(memoList[index]), ), ); }, ), ), ], ), floatingActionButton: FloatingActionButton( onPressed: () { memoList.add(_myController.text); // memoListにすると参照渡しになり、変更が検知されない。値渡しにして変更を検知させる。 ref.read(memosProvider.notifier).state = [...memoList]; _myController.clear(); }, child: const Icon(Icons.add), ), ); } }
Riverpodを用いるとグローバルにProviderを利用できるので、大規模なアプリでは非常に便利なので、 これからも色々なプロジェクトでの導入が増えていきそうです!
振り返り
最後に、今回のトレーニングでRiverpodの利点と書き方を学ぶことができました! ですが、まだProviderを全て使い切れた訳ではありません。 このメモアプリ自体もUI、アーキテクチャ等の改善の余地があるので、 今後もアップデートしていきたいと思います!
最後に
エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しております。長期インターンも歓迎していますので、興味があれば連絡いただければと思います。今回の記事を読んで少しでも興味が湧きましたら是非応募お願い致します!!
就業型インターンの募集情報です! www.wantedly.com
募集職種一覧はこちらになります!カジュアル面談からでも構いません。 www.wantedly.com