Open18

Flutter を勉強する

yuyu

Flutter のインストール

https://www.flutter-study.dev/getting-started/install

  • Flutter をインストール(公式の手順をみるのが良さそう)
  • flutter doctor で設定状況が確認できる

↑ Android と iOS(Xcode) だけダメっぽかったので設定をする

iOS (Xcode)

Android

エラーはこれ

Android toolchain - develop for Android devices (Android SDK version 29.0.3)
    ✗ cmdline-tools component is missing
      Run `path/to/sdkmanager --install "cmdline-tools;latest"`
      See https://developer.android.com/studio/command-line for more details.
    ✗ Android license status unknown.
      Run `flutter doctor --android-licenses` to accept the SDK licenses.
      See https://flutter.dev/docs/get-started/install/macos#android-setup for more details.
  • 過去に Android Studio は入れたことあったので起動してみた
  • command-line tools は Android Studio を開いて More ActionSDK ManagerSDK ToolsAndroid SDK Command-line Tools(latest)を選択で一つは解決
  • もう一つのエラーは flutter doctor --android-licenses を叩いて全部許可すればOK
  • Unable to find bundled Java version. は以下の記事を参考にしたらできた
Doctor summary (to see all details, run flutter doctor -v):
[] Flutter (Channel stable, 3.7.1, on macOS 12.6.3 21G419 darwin-x64, locale ja-JP)
[] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
[] Xcode - develop for iOS and macOS (Xcode 14.2)
[] Chrome - develop for the web
[] Android Studio (version 2022.1)
[] VS Code (version 1.74.2)
[] Connected device (2 available)
[] HTTP Host Availability

• No issues found!
yuyu

アプリ起動

以下で作成

flutter create [myapp]

Null案件を導入

以下のコマンドで ? での null 表現ができるようになる

dart migrate --apply-changes

アプリをコマンドから実行

VSCodeのステータスバーに対象のデバイスがでるのでそれで選ぶ

flutter devices # 起動しているデバイスを確認
flutter run --device-id chrome # chromeを起動 (F5キーでも可)

↓ ローカルホストが立ち上がった

http://localhost:63215/#/

q キーで停止

https://www.flutter-study.dev/getting-started/run-app

yuyu

Widgetとは

Flutter の UI を構築しているパーツを Widget と呼ぶ
Widget はツリー状になっている(HTMLみたいな感じかな)

テキストを表示

Text('Default')

スタイル

色々なスタイルの指定方法

Text('Bold', style: TextStyle(fontWeight: FontWeight.bold))
Text('Italic', style: TextStyle(fontStyle: FontStyle.italic))
Text('fontSize = 36', style: TextStyle(fontSize: 36))
Text('Red', style: TextStyle(color: Colors.red))

位置

Text('TextAlign.right', textAlign: TextAlign.right)

https://www.flutter-study.dev/widgets/text-widget

Container

要素を囲って余白とかサイズとかを決める要素

線を引くには decoration を使う

Container(
  decoration: BoxDecoration(
    border: Border.all(color: Colors.blue, width: 2),
    borderRadius: BorderRadius.circular(8),
   ),
   child: Text('border')
)

余白、色とかはcssみたいな感じ
padding とかで方向ごとに決めるときは EdgeInsets.only() を使えそう

Container(
  color: Colors.blue,
  width: 200,
  height: 50,
  padding: EdgeInsets.only(left: 20, top: 8, bottom: 8, right: 20),
  margin: EdgeInsets.all(8),
  child: Text('container')
)

背景画像はこんな感じ
ローカルの画像を指定するときは AssetImage('image_path') とのこと
画像をかくだいするには BoxFit を使う(contain, cover, fitWidth, fitHeight, scaleDown, none

Container(
  width: 400,
  height: 400,
  decoration: BoxDecoration(
    image: DecorationImage(
      image: NetworkImage('https://source.unsplash.com/960x540/?city,sky')
      fit: BoxFit.fill,
    )
  ),
)

https://www.flutter-study.dev/widgets/container-widget

縦、横並び

縦並びは Column、横並びは Row を使う

Column(
  mainAxisAlignment: MainAxisAlignment.center, // 
  children: <Widget>[
    Text('fisrt'),
    Text('seconds'),
  ]
),
Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    Text('fisrt'),
    Text('seconds'),
  ]
),

