【Flutter】firebaseへのデプロイ

### build
firebaseへデプロイする前に、ファイルをcompileします
$ flutter build web

Fireforceのhosting機能を有効化
プロジェクトのcurrent directoryで
$ sudo npm install -g firebase-tools –force
$ firebase login
プロジェクトの初期化
$ firebase init
=> publicのdirectoryを ./build/web/ で設定する

firebase
$ firebase deploy

webアプリをbuildする
$ flutter build web
$ firebase deploy

firebase hostingを無効化する
$ firebase hosting:disable

OKKKKKKKKKKKKKKKK

【flutter】session_storageでセッション管理

Flutterでsession_storageのパッケージを利用します。
https://pub.dev/packages/session_storage

pubspec.yaml

dependencies:
  session_storage:
  form_field_validator:
  flutter:
    sdk: flutter

main.dart

import 'package:session_storage/session_storage.dart';
// 省略
            onTap: (int value) {
              if (value == 1)
                SessionStorage()..addAll({'language': 'english'});
                Navigator.push(
                  context,
                  MaterialPageRoute(builder:
                      (context)=> SecondScreen()),
                );
            }
// 省略

        body: Center(
          child: Container (
            child: Text(
                'Here: ${session['language']}',
                style: const TextStyle(fontSize:32.0)),
          ),
        ),

セッションとDBが使えるとかなり幅が広がる

【flutter】firestoreでフィールドの値があるかDartで判定

firestoreにフィールドの値が挿入されているか確認し、値の有無によってflutter側でリダイレクト処理を行う
=> 単純にif nullで処理できる

    final snapshot = await firestore.collection('mydata').doc('12345678').get();
    final Map<String, dynamic>? mydata = snapshot.data();
    if (mydata?['hoge']  != null) {
      msg = '値があります';
    } else {
      msg = '値がありません';
    }

これにアラートメッセージを追加する

    if (mydata?['hoge']  != null) {
      msg = '値があります';
    } else {
      showDialog(
        context: context,
        builder: (BuildContext context) => AlertDialog(
          title: Text("値がありません"),
          content: Text("値がありません"),
        )
      );

エリアを指定してアラート文を表示が一般的かもしれないが、アラートダイアログなどを一発で実装できるのもFlutterの良いところですね。

【flutter】firestoreから1件取り出す

docでuidを指定した後に、mapで取得する。その際に、Mapではなく、Map?とする。また、mapは mydata[‘name’] とするとエラーになるので、mydata?[‘name’]とする。

    String? msg = '';
    FirebaseFirestore firestore = FirebaseFirestore.instance;
    final snapshot = await firestore.collection('mydata').doc('12345678').get();
    final Map<String, dynamic>? mydata = snapshot.data();
    msg = mydata?['name'];
    _controller.text = msg!;

firestoreではなく、flutterの中でmap型を指定する場合は以下のように書いてOK

    final Map<String, String> frameworks = {
      'Flutter' : 'Dart',
      'Rails' : 'Ruby',
    };

コレクションを複数指定する場合とは書き方が大分異なる

    var msg = '';
    FirebaseFirestore firestore = FirebaseFirestore.instance;
    final snapshot = await firestore.collection('mydata').orderBy('name', descending: false).get();
    snapshot.docChanges.forEach((element) {
      final name = element.doc.get('name');
      final mail = element.doc.get('mail');
      final age = element.doc.get('age');
      msg += "${name} (${age}) <${mail}>\n";
    });
    _controller.text = msg;

【flutter】firestoreを更新する方法

uidを指定して、setを実行するとcollection自体の値が全て更新されてしまう。

    var msg = _controller.text;
    final data = {
      'address': msg,
    };
    FirebaseFirestore firestore = FirebaseFirestore.instance;
    final snapshot = await firestore.collection('mydata').doc('12345678').set(data);

コレクション自体を残す場合はsetではなく、updateを使用する

    var msg = _controller.text;
    final data = {
      'address': msg,
    };
    FirebaseFirestore firestore = FirebaseFirestore.instance;
    final snapshot = await firestore.collection('mydata').doc('12345678').update(data);

【Flutter】別ページへのデータの引き渡し

引数に値を入れる

  static var _prev = 'ページ1';
  
  void buttonPressed() {
    setState(() {
      // Navigator.pushNamed(context, '/complete');
      Navigator.push(context, MaterialPageRoute(builder: (context)=>CompleteScreen(_prev)),);
    });
  }

複数画面の入力フォームなどはどのような設計にするかは要検討か

【Flutter】Scaffoldの中でFormを使いたい

https://pub.dev/packages/form_field_validator

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
      title: Text(widget.title),
      ),
      body: Form (
          key: _formKey,

Widget build(BuildContext context)は複数記述できないので、body: Formと書く

そうすると、TextFormFieldが使えるようになる

            Padding(
              padding: EdgeInsets.all(10.0),
              child: TextFormField(
                controller: _id_controller,
                style: TextStyle(
                    fontSize: 28.0,
                    color: const Color(0xffFF0000),
                    fontWeight: FontWeight.w400,
                    fontFamily:"Roboto"),
                validator: idValidator,
              ),
            ),

【Flutter】バリデーション その2

class MyCustomFormState extends State<MyCustomForm>{
  final _formKey = GlobalKey<FormState>();
  final textValidator = MultiValidator([
    RequiredValidator(errorText: '入力必須の項目です。'),
    MinLengthValidator(8, errorText: '8文字以上で入力してください。'),
  ]);

  @override
  Widget build(BuildContext context){
    return Form(
      key: _formKey,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          TextFormField(
                  validator: textValidator,
          ),
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 16.0),
            child: ElevatedButton(
              onPressed:() {
                if(_formKey.currentState!.validate()){
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('送信完了')),
                  );
                }
              },
              child: const Text('送信'),
            )
          )
        ]
      )
    );
  }
}

【Flutter】バリデーション

class MyCustomFormState extends State<MyCustomForm>{
  final _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context){
    return Form(
      key: _formKey,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          TextFormField(
            validator: (value){
              if(value == null || value.isEmpty){
                return 'テキストを入力してください';
              }
            },
          ),
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 16.0),
            child: ElevatedButton(
              onPressed:() {
                if(_formKey.currentState!.validate()){
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('送信完了')),
                  );
                }
              },
              child: const Text('送信'),
            )
          )
        ]
      )
    );
  }
}

フォームを一意に認識するためのキー

final _formKey = GlobalKey<FormState>();
Form(
     key: _formKey,
     )

入力がない場合テキストを返す

if (_formKey.currentState!.validate()) {
                 ScaffoldMessenger.of(context).showSnackBar(
                   const SnackBar(content: Text('送信完了')),
                 );
               }

【Flutter】widgetのコンポーネント化

demoのソースコード

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

classにする

class BodyText extends StatelessWidget {
  final int counter;
  const BodyText({Key? key, required this.counter}):super(key: key);

  @override build(BuildContext context){
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          const Text(
            'You have pushed the button this many times:',
          ),
          Text(
            '$counter',
            style: Theme.of(context).textTheme.headlineMedium,
          ),
        ],
      ),
    );
  }
}

呼び出す
body: BodyText(counter: _counter),

なるほど、これをbottomNavigationBarでやりたいが、setStateがあるとclassで共通化できないのか…