【Flutter】非同期処理に強くなる!StreamBuilder入門

対象者

  • Flutterを使用したアプリ開発経験があるが、非同期処理やリアルタイムデータ更新についての知識が不十分な方
  • StreamBuilderの基本的な使い方や実例を学び、効率的にアプリ開発を進めたい方
  • StreamBuilderとFutureBuilderの違いや、それぞれの適切な使用シーンを把握したい方

はじめに

Flutterを使用したアプリ開発を行っているあなたは、非同期処理やリアルタイムデータ更新に関する知識や実例を学びたいと思っていませんか?StreamBuilderを効率的に活用することで、アプリ開発のスピードが大幅に向上し、ユーザーに素早く最新の情報を提供できるようになります。また、StreamBuilderとFutureBuilderの違いやそれぞれの適切な使用シーンを理解することで、さらにアプリの品質を向上させることができます。

本記事では、StreamBuilderの基本的な役割や使い方、実例を紹介し、さらにStreamBuilderとFutureBuilderの比較を行います。これらの情報を通じて、あなたのアプリ開発スキルがさらに磨かれることでしょう。また、実践的なアプリケーション例を知ることで、具体的なアイデアを形にするためのインスピレーションを得られるはずです。

アプリ開発を成功させるために、是非この記事を参考にして、StreamBuilderの活用法を習得しましょう。あなたのアプリ開発が一歩先に進むことをお約束します。

https://youtu.be/yJL1IZAk1JQ<

StreamBuilderの基本

StreamBuilderとは

StreamBuilderは、Flutterアプリケーションで非同期処理を行う際に役立つウィジェットです。その基本的な役割は、指定されたストリームから流れてくるデータに応じて、自動的にウィジェットの再描画を行うことです。これにより、アプリケーションの状態が常に最新であることが保証されます。

非同期処理において、StreamBuilderは特に有用です。例えば、連続したデータ(イベント)を扱う場合、StreamBuilderはlisten()メソッドを使って新しいデータが追加されたタイミングで処理を行います。これにより、非同期的なデータの取得や更新がスムーズに行われます。

StreamBuilderの実装

StreamBuilderを使用する方法は非常にシンプルです。まず、StreamBuilderウィジェットを実装し、次に監視するストリームを指定します。最後に、新しいイベントが発生した際にウィジェットを再描画するためのビルダー関数を定義します。

StreamBuilderウィジェットの基本的な構造は以下のようになります。

StreamBuilder<T>(
  stream: stream,
  initialData: initialData,
  builder: (BuildContext context, AsyncSnapshot<T> snapshot) {
    // ウィジェットの再描画処理
  },
)

ここで、Tはストリームで扱うデータ型を示しています。また、streamプロパティには監視するストリームを指定します。initialDataプロパティは、ストリームからデータが届く前の初期値を設定するために使用されます。

Streamからイベントを監視する方法

StreamBuilderは、指定されたストリームからイベントを監視し、新しいイベントが発生した際にbuilder関数を呼び出します。この関数は、BuildContextとAsyncSnapshotの2つの引数を受け取り、ウィジェットの再描画処理を行います。

AsyncSnapshotは、ストリームから受け取ったデータやエラー情報を含むオブジェクトです。このオブジェクトを使って、ウィジェットの状態を最新のものに更新します。

以下は、StreamBuilderを使ってイベントを監視し、ウィジェットを再描画する簡単な例です。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: StreamBuilder<int>(
            stream: _counterStream(),
            initialData: 0,
            builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
              if (snapshot.hasError) {
                return Text('Error: ${snapshot.error}');
              }
              return Text('Counter: ${snapshot.data}');
            },
          ),
        ),
      ),
    );
  }

  Stream<int> _counterStream() async* {
    int counter = 0;
    while (true) {
      await Future.delayed(Duration(seconds: 1));
      counter++;
      yield counter;
    }
  }
}

この例では、1秒ごとにカウンターが更新されるストリームを作成し、StreamBuilderを使用してその値を表示しています。カウンターが更新されるたびに、StreamBuilderは自動的にウィジェットを再描画し、最新の値が表示されます。

StreamBuilderとFutureBuilderの比較

StreamBuilderとFutureBuilderは、Flutterで非同期処理を行う際に用いられるウィジェットです。どちらも似た目的で使われますが、それぞれ異なるシナリオで適した用途があります。