https://www.flutter-study.dev/widgets/column-row-widget

ボタン

  • TextButton テキストボタン
    • onPressed にクリック時の処理を書く
    • 実行したくない場合は null をセットする
  • OutlinedButton 枠線ありボタン
  • ElevatedButton 影ありボタン
  • IconButton アイコンボタン
    • TextButton.icon 他のボタンでアイコンをつけるとき
  • Scaffold + FloatingActionButton フローティングのボタンを作るとき
    • Scaffold はUIのベースとなるWidget
    • 白紙のキャンバスみたいなイメージ
TextButton(
  onPressed: () {},
  style: TextButton.styleFrom(primary: Colors.red),
  child: const Text('影なしボタン')
),
OutlinedButton(
  onPressed: () {},
  style: OutlinedButton.styleFrom(primary: Colors.red),
  child: const Text('枠線ボタン')
),
ElevatedButton(
  onPressed: null,
  style: ElevatedButton.styleFrom(
      primary: Colors.red, elevation: 16),
  child: const Text('影ありボタン')
),
IconButton(
  onPressed: () {},
  icon: const Icon(Icons.thumb_up),
  color: Colors.pink,
  iconSize: 64
),
ElevatedButton.icon(
  onPressed: null,
  style: ElevatedButton.styleFrom(
      primary: Colors.red, elevation: 16),
  icon: const Icon(Icons.thumb_up_sharp),
  label: const Text('影ありボタン + icon')
),

https://www.flutter-study.dev/widgets/button-widget

リスト

  • ListView の children に要素を並べるとスクロール可能なリストが作れる
  • ListView.builder を使うと中身を変数で定義してそれをもとにリストを作ることができる
  • ListTile, Card を使って見た目をいい感じに作ることもできる
final listItems = [
  'apple',
  'orange',
  'banana',
];

ListView.builder(
    itemCount: listItems.length,
    itemBuilder: (context, index) {
      return Text(listItems[index]));
    }),
)
Card(
  child: ListTile(
    title: Text('list title'), subtitle: Text('sub title')
  ),
),

https://www.flutter-study.dev/widgets/list-view-widget

AppBar

よくあるヘッダーのバー
AppBar を使う

Scaffold(
  appBar: AppBar(
    // 左側のアイコン
    leading: Icon(Icons.arrow_back),
    // タイトルテキスト
    title: Text('Hello'),
    // 右側のアイコン一覧
    actions: <Widget>[
      IconButton(
        onPressed: () {},
        icon: Icon(Icons.favorite),
      ),
      IconButton(
        onPressed: () {},
        icon: Icon(Icons.more_vert),
      ),
    ],
  ),
)

https://www.flutter-study.dev/widgets/app-bar-widget

yuyu

NetworkImage を使って外部リソースを読み込もうとしたらcorsエラーが出た

Flutter にはレンダー方式が2つあって、自動では canvaskitレンダー という方式らしい
canavs で外部リソースを読み込むと確かにエラーになるのでそれのせいっぽい

HTMLレンダー と言う方を使えば良いみたい
↓起動時に以下のコマンドを叩くと表示された

flutter run -d chrome --web-renderer html

本番用のビルドも変わる

flutter build web --web-renderer html

https://zenn.dev/neruneru/articles/b9dcb800bd9f11

yuyu

パッケージのインストールのやり方

  1. pub.dev でパッケージを探す
  2. flutter pub add [パッケージ名] でパッケージをインストール。もしくはpubspec.yamldependencies にパッケージ情報を追記してから flutter pub get コマンドでパッケージをインストールする
  3. 使いたいところで import して使う

参考
https://qiita.com/akeome/items/0a6ebf3af402fdf62c79

yuyu

