【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で共通化できないのか…

【Flutter】ゲーム画面の端との衝突

import 'package:flutter/material.dart';
import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flutter/services.dart';
import 'package:flame/components.dart';
import 'package:flame/collisions.dart';
import 'dart:math';

void main() async {
  runApp(new MyApp());
}
class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Generated App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
        primaryColor: const Color(0xFF2196f3),
        canvasColor: const Color(0xFFfafafa),
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('My App'),
      ),
        body: GameWidget(game: SampleGame())
    );
  }
}

class SampleGame extends FlameGame with HasCollisionDetection, HasKeyboardHandlerComponents, HasTappableComponents {

  late final List<MySprite> _splayers;

  @override
  Color backgroundColor() => const Color(0xffCCCCFF);

  @override
  Future<void> onLoad() async {
    await super.onLoad();
    add(ScreenHitbox());

    _splayers = <MySprite>[];
    for(var i = 0; i < 10; i++) {
      await add(MySprite());
    }
  }
}

class MySprite extends CircleComponent with CollisionCallbacks, HasGameRef<SampleGame> {
  var _size = 50.0;
  late Vector2 _delta;
  late Color _color;

  @override
  Future<void> onLoad() async {
    await super.onLoad();
    final _rnd = Random();
    _color = Color.fromARGB(255,
      _rnd.nextInt(256),
      _rnd.nextInt(256),
      _rnd.nextInt(256));
    size = Vector2(_size, _size);
    final _loc = Vector2.random();
    _loc.x *= gameRef.canvasSize.x;
    _loc.y *= gameRef.canvasSize.y;
    position = _loc;
    _delta = Vector2.random() * 3.0;
    setColor(_color);
    CircleHitbox hitbox = CircleHitbox();
    add(hitbox);
  }

  @override
  void render(Canvas canvas) {
    super.render(canvas);
  }

  @override
  void update(double delta){
    super.update(delta);
    position += _delta * delta * 100;
  }

  @override
  void onCollision(Set<Vector2> points, PositionComponent other){
    if(other is ScreenHitbox) {
      if (position.x <= 0) {
        _delta.x = _delta.x.abs();
      } if (position.x >= other.width - _size) {
        _delta.x = -_delta.abs();
      }
      if (position.y <= 0) {
        _delta.y = _delta.y.abs();
      } if (position.y >= other.height - _size) {
        _delta.y = -_delta.y.abs();
      }
    } else {
      setColor(Colors.yellow);
    }
  }

  @override
  void onCollisionEnd(PositionComponent other) {
    setColor(_color);
  }
}
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flame/experimental.dart';
import 'package:flame/extensions.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  runApp(new MyApp());
}
class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Generated App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
        primaryColor: const Color(0xFF2196f3),
        canvasColor: const Color(0xFFfafafa),
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('My App'),
      ),
        body: GameWidget(game: SampleGame())
    );
  }
}

class SampleGame extends FlameGame with HasCollisionDetection, HasKeyboardHandlerComponents {

  late final List<BlockSprite> _blocks;
  bool _flag = true;
  final List<Color> colors = [
    Colors.red,
    Colors.orange,
    Colors.yellow,
    Colors.green,
    Colors.cyan,
    Colors.blue,
    Colors.purple,
  ];

  @override
  Color backgroundColor() => const Color(0xffCCCCFF);

  @override
  Future<void> onLoad() async {
    await super.onLoad();
    add(ScreenHitbox());
    final _size = Vector2((size.x - 10) / 5 - 10, 25);
    _blocks = <BlockSprite>[];
    for(var i = 0; i < 7; i++){
      for(var j = 0; j < 5; j++){
        var sp = await BlockSprite(_size,
          Vector2(j * (_size.x + 10) + 10, 30 * i + 50),
          colors[i]);
        _blocks.add(sp);
        add(sp);
      }
    }
    await(await BallSprite());
    await add(BarSprite());
  }

  void gameOver(){
    _flag = false;
    add(TextSprite("FINISHED."));
  }
}

class BallSprite extends CircleComponent with CollisionCallbacks, HasGameRef<SampleGame> {
  var _size = 25.0;
  late Vector2 _delta;

  @override
  Future<void> onLoad() async {
    await super.onLoad();
    size = Vector2(_size, _size);
    final _loc = Vector2(
      gameRef.canvasSize.x / 2 - 12,
      gameRef.canvasSize.y - 100);
    position = _loc;
    _delta = Vector2(1, -1);
    setColor(Colors.white);
    add(CircleHitbox());
  }

  @override
  void update(double delta) {
    if (!gameRef._flag) {return;}
    super.update(delta);
    position += _delta * delta * 100;
  }