両方ともsnapshotを引数に持ちます。そしてsnapshotには3つの状態があり、この例では以下のようなWidgetが表示されるようになっています。

  • 待機中のケース(snapshot.connectionState == ConnectionState.waiting):
    fetchPost()がまだ完了していない場合、つまりデータの取得が終わっていない間、CircularProgressIndicator()が表示されます。

  • 成功した場合(snapshot.hasData == true):
    fetchPost()が成功し、データが取得された場合、AsyncSnapshotのdataプロパティにデータが格納されます。この例では、snapshot.data.titleを使ってタイトルを表示します。

  • 失敗した場合(snapshot.hasError == true):
    fetchPost()が失敗し、エラーが発生した場合、AsyncSnapshotのerrorプロパティにエラー情報が格納されます。この例では、Text(‘Error: ${snapshot.error}’)を使ってエラー情報を表示します。

一度のデータ取得におけるFutureBuilderの使用

FutureBuilderは、一度だけデータを取得する非同期処理に適しています。例えば、APIからデータをフェッチする場合や、データベースからの読み取りなど、一度の処理で結果が返ってくる場合に使用します。これは、FutureBuilderがFutureオブジェクトを利用しているためです。

以下は、APIからデータを取得し表示するFutureBuilderの例です。

FutureBuilder<Post>(
  future: fetchPost(),
  builder: (BuildContext context, AsyncSnapshot<Post> snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return CircularProgressIndicator();
    } else if (snapshot.hasError) {
      return Text('Error: ${snapshot.error}');
    } else {
      return Text('Title: ${snapshot.data.title}');
    }
  },
)

動的データ更新におけるStreamBuilderの使用

一方で、StreamBuilderは動的に更新されるデータに対応しています。これは、StreamBuilderがストリームを監視し、新しいイベントが発生するたびにウィジェットを再描画することができるためです。リアルタイムデータの取得や、継続的にデータが変化する場合にはStreamBuilderが適しています。

以下は、リアルタイムでカウンターが更新されるStreamBuilderの例です。

StreamBuilder<int>(
  stream: _counterStream(),
  initialData: 0,
  builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
    if (snapshot.hasError) {
      return Text('Error: ${snapshot.error}');
    }
    return Text('Counter: ${snapshot.data}');
  },
)

StreamBuilderはinitialDataで初期値を設定することが可能です。

結論

StreamBuilderとFutureBuilderは、それぞれ異なるシナリオで最適なウィジェットです。一度のデータ取得にはFutureBuilderを、動的なデータ更新にはStreamBuilderを使用することで、非同期処理を効率的に行うことができます。適切なウィジェットを使い分けることで、アプリケーションのパフォーマンスを向上させることができます。

StreamBuilderの実例

StreamBuilderは、リアルタイムデータや動的に変化するデータを扱うアプリケーションに適しています。ここでは、リアルタイム時計アプリとデモチャットアプリの2つの実例を紹介します。

時計

このサンプルでは、現在時刻が秒ごとに更新されるウィジェットを作成できます。以下は、StreamBuilderを使用してリアルタイム時計アプリを実装する例です。

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('リアルタイム時計')),
        body: Center(child: RealTimeClock()),
      ),
    );
  }
}

class RealTimeClock extends StatefulWidget {
  @override
  _RealTimeClockState createState() => _RealTimeClockState();
}

class _RealTimeClockState extends State<RealTimeClock> {
  Stream<DateTime> _dateTimeStream() {
    return Stream.periodic(Duration(seconds: 1), (_) => DateTime.now());
  }

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<DateTime>(
      stream: _dateTimeStream(),
      builder: (BuildContext context, AsyncSnapshot<DateTime> snapshot) {
        if (snapshot.hasData) {
          return Text(snapshot.data!.toIso8601String(), style: TextStyle(fontSize: 24));
        } else {
          return CircularProgressIndicator();
        }
      },
    );
  }
}

StreamControllerでチャットアプリ

チャットでは、ユーザーがメッセージを送信すると、リアルタイムでチャット画面に表示されます。このデモはそれを想定して、以下は、StreamBuilderを用いたデモチャットアプリの例です。

この例では、StreamControllerを使用してます。
StreamControllerは、DartやFlutterで非同期処理を行うためのクラスです。制御するストリームを持ち、データ、エラー、終了イベントをストリーム上で送信できます。StreamControllerを使うと、他の人が待機できる単純なストリームを作成したり、そのストリームにイベントをプッシュしたりできます。また、ストリームの一時停止やサブスクライバの存在、状態変更時のコールバックの受け取りが可能です。データを追加する方法としては、addメソッドやsink.addを利用できます。

  • add
    StreamControllerに値を直接追加する方法。引数に値を入れることで、StreamControllerにデータを持たせることができる。
  • sink.add
    StreamControllerのsinkプロパティを通じて値を追加する方法。StreamSinkは入力のみを受け付けるメソッドしか持たないため、誤った値の購読を防ぐことができる。