npm scripts みたいによく使うコマンドを定義したい

これでできるみたい
https://pub.dev/packages/rps

グローバルにインストール

dart pub global activate rps

パスが通ってないので .zshrc に以下を追加

export PATH="$PATH":"$HOME/.pub-cache/bin"

pubspec.yamlscripts を追加

scripts:
  run: "flutter run -d chrome --web-renderer html"

実行

rps run

動いた!

yuyu

状態を持った Widget

  • StatefulWidget を継承した Widget
  • State を継承したデータ
// StatefulWidgetを継承するとStateを扱える
// このWidgetを表示すると、Stateを元にUIが作成される
class MyWidget extends StatefulWidget {
  // 使用するStateを指定
  
  _MyWidgetState createState() => _MyWidgetState();
}

// Stateを継承して使う
class _MyWidgetState extends State<MyWidget> {
  // データを宣言
  int count = 0;

  // データを元にWidgetを作る
  
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Text(count.toString()),
        RaisedButton(
          onPressed: () {
            // データを更新する時は setState を呼ぶ
            setState(() {
              // データを更新
              count = count + 1;
            });
          },
          child: Text('カウントアップ'),
        ),
      ],
    );
  }
}

https://www.flutter-study.dev/widgets/state-widget

yuyu

TODOを作る

https://www.flutter-study.dev/todo-app/about-todo-app

ページを作る

一旦、特に何でもないページを作成

import 'package:flutter/material.dart';

void main() {
  // 最初に表示するWidged
  runApp(MyTodoApp());
}

// アプリ自体
class MyTodoApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'My Todo App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: TodoListPage(),
    );
  }
}

// トップページに表示するページ
class TodoListPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Text('リスト一画面')
      ),
    );
  }
}

ページ遷移を作る

  • もう一つページを作って Navigator.on(context).push()MaterialPageRoute() を使って遷移させる
  • ページを戻るだけなら Navigator.of(context).pop() で良いみたい
/// リスト一覧ページ
class TodoListPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: const Center(
        child: Text('リスト一画面')
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // "push" で新規画面に遷移
          Navigator.of(context).push(
            MaterialPageRoute(builder: (context) {
              return TodoAddPage();
            }),
          );
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

/// 追加ページ
class TodoAddPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: TextButton(
        onPressed: () {
          Navigator.of(context).pop();
        },
        child: const Text('リスト追加画面(クリックで戻る)')
      )
    );
  }
}

リスト表示を作る

  • ListView, Card, ListTile を使って作る
/// リスト一覧ページ
class TodoListPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('リスト一覧')
      ),
      body: ListView(
        children: const <Widget>[
          Card(
            child: ListTile(
              title: Text('にんじんを買う'),
            )
          ),
          Card(
            child: ListTile(
              title: Text('にんじんを買う'),
            )
          ),
          Card(
            child: ListTile(
              title: Text('にんじんを買う'),
            )
          ),
          Card(
            child: ListTile(
              title: Text('にんじんを買う'),
            )
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // "push" で新規画面に遷移
          Navigator.of(context).push(
            MaterialPageRoute(builder: (context) {
              return TodoAddPage();
            }),
          );
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

タスクを入力するフォームを作る

  • TodoAddPage の継承を StatefulWidget に変更する
  • _TodoAddPageState クラスを作って State<TodoAddPage> を継承する
    • String _text = ''; として状態を作る
    • TextFieldonChanged で値の変更を受け取り、setState() で変数にセット
  • Navigator.of(context).pop(_text); でページを戻る時に値を返す
/// 追加ページ
class TodoAddPage extends StatefulWidget {
  
  _TodoAddPageState createState() => _TodoAddPageState();
}

class _TodoAddPageState extends State<TodoAddPage> {
  String _text = '';

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('リスト追加')
      ),
      body: Container(
        padding: const EdgeInsets.all(64),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(_text, style: const TextStyle(color: Colors.blue)),
            TextField(
              onChanged: (String value) {
                setState(() { 
                  _text = value;
                });
              }
            ),
            const SizedBox(height: 8),
            Container(
              width: double.infinity, // 横いっぱいに広げる
              child: ElevatedButton(
                onPressed: () {
                  Navigator.of(context).pop(_text); // 変更後のテキストを返す
                },
                child: const Text('リスト追加', style: TextStyle(color: Colors.white))
              )
            ),
            const SizedBox(height: 8),
            Container(
              width: double.infinity,
              child: TextButton(
                onPressed: () {},
                child: const Text('キャンセル'),
              )
            )
          ]
        )
      )
    );
  }
}

