← 使用场景

iOS、Android 和网页上的 Flutter

一个 Dart 客户端:私有频道、自动重连,App 关闭时改用推送。

Flutter 用一份代码构建 iOS、Android、网页和桌面应用,实时功能的代码也应该是一份。官方的 Pusher Flutter 插件总是连接 pusher.com 的主机,所以请改用 dart_pusher_channels:一个纯 Dart 的 Pusher 协议客户端,可以设置 host,而且 Flutter 能运行的地方它都能运行。

用户得到什么

  • 订单、聊天和比分界面原地更新,无需下拉刷新。
  • 经过隧道、乘电梯,或从 Wi-Fi 切换到移动网络后,连接会自动恢复。
  • 手机上和浏览器里的表现一致。

用到的功能

  • dart_pusher_channels,整个 App 共用一个客户端。
  • 私有频道(private-),由你的后端用已登录用户的 token 授权。
  • 私有和 presence 频道上的客户端事件,用于正在输入提示和其他小信号。
  • App 在后台或已关闭时,改用 Hibira Push。

步骤 1:安装

flutter pub add dart_pusher_channels

步骤 2:整个 App 共用一个客户端

// 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(),
);

构建时传入 key:flutter run --dart-define=HIBIRA_KEY=YOUR_APP_KEY。App secret 绝不放进 App;私有频道由你的后端签名。

步骤 3:在界面上跟踪一个订单

// 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;

  @override
  State<OrderScreen> createState() => _OrderScreenState();
}

class _OrderScreenState extends State<OrderScreen> {
  late final PrivateChannel channel;
  final subs = <StreamSubscription>[];
  String status = '…';

  @override
  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();
  }

  @override
  void dispose() {
    for (final s in subs) {
      s.cancel();
    }
    channel.unsubscribe();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => Text(status);
}

在 onConnectionEstablished 时重新订阅,每次重连后频道都会恢复。你的后端授权端点会收到 socket_id 和 channel_name,检查 token 后,用任何兼容 Pusher 的服务器库返回签名。

步骤 4:App 打开时才连接

class _AppState extends State<App> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    realtime.connect();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.paused) realtime.disconnect();
    if (state == AppLifecycleState.resumed) realtime.connect();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => const MaterialApp(home: HomeScreen());
}

iOS 和 Android 本来就会暂停后台 App 的网络,所以在 paused 时断开,resumed 时重新连接。App 关闭时用户必须知道的事,请用推送通知发送:请看把用户带回来的推送通知和推送指南,其中介绍了你的 Flutter 宿主 App 可以调用的 Android 和 iOS SDK。

小提醒

  • 先从你的 API 加载界面需要的数据,再让事件更新它;打开时就必须存在的状态,请发布到 cache- 频道。
  • 在应用设置中开启 Allow client events 后,客户端事件(channel.trigger(eventName: 'client-typing', data: {...}))就能在私有和 presence 频道上使用。
  • 登录后,Flutter 指南会用你的应用数值填好客户端设置。