🔆

yieldを使ってみた!

2023/06/05に公開

関数を自作して動作検証してみる

最近、Dartのコードを書いていてfor文とかyield使ってないので、なんかアプリで使えないかなと思って、いつもならStreamBuilderでFirestoreのデータを取得するのですが、自作した関数を使ってみることにしました。

こんな関数を自作してみました

// Stream<List<User>>を返す_getUsers()を定義
  Stream<List<User>> _getUsers() async* {
    // _db.collection('users').snapshots()を監視
    await for (var snapshot in _db.collection('users').snapshots()) {
      List<User> users = [];
      // snapshot.docsをループ
      for (var doc in snapshot.docs) {
        // Userクラスのインスタンスを作成し、usersに追加
        users.add(User(name: doc['name'], age: doc['age']));
      }
      // usersをyieldする。returnと同じようなもの
      yield users;
    }
  }

yieldとはどんなものか?

Dartのyieldキーワードは、非同期ジェネレータ関数内で使用されます。yieldは関数が値を生成するたびにそれを呼び出し元に返すことを可能にします。具体的には、関数の実行を一時停止し、新たな値をStreamに追加した上で、後からその停止した位置から再開することができます。

この機能は、大量のデータを扱うときや、時間がかかる処理を行うときに非常に有用です。すべてのデータを一度に生成して返す代わりに、必要なデータを一つずつ生成してそれを即時に利用することができます。これによりメモリ使用量を抑えることが可能で、またデータが順次利用可能になるためレスポンス性が向上します。

Pythonでもyieldがありまして、メモリの使用量を抑えたいときに使うそうなので、Dartでも同じ役割をしているようです。でも普段は、returnしか使わないんですよね。

全体のコード
今回は、Firestoreからデータを取得するのに使ってみました。main.dartでインポートすれば使えます。

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';

// Userクラスを定義
class User {
  final String name;
  final int age;

  User({required this.name, required this.age});
}

class StreamFunctions extends StatefulWidget {
  
  _StreamFunctionsState createState() => _StreamFunctionsState();
}

class _StreamFunctionsState extends State<StreamFunctions> {
  // Firestoreのインスタンスを作成
  final FirebaseFirestore _db = FirebaseFirestore.instance;
  // Userクラスのリストを作成
  List<User> _users = [];
  // StreamSubscriptionを作成
  
  void initState() {
    super.initState();
    // _getUsers()を呼び出し、_usersに値をセット
    _getUsers().listen((users) {
      setState(() {
        _users = users;
      });
    });
  }
  // Stream<List<User>>を返す_getUsers()を定義
  Stream<List<User>> _getUsers() async* {
    // _db.collection('users').snapshots()を監視
    await for (var snapshot in _db.collection('users').snapshots()) {
      List<User> users = [];
      // snapshot.docsをループ
      for (var doc in snapshot.docs) {
        // Userクラスのインスタンスを作成し、usersに追加
        users.add(User(name: doc['name'], age: doc['age']));
      }
      // usersをyieldする。returnと同じようなもの
      yield users;
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('自作したStreamの関数を使う'),
      ),
      // _usersが空の場合は、CircularProgressIndicator()を表示
      body: _users.isEmpty
          ? const CircularProgressIndicator()
          : ListView.builder(
              itemCount: _users.length,// _usersの長さを指定
              itemBuilder: (BuildContext context, int index) {
                return ListTile(
                  title: Text(_users[index].name),
                  subtitle: Text('${_users[index].age}'),
                );
              },
            ),
    );
  }
}

こちらのファイルでインポートします。

main.dart
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_tutorial/firebase_options.dart';
import 'package:firebase_tutorial/listen.dart';
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: StreamFunctions(),
    );
  }
}

こんな感じで画面に表示することができました!

データを増やしてみましょう。コンソールで直接追加してみます。

リアルタイムに表示されました!

まとめ

過去にStreamの記事を書いていたのを見つけました。yieldを使ったコードも書いてるので、ご興味ある方いたら見てみてください。
https://zenn.dev/joo_hashi/articles/3c1e845b4b677e

Discussion