StreamControllerを使用するときは、disposeをお忘れなく!

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('デモチャットアプリ')),
        body: DemoChatApp(),
      ),
    );
  }
}

class DemoChatApp extends StatefulWidget {
  @override
  _DemoChatAppState createState() => _DemoChatAppState();
}

class _DemoChatAppState extends State<DemoChatApp> {
  final _controller = StreamController<String>();

  @override
  void dispose() {
    _controller.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        TextField(
          onSubmitted: (text) {
            _controller.sink.add(text);
          },
        ),
        Expanded(
          child: StreamBuilder<String>(
            stream: _controller.stream,
            builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
              if (snapshot.hasData) {
                return ListView(
                  children: snapshot.data!
                      .split('\n')
                      .map((message) => Text(message))
                      .toList(),
                );
              } else {
                return CircularProgressIndicator();
              }
            },
          ),
        ),
      ],
    );
  }
}

ユースケース

StreamBuilderは以下のような色々なケースにおいて使用することがあります。

  • チャットアプリケーション
    StreamBuilderを用いることで、リアルタイムでメッセージのやり取りができるチャットアプリケーションを作成できます。新しいメッセージが送信されると、StreamBuilderが再描画を行い、リアルタイムでメッセージが表示されます。

  • ソーシャルネットワークアプリケーション
    ソーシャルネットワークアプリケーションでは、ユーザーが投稿したコンテンツをリアルタイムで表示することが求められます。StreamBuilderを使用すると、この要件を簡単に実現できます

  • リアルタイムコンテンツ更新アプリケーション
    ニュースやブログのようなリアルタイムでコンテンツが更新されるアプリケーションでも、StreamBuilderが役立ちます。新しい記事が投稿されると、自動的にリストに反映されます。

Q&A

Q1: StreamBuilderとFutureBuilderの違いは何ですか?

A1: StreamBuilderはリアルタイムでデータが更新されるアプリケーションに適しており、Streamからイベントを監視してウィジェットの再描画を行います。一方、FutureBuilderは一度のデータ取得に適しており、非同期処理の結果をもとにウィジェットを構築します。StreamBuilderは動的データ更新に対応しているのに対し、FutureBuilderは主に静的データ取得に用いられます。

Q2: StreamBuilderを利用する際の実装方法はどのようになりますか?

A2: StreamBuilderを利用する際は、まずStreamを作成し、StreamBuilderウィジェット内でそのStreamを指定します。また、builderプロパティに、BuildContextとAsyncSnapshotを引数とする関数を指定します。この関数内で、AsyncSnapshotからデータを取得し、それをもとにウィジェットを構築します。

Q3: StreamBuilderでウィジェットの更新を行う方法は何ですか?

A3: StreamBuilderでウィジェットの更新を行うには、Streamからイベントを監視し、イベントが発生した際にウィジェットを再描画します。StreamBuilderは非同期処理に対応しており、コードの順序に関係なくデータ処理を行うことができます。Streamからの新しいデータを受け取ると、自動的にウィジェットが再描画されます。

まとめ

StreamBuilderは、Flutterでリアルタイムデータの更新に対応したウィジェット構築を効率的に行うためのウィジェットです。非同期処理に対応しており、Streamからイベントを監視してウィジェットの再描画を行います。これにより、動的データの更新に適したアプリケーションを容易に実現できます。

比較対象であるFutureBuilderとは異なり、StreamBuilderはリアルタイムなデータの更新に対応するために設計されています。これにより、チャットアプリケーションやソーシャルネットワークアプリケーションなど、リアルタイムでデータが更新される機能を簡単に実装できます。

重要なポイント:

  • StreamBuilderはリアルタイムデータの更新に対応したウィジェット構築を効率的に行う
  • 非同期処理に対応し、Streamからイベントを監視してウィジェットの再描画を行う
  • 動的データの更新に適しており、様々なアプリケーションで活用できる
  • FutureBuilderと比較してリアルタイムなデータ更新に対応するために設計されている

これらの要点を踏まえ、StreamBuilderはリアルタイムなデータの更新に対応したアプリケーション開発に適したウィジェットであると言えます。そのため、効率的かつ迅速な開発が可能となります。

参考