iOS・Android・Web の Flutter
Dart クライアント 1 つで、プライベートチャンネル、再接続、閉じているときはプッシュ。
Flutter は 1 つのコードから iOS、Android、Web、デスクトップのアプリを作ります。リアルタイムのコードも 1 つであるべきです。公式の Pusher Flutter プラグインは常に pusher.com のホストに接続するので、代わりに dart_pusher_channels を使います。ホストを設定できる純 Dart の Pusher プロトコルクライアントで、Flutter が動く場所ならどこでも動きます。
ユーザーにとっての価値
- 注文、チャット、スコアの画面がその場で更新され、引っ張って更新する必要はありません。
- トンネルやエレベーター、Wi-Fi からモバイル回線への切り替えのあとも、接続は自動で戻ります。
- スマートフォンでもブラウザでも同じ動き。
使う機能
- アプリ全体で 1 つのクライアントにする dart_pusher_channels。
- サインイン中のユーザーのトークンでバックエンドが認可するプライベートチャンネル(
private-)。 - 入力中表示などの小さな合図に使う、プライベート/プレゼンスチャンネルのクライアントイベント。
- アプリがバックグラウンドや終了しているときの Hibira Push。
ステップ 1:インストール
flutter pub add dart_pusher_channelsステップ 2:アプリ全体で 1 つのクライアント
// lib/realtime.dart
import 'package:dart_pusher_channels/dart_pusher_channels.dart';
final realtime = PusherChannelsClient.websocket(
options: PusherChannelsOptions.fromHost(
scheme: 'wss',
host: 'ws.hibira.io',
port: 443,
key: const String.fromEnvironment('HIBIRA_KEY'),
),
connectionErrorHandler: (error, trace, refresh) => refresh(),
);キーはビルド時に渡します:flutter run --dart-define=HIBIRA_KEY=YOUR_APP_KEY。アプリのシークレットは決してアプリに入れません。プライベートチャンネルはバックエンドが署名します。
ステップ 3:画面で 1 件の注文を追う
// lib/order_screen.dart
import 'dart:async';
import 'package:dart_pusher_channels/dart_pusher_channels.dart';
import 'package:flutter/material.dart';
import 'realtime.dart';
class OrderScreen extends StatefulWidget {
const OrderScreen({super.key, required this.orderId, required this.token});
final String orderId;
final String token;
State<OrderScreen> createState() => _OrderScreenState();
}
class _OrderScreenState extends State<OrderScreen> {
late final PrivateChannel channel;
final subs = <StreamSubscription>[];
String status = '…';
void initState() {
super.initState();
channel = realtime.privateChannel(
'private-order-${widget.orderId}',
authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate.forPrivateChannel(
authorizationEndpoint: Uri.parse('https://api.example.com/pusher/auth'),
headers: {'Authorization': 'Bearer ${widget.token}'},
),
);
subs.add(channel.bind('status').listen((event) {
setState(() => status = event.tryGetDataAsMap()?['status'] ?? status);
}));
subs.add(realtime.onConnectionEstablished.listen((_) => channel.subscribeIfNotUnsubscribed()));
channel.subscribeIfNotUnsubscribed();
}
void dispose() {
for (final s in subs) {
s.cancel();
}
channel.unsubscribe();
super.dispose();
}
Widget build(BuildContext context) => Text(status);
}onConnectionEstablished で購読し直すと、再接続のたびにチャンネルが戻ります。バックエンドの認可エンドポイントは socket_id と channel_name を受け取り、トークンを確認して、Pusher 互換のサーバーライブラリで作った署名を返します。
ステップ 4:アプリを開いている間だけ接続する
class _AppState extends State<App> with WidgetsBindingObserver {
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
realtime.connect();
}
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.paused) realtime.disconnect();
if (state == AppLifecycleState.resumed) realtime.connect();
}
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
Widget build(BuildContext context) => const MaterialApp(home: HomeScreen());
}iOS と Android はバックグラウンドのアプリの通信をどのみち止めるので、paused で切断し、resumed で接続し直します。アプリを閉じている間に伝えるべきことは、プッシュ通知で送りましょう。ユーザーを呼び戻すプッシュ通知と、Flutter のホストアプリから呼べる Android/iOS SDK を説明したプッシュのガイドを参照してください。
ヒント
- 画面に必要なものはまず API から読み込み、あとはイベントで更新します。開いた瞬間に必要な状態は
cache-チャンネルに配信しましょう。 - アプリ設定で Allow client events をオンにすると、クライアントイベント(
channel.trigger(eventName: 'client-typing', data: {...}))をプライベート/プレゼンスチャンネルで使えます。 - サインインすると、Flutter ガイドにアプリの値を埋めたクライアント設定が表示されます。