  @override
  void onCollision(Set<Vector2> points,
      PositionComponent other) {
    if (!gameRef._flag) { return; }
    if (other is ScreenHitbox) {
      if(position.x <= 0) {
        _delta.x = _delta.x.abs();
      } if (position.x > other.width - _size) {
        _delta.x = -_delta.x.abs();
      }
      if(position.y <= 0) {
        _delta.y = _delta.y.abs();
      } if (position.y >= other.height - _size) {
        _delta.y = -_delta.y.abs();
        gameRef.gameOver();
      }
    }
    if(other is BlockSprite) {
      if (position.x + size.x / 2 < other.position.x) {
        _delta.x = _delta.x.abs();
      } if (position.x + size.x / 2 > other.position.x + other.width) {
        _delta.x = _delta.x.abs();
      }
      if (position.y + size.y / 2  < other.position.y) {
        _delta.y = -_delta.y.abs();
      }
      if(position.y + size.y / 2 > other.position.y + other.height) {
        _delta.y = _delta.y.abs();
      }
    }
    if (other is BarSprite) {
      if (position.x + size.x / 2 < other.position.x) {
        _delta.x = _delta.x.abs();
      } if (position.x + size.x / 2 > other.position.x + other.width) {
        _delta.x = _delta.x.abs();
      }
      if (position.y + size.y / 2 < other.position.y) {
        _delta.y = -_delta.y.abs();
        final d = ((other.position.x + other.width / 2) - (position.x + size.x / 2));
        final p = (other.width / 5).floor();
        _delta.x -= (d/p);
        _delta.x = _delta.x > 3 ? 3 : _delta.x;
        _delta.y = -(5/ (gameRef._blocks.length/5).ceil() + 1);
      }
      if (position.y + size.y / 2 > other.position.y + other.height) {
        _delta.y = _delta.y.abs();
    }
    }
  }
}

class BlockSprite extends PositionComponent with CollisionCallbacks, HasGameRef<SampleGame> {
  late Vector2 _position;
  late Vector2 _size;
  late Paint _paint;
  late Color _color;

  BlockSprite(this._size, this._position, this._color): super();

  @override
  Future<void> onLoad() async {
    await super.onLoad();
    position = _position;
    size = _size;
    _paint = Paint()
      ..style = PaintingStyle.fill
      ..color = _color;
    add(RectangleHitbox());
  }

  @override
  void render(Canvas canvas) {
    super.render(canvas);
    final r = Rect.fromLTWH(0, 0, _size.x, _size.y);
    canvas.drawRect(r, _paint);
  }

  @override
  void onCollisionEnd(PositionComponent other) {
    if(!gameRef._flag) { return ; }
    gameRef.remove(this);
    gameRef._blocks.remove(this);
    if (gameRef._blocks.length == 0) {
      gameRef.gameOver();
    }
  }
}

class BarSprite extends PositionComponent with CollisionCallbacks, KeyboardHandler, HasGameRef<SampleGame> {
  Vector2 _delta = Vector2.zero();
  late Paint _paint;

  BarSprite(): super();

  @override
  Future<void> onLoad() async {
    await super.onLoad();
    size = Vector2(100, 25);
    position = Vector2(gameRef.canvasSize.x / 2 - 50, gameRef.canvasSize.y - 50);
    _paint = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.white;
    add(RectangleHitbox());
  }

  @override
  void update(double delta) {
    if (!gameRef._flag) { return; }
    position += _delta * delta * 100;
    super.update(delta);
  }

  @override
  void render(Canvas canvas) {
    super.render(canvas);
    final r = Rect.fromLTWH(0, 0, 100, 25);
    canvas.drawRect(r, _paint);
  }

  @override
  bool onKeyEvent(
      RawKeyEvent event,
      Set<LogicalKeyboardKey> keysPressed,
      ) {
    if(!gameRef._flag) { return false; }
    if (event is RawKeyUpEvent) {
      _delta = Vector2.zero();
    }
    if (event.character == 'j') {
      _delta.x = -3;
    }
    if (event.character == 'l') {
      _delta.x = 3;
    }
    return true;
  }
}

class TextSprite extends TextComponent with HasGameRef<SampleGame> {
  late String _message;

  TextSprite(this._message): super();

  @override
  Future<void> onLoad() async {
    await super.onLoad();
    anchor = Anchor.center;
    position = Vector2(gameRef.canvasSize.x / 2,
     gameRef.canvasSize.y / 2);
    text = _message;
    textRenderer = TextPaint(
      style: TextStyle(
        fontSize: 60.0,
        fontWeight: FontWeight.w300,
        color: Colors.red,
      ),
    );
  }
}

【Flutter】スプライトの衝突判定

class SampleGame extends FlameGame with HasCollisionDetection, HasKeyboardHandlerComponents {

  @override
  Color backgroundColor() => const Color(0xffCCCCFF);

  @override
  Future<void> onLoad() async {
    await super.onLoad();
    add(MySprite(Vector2(300, 300), true));
    add(MySprite(Vector2(100, 100), false));
  }
}

class MySprite extends CircleComponent with CollisionCallbacks, KeyboardHandler {
  var _size = 100.0;
  Vector2 _delta = Vector2.zero();
  late Color _color;
  late Vector2 _position;
  late bool _stay;

  MySprite(this._position, this._stay): super();

  @override
  Future<void> onLoad() async {
    await super.onLoad();
    _color = _stay ? Colors.green : Colors.red;
    size = Vector2(_size, _size);
    position = _position;
    setColor(_color);
    CircleHitbox hitbox = CircleHitbox();
    add(hitbox);
  }

  @override
  bool onKeyEvent(
      RawKeyEvent event,
      Set<LogicalKeyboardKey> keysPressed,
      )  {
    if (this._stay) { return false; }
    if (event is RawKeyUpEvent) {
      _delta = Vector2.zero();
    }
    if (event.character == 'j') {
      _delta.x = -1;
    }
    if (event.character == 'l') {
      _delta.x = 1;
    }
    if (event.character == 'i') {
      _delta.y = -1;
    }
    if (event.character == 'k') {
      _delta.y = 1;
    }
    return true;
  }

  @override
  void update(double delta){
    super.update(delta);
    position += _delta * delta * 100;
  }

  @override
  void onCollision(Set<Vector2> points, PositionComponent other) {
    setColor(Colors.yellow);
  }

  @override
  void onCollisionEnd(PositionComponent other){
    setColor(_color);
  }
}