一覧ページでデータを受け取る

  • onPressedasync にして Navigator.of().push()await にする
  • その引数を変数 newListText に入れることで、TodoAddPage から返ってきた時の値を受け取れる
class TodoListPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('リスト一覧')
      ),
      body: ListView(
        children: const <Widget>[
          Card(
            child: ListTile(
              title: Text('にんじんを買う'),
            )
          ),
          Card(
            child: ListTile(
              title: Text('にんじんを買う'),
            )
          ),
          Card(
            child: ListTile(
              title: Text('にんじんを買う'),
            )
          ),
          Card(
            child: ListTile(
              title: Text('にんじんを買う'),
            )
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          // 返ってきた時の値を受け取る
          final newListText = await Navigator.of(context).push(
            MaterialPageRoute(builder: (context) {
              return TodoAddPage();
            }),
          );
          if (newListText != null) {
            // 
          }
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

受け取ったデータを表示する

  • List<string> todoList = [] と変数を定義する
  • 返ってきた値を受け取ったら追加するようにする setState(() { todoList.add(newListText) });
  • ListView.builder()todoList の中身を表示する
/// リスト一覧ページ
class TodoListPage extends StatefulWidget {
  
 _TodoListPageState createState() => _TodoListPageState(); 
}

class _TodoListPageState extends State<TodoListPage> {
  List<String> todoList = [];
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('リスト一覧')
      ),
      body: ListView.builder(
        itemCount: todoList.length,
        itemBuilder: (context, index) {
          return Card(
            child: ListTile(
              title: Text(todoList[index]),
            )
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          // かえってきた時の値を受け取る
          final newListText = await Navigator.of(context).push(
            MaterialPageRoute(builder: (context) {
              return TodoAddPage();
            }),
          );
          if (newListText != null) {
            setState(() {
              todoList.add(newListText);
            });
          }
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

https://www.flutter-study.dev/todo-app/add-page

yuyu

vim で開発する

VSCode だとなんか重いなーってたけど以下の方法で環境を作って Vim で開発したら快適だった

  • coc を使っているので :CocInstall coc-flutter でインストールする
  • シンタックスハイライトのために以下をインストール(vim-plug)
    • Plug 'dart-lang/dart-vim-plugin'
    • Plug 'thosakwe/vim-flutter'

https://dev.classmethod.jp/articles/cocnvim-adventcalendar-day21/

yuyu

Firebase を使う

プロジェクトを作るのはいつも通り

セットアップ

https://zenn.dev/snova301/books/6df29a230d681f/viewer/532641

firebase 側の公式があるからこれがいいかも...
https://firebase.google.com/docs/flutter/setup?hl=ja&platform=ios

firebase にログイン

firebase login

flutterfire_cli をグローバルで有効化する

dart pub global activate flutterfire_cli

flutterfire_cli の参考はこれ
https://firebase.flutter.dev/docs/overview

あとは以下のコマンドでfirebaseのプロジェクトを選択し、プラットフォームを全部選択し、android/build.gradleの更新を行うかどうかを yes にしてセットアップする
これで lib/firebase_options.dart が作られてそこに firebase のオプションがセットアップされているファイルが作られる...すごい...

flutterfire configure

firebase_core をインストール

flutter pub add firebase_core

main.dart に import して、初期化の処理を入れる

lib/main.dart
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(MyTodoApp());
}

Firebase Authentication

https://zenn.dev/snova301/books/6df29a230d681f/viewer/06a9ce
https://zenn.dev/flutteruniv_dev/articles/710683144ca9cf

Firebase側で Auth の設定をする
今回使いたいのは Google ログイン

Firebase の構成を変えたら以下のコマンドで最新の状態を取ってくるのが良さそう

flutterfire configure

あとは使うパッケージをインストール

flutter pub add firebase_auth google_sign_in sign_in_button
処理はこんな感じ
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:sign_in_button/sign_in_button.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'firebase_options.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(MyTodoApp());
}

/// アプリのベース
class MyTodoApp extends StatelessWidget {
  final String title = 'murmur';

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: title,
      theme: ThemeData(
        primarySwatch: Colors.blue, // テーマーカラーを設定
      ),
      home: TopPage(), // 表示するページを指定
    );
  }
}

class TopPage extends StatefulWidget {
  
  State<TopPage> createState() => _TopPageState();
}

class _TopPageState extends State<TopPage> {
  Future<UserCredential> signInWithGoogle() async {
    // Trigger the authentication flow
    final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();

    // Obtain the auth details from the request
    final GoogleSignInAuthentication? googleAuth = await googleUser?.authentication;

    // Create a new credential
    final credential = GoogleAuthProvider.credential(
      accessToken: googleAuth?.accessToken,
      idToken: googleAuth?.idToken,
    );

    // ログイン後に遷移する
    Navigator.push(context, MaterialPageRoute(builder: (context) => TodoListPage()));

    // Once signed in, return the UserCredential
    return await FirebaseAuth.instance.signInWithCredential(credential);
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('ログイン')
      ),
      body: Center(
        child: 
          Container(
            width: 200,
            height: 30,
            child: SignInButton(Buttons.google, onPressed: () {
              signInWithGoogle();
            }),
          ),
        )
    );
  }
}

/// リスト一覧ページ
class TodoListPage extends StatefulWidget {
  
 _TodoListPageState createState() => _TodoListPageState(); 
}

class _TodoListPageState extends State<TodoListPage> {
  List<String> todoList = [];
  final _auth = FirebaseAuth.instance;
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('リスト一覧'),
        actions: [
          IconButton(
            onPressed: () async {
              await _auth.signOut();
              Navigator.pop(context);
            },
            icon: const Icon(Icons.arrow_back)
          )
        ]
      ),
      body: ListView.builder(
        itemCount: todoList.length,
        itemBuilder: (context, index) {
          return Card(
            child: ListTile(
              title: Text(todoList[index]),
            )
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          // かえってきた時の値を受け取る
          final newListText = await Navigator.of(context).push(
            MaterialPageRoute(builder: (context) {
              return TodoAddPage();
            }),
          );
          if (newListText != null) {
            setState(() {
              todoList.add(newListText);
            });
          }
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

web で動かしてる時に以下のエラーが出た

"ClientID not set. Either set it on a <meta name="google-signin-client_id" content="CLIENT_ID" /> tag, or pass clientId when calling init()"

↓この手順でクライアントIDとかを調べて入れたら動いた。アプリはわからん...
https://qiita.com/masatomix/items/791699b3de3486618dd7

試せてないもの

  • ログインしてなかったらトップページに戻る
  • アプリでのログイン
yuyu

iOS のシミュレーターで実行する方法

以下のコマンドでシミュレーターを起動する

open -a Simulator

以下のコマンドでデバイスリストを表示する。シミュレーターが起動していればそのデバイスも表示される。

flutter devices

その中から選んで以下のコマンドを実行するとシミュレーター上にアプリがデプロイされる(結構時間かかるっぽい)

flutter run -d BE1D1287-B81A-4BDD-8B62-5486E2018854 #←これはきっと毎回違う乱数

https://dev.classmethod.jp/articles/master-the-flutter-devicesflutter-run-command-in-flutter-app-development/

yuyu

iOSのデバッグはどうしてるんだろう
シミュレーターめっちゃ重いけど...

実機にもどうやって入れるのか

yuyu

起動時のポート番号を固定する

デフォルトでは毎回変わってしまっているっぽい
Fireabse を使う際に指定している ClientId がURLを指定しないといけないのでポートが変わると使えなくなってしまうので固定する

起動時に --web-port というオプションで設定できる
以下のような形で落ち着いた

flutter run -d chrome --web-renderer html --web-port 5000
yuyu

Firestore

必要なライブラリを落としてくる

flutter pub add cloud_firestore

インポートして、こんな感じで使う
使うAPIはJSとかとほぼ変わらずかけそう

import 'package:cloud_firestore/cloud_firestore.dart';

final db = FirebaseFirestore.instance;
final userID = FirebaseAuth.instance.currentUser?.uid;
List todoList = [];

try {
  await db.collection('users').doc(userID).get().then((event) {
    if (event.exists) {
      todoList = data;
    }
  });
} catch(e) {
  print('Error: $e');
}

参考サイトのほぼそのままだけど使いやすい感じにクラス化して使った

クラスを作った
class FirestoreService {
  final db = FirebaseFirestore.instance;
  final userID = FirebaseAuth.instance.currentUser?.uid ?? '';

  Future getUser() async {
    try {
      return db.collection('users').doc(userID).get().then((event) async {
        print(userID);
        // ユーザーデータがないときはデータを作成する
        if (!event.exists && userID is String) {
          await db.collection('users').doc(userID).set({
            'todo': [
              {
                'body': 'ようこそ!',
                'timestamp': Timestamp.now()
              }
            ]
          });
        }
      });
    } catch(e) {
      print('Error: $e');
    }
  }

  void get(WidgetRef ref) async {
    try {
      await db.collection('users').doc(userID).get().then((event) {
        final data = event.get('todo');
        if (event.exists) {
          ref.read(todoProvider.notifier).state = data;
        } else {
          ref.read(todoProvider.notifier).state = [];
        }
      });
    } catch(e) {
      print('Error: $e');
    }
  }

  void add(WidgetRef ref, String value) async {
    final timestamp = Timestamp.now();
    final oldList = ref.read(todoProvider);
    final Map<String, List<dynamic>> todoMap = {
      'todo' : [
        ...oldList,
        {
          'body': value,
          'timestamp': timestamp,
        },
      ]
    };
    try {
      db.collection('users').doc(userID).set(todoMap);
    } catch(e) {
      print('Error: $e');
    }
  }

  void delete() async {
    try {
      db.collection('users').doc(userID).delete().then((doc) => null);
    } catch(e) {
      print('Error: $e');
    }
  }
}
yuyu

Provider

↑の例でも使ってるけど、アプリ全体でデータを共有するために使うやつっぽい(全然わかってない)

こんな感じで todoProvider を作って runApp() の時にアプリを囲む

import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(ProviderScope(child: MyTodoApp()));
}

final todoProvider = StateNotifierProvider.autoDispose<Todo, List<dynamic>>((ref) {
  return Todo();
});

class Todo extends StateNotifier<List<dynamic>> {
  Todo() : super([]);
}

使いたいページを CunsumerStatefulWidgetConsumerState で作ると、ref が使えるようになるので、その ref を使ってprovider の変化とかを検知して使う

class TodoListPage extends ConsumerStatefulWidget {
  
 _TodoListPageState createState() => _TodoListPageState(); 
}

class _TodoListPageState extends ConsumerState<TodoListPage> {
  
  void initState() {
    super.initState();
    init();
  }

  List<String> todoList = [];

  void init () async {
    FirestoreService().get(ref);
  }

  
  Widget build(BuildContext context) {
    final list = ref.watch(todoProvider);
    return Scaffold(
      body: 
        ListView.builder(
          itemCount: list.length,
          itemBuilder: (context, index) {
            return Card(
              child: ListTile(
                title: Text(list[list.length]['body']),
                subtitle: Text(list[list.length]['timestamp'].toString()),
              )
            );
          },
        ),
    );
}

https://riverpod.dev/ja/docs/concepts/reading