Flutter实时通信插件pusher_channels_flutter的使用

发布于 1周前 作者 zlyuanteng 来自 Flutter

Flutter实时通信插件pusher_channels_flutter的使用

概述

pusher_channels_flutter 是一个用于在 Flutter 应用中集成 Pusher Channels 的客户端库。Pusher Channels 提供了一种简单的方法来实现实时通信,支持多种平台(Android、iOS 和 Web)。

支持的 Flutter 平台

  • Android 通过 pusher-websocket-java
  • iOS 通过 pusher-websocket-swift
  • Web 通过 pusher-js

部署目标

  • iOS 13.0 及以上
  • Android 6.0 及以上
  • Web 支持 Chrome/Edge/Firefox/Safari

安装

要将插件集成到你的 Flutter 应用中,需要在 pubspec.yaml 文件中添加依赖:

dependencies:
  pusher_channels_flutter: '^1.0.1'

iOS 特定安装

运行以下命令以安装 CocoaPods 依赖:

$ pod install

Android 特定安装

Gradle 会自动包含 pusher-websocket-java 依赖。

Web 特定安装

index.html 文件的 <head> 部分添加以下脚本:

<head>
  <script
    charset="utf-8"
    src="https://js.pusher.com/8.3.0/pusher.min.js"
  ></script>
  ...
</head>

初始化

PusherChannelsFlutter 类是一个单例,可以通过 getInstance() 方法实例化。然后需要初始化客户端并设置一些配置选项,例如回调函数:

PusherChannelsFlutter pusher = PusherChannelsFlutter.getInstance();
try {
  await pusher.init(
    apiKey: API_KEY,
    cluster: API_CLUSTER,
    onConnectionStateChange: onConnectionStateChange,
    onError: onError,
    onSubscriptionSucceeded: onSubscriptionSucceeded,
    onEvent: onEvent,
    onSubscriptionError: onSubscriptionError,
    onDecryptionFailure: onDecryptionFailure,
    onMemberAdded: onMemberAdded,
    onMemberRemoved: onMemberRemoved,
    // authEndpoint: "<Your Authendpoint>",
    // onAuthorizer: onAuthorizer
  );
  await pusher.subscribe(channelName: 'presence-chatbox');
  await pusher.connect();
} catch (e) {
  print("ERROR: $e");
}

配置参数

参数 Android iOS Web
activityTimeout
apiKey
authParam ⬜️ ⬜️
authEndpoint
authTransport ⬜️ ⬜️
cluster
disabledTransports ⬜️ ⬜️
enabledTransports ⬜️ ⬜️
enableStats ⬜️ ⬜️
ignoreNullOrigin ⬜️ ⬜️
logToConsole ⬜️ ⬜️
maxReconnectGapInSeconds ⬜️
maxReconnectionAttempts ⬜️
pongTimeout
proxy ⬜️ ⬜️
useTLS ⬜️

配置参数说明

  • activityTimeout (double): 在没有收到服务器消息的情况下,多久发送一次 ping 消息,默认值由服务器提供。
  • apiKey (String): 从 Pusher Channels 控制台获取。
  • authParams (Map): 传递额外数据给授权者。
  • authEndpoint (String): 授权端点 URL。
  • cluster (String): 连接的集群,默认为 mt1
  • useTLS (bool): 是否使用 TLS 加密传输,默认为 true

事件回调参数

onEvent

void onEvent(PusherEvent event) {
  print("onEvent: $event");
}

onSubscriptionSucceeded

void onSubscriptionSucceeded(String channelName, dynamic data) {
  print("onSubscriptionSucceeded: $channelName data: $data");
}

onSubscriptionError

void onSubscriptionError(String message, dynamic e) {
  print("onSubscriptionError: $message Exception: $e");
}

onDecryptionFailure

void onDecryptionFailure(String event, String reason) {
  print("onDecryptionFailure: $event reason: $reason");
}

onMemberAdded

void onMemberAdded(String channelName, PusherMember member) {
  print("onMemberAdded: $channelName member: $member");
}

onMemberRemoved

void onMemberRemoved(String channelName, PusherMember member) {
  print("onMemberRemoved: $channelName member: $member");
}

onAuthorizer

dynamic onAuthorizer(String channelName, String socketId, dynamic options) async {
  return {
    "auth": "foo:bar",
    "channel_data": '{"user_id": 1}',
    "shared_secret": "foobar"
  };
}

onConnectionStateChange

void onConnectionStateChange(dynamic currentState, dynamic previousState) {
  print("Connection: $currentState");
}

onError

void onError(String message, int? code, dynamic e) {
  print("onError: $message code: $code exception: $e");
}

连接处理

连接

await pusher.connect();

断开连接

await pusher.disconnect();

重新连接

当连接断开时,客户端会尝试重新连接。

订阅

公共频道

final myChannel = await pusher.subscribe(channelName: "my-channel");

私有频道

final myPrivateChannel = await pusher.subscribe(channelName: "private-my-channel");

私有加密频道

final privateEncryptedChannel = await pusher.subscribe(channelName: "private-encrypted-my-channel");

带有成员的频道

final myPresenceChannel = await pusher.subscribe(
  channelName: "presence-my-channel",
  onSubscriptionSucceeded: (channelName, data) {
    print("Subscribed to $channelName");
    print("I can now access me: ${myChannel.me}");
    print("And here are the channel members: ${myChannel.members}");
  },
  onMemberAdded: (member) {
    print("Member added: $member");
  },
  onMemberRemoved: (member) {
    print("Member removed: $member");
  },
  onEvent: (event) {
    print("Event received: $event");
  },
);

取消订阅

await pusher.unsubscribe("my-channel");

绑定事件

单个频道事件

final myChannel = await pusher.subscribe(
  channelName: "my-channel",
  onEvent: (event) {
    print("Got channel event: $event");
  }
);
await pusher.connect();

全局事件

await pusher.init(
  apiKey: API_KEY,
  cluster: API_CLUSTER,
  onEvent: (event) {
    print("Got event: $event");
  }
);
final myChannel = await pusher.subscribe(
  channelName: "my-channel"
);

触发事件

await myChannel.trigger(eventName: "client-my-event", data: {"myName": "Bob"});

或全局触发事件:

await pusher.trigger(channelName: "my-channel", eventName: "client-my-event", data: {"myName": "Bob"});

获取频道

final channel = pusher.getChannel("presence-channel");

获取 Socket 信息

final socketId = await pusher.getSocketId();

示例代码

以下是一个完整的示例代码,展示了如何使用 pusher_channels_flutter 插件:

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

import 'package:shared_preferences/shared_preferences.dart';
import 'package:pusher_channels_flutter/pusher_channels_flutter.dart';

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

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  PusherChannelsFlutter pusher = PusherChannelsFlutter.getInstance();
  String _log = 'output:\n';
  final _apiKey = TextEditingController();
  final _cluster = TextEditingController();
  final _channelName = TextEditingController();
  final _eventName = TextEditingController();
  final _channelFormKey = GlobalKey<FormState>();
  final _eventFormKey = GlobalKey<FormState>();
  final _listViewController = ScrollController();
  final _data = TextEditingController();

  void log(String text) {
    print("LOG: $text");
    setState(() {
      _log += text + "\n";
      Timer(
          const Duration(milliseconds: 100),
          () => _listViewController
              .jumpTo(_listViewController.position.maxScrollExtent));
    });
  }

  @override
  void initState() {
    super.initState();
    initPlatformState();
  }

  void onConnectPressed() async {
    if (!_channelFormKey.currentState!.validate()) {
      return;
    }
    // Remove keyboard
    FocusScope.of(context).requestFocus(FocusNode());
    SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setString("apiKey", _apiKey.text);
    prefs.setString("cluster", _cluster.text);
    prefs.setString("channelName", _channelName.text);

    try {
      await pusher.init(
        apiKey: _apiKey.text,
        cluster: _cluster.text,
        onConnectionStateChange: onConnectionStateChange,
        onError: onError,
        onSubscriptionSucceeded: onSubscriptionSucceeded,
        onEvent: onEvent,
        onSubscriptionError: onSubscriptionError,
        onDecryptionFailure: onDecryptionFailure,
        onMemberAdded: onMemberAdded,
        onMemberRemoved: onMemberRemoved,
        onSubscriptionCount: onSubscriptionCount,
        // authEndpoint: "<Your Authendpoint Url>",
        // onAuthorizer: onAuthorizer
      );
      await pusher.subscribe(channelName: _channelName.text);
      await pusher.connect();
    } catch (e) {
      log("ERROR: $e");
    }
  }

  void onConnectionStateChange(dynamic currentState, dynamic previousState) {
    log("Connection: $currentState");
  }

  void onError(String message, int? code, dynamic e) {
    log("onError: $message code: $code exception: $e");
  }

  void onEvent(PusherEvent event) {
    log("onEvent: $event");
  }

  void onSubscriptionSucceeded(String channelName, dynamic data) {
    log("onSubscriptionSucceeded: $channelName data: $data");
    final me = pusher.getChannel(channelName)?.me;
    log("Me: $me");
  }

  void onSubscriptionError(String message, dynamic e) {
    log("onSubscriptionError: $message Exception: $e");
  }

  void onDecryptionFailure(String event, String reason) {
    log("onDecryptionFailure: $event reason: $reason");
  }

  void onMemberAdded(String channelName, PusherMember member) {
    log("onMemberAdded: $channelName user: $member");
  }

  void onMemberRemoved(String channelName, PusherMember member) {
    log("onMemberRemoved: $channelName user: $member");
  }

  void onSubscriptionCount(String channelName, int subscriptionCount) {
    log("onSubscriptionCount: $channelName subscriptionCount: $subscriptionCount");
  }

  dynamic onAuthorizer(String channelName, String socketId, dynamic options) {
    return {
      "auth": "foo:bar",
      "channel_data": '{"user_id": 1}',
      "shared_secret": "foobar"
    };
  }

  void onTriggerEventPressed() async {
    var eventFormValidated = _eventFormKey.currentState!.validate();

    if (!eventFormValidated) {
      return;
    }
    SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setString("eventName", _eventName.text);
    prefs.setString("data", _data.text);
    pusher.trigger(PusherEvent(
        channelName: _channelName.text,
        eventName: _eventName.text,
        data: _data.text));
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) return;
    SharedPreferences prefs = await SharedPreferences.getInstance();
    setState(() {
      _apiKey.text = prefs.getString("apiKey") ?? '';
      _cluster.text = prefs.getString("cluster") ?? 'eu';
      _channelName.text = prefs.getString("channelName") ?? 'my-channel';
      _eventName.text = prefs.getString("eventName") ?? 'client-event';
      _data.text = prefs.getString("data") ?? 'test';
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text(pusher.connectionState == 'DISCONNECTED'
              ? 'Pusher Channels Example'
              : _channelName.text),
        ),
        body: Padding(
          padding: const EdgeInsets.all(8.0),
          child: ListView(
              controller: _listViewController,
              scrollDirection: Axis.vertical,
              shrinkWrap: true,
              children: <Widget>[
                if (pusher.connectionState != 'CONNECTED')
                  Form(
                      key: _channelFormKey,
                      child: Column(children: <Widget>[
                        TextFormField(
                          controller: _apiKey,
                          validator: (String? value) {
                            return (value != null && value.isEmpty)
                                ? 'Please enter your API key.'
                                : null;
                          },
                          decoration: const InputDecoration(labelText: 'API Key'),
                        ),
                        TextFormField(
                          controller: _cluster,
                          validator: (String? value) {
                            return (value != null && value.isEmpty)
                                ? 'Please enter your cluster.'
                                : null;
                          },
                          decoration: const InputDecoration(
                            labelText: 'Cluster',
                          ),
                        ),
                        TextFormField(
                          controller: _channelName,
                          validator: (String? value) {
                            return (value != null && value.isEmpty)
                                ? 'Please enter your channel name.'
                                : null;
                          },
                          decoration: const InputDecoration(
                            labelText: 'Channel',
                          ),
                        ),
                        ElevatedButton(
                          onPressed: onConnectPressed,
                          child: const Text('Connect'),
                        )
                      ]))
                else
                  Form(
                    key: _eventFormKey,
                    child: Column(children: <Widget>[
                      ListView.builder(
                          scrollDirection: Axis.vertical,
                          shrinkWrap: true,
                          itemCount: pusher
                              .channels[_channelName.text]?.members.length,
                          itemBuilder: (context, index) {
                            final member = pusher
                                .channels[_channelName.text]!.members.values
                                .elementAt(index);

                            return ListTile(
                                title: Text(member.userInfo.toString()),
                                subtitle: Text(member.userId));
                          }),
                      TextFormField(
                        controller: _eventName,
                        validator: (String? value) {
                          return (value != null && value.isEmpty)
                              ? 'Please enter your event name.'
                              : null;
                        },
                        decoration: const InputDecoration(
                          labelText: 'Event',
                        ),
                      ),
                      TextFormField(
                        controller: _data,
                        decoration: const InputDecoration(
                          labelText: 'Data',
                        ),
                      ),
                      ElevatedButton(
                        onPressed: onTriggerEventPressed,
                        child: const Text('Trigger Event'),
                      ),
                    ]),
                  ),
                SingleChildScrollView(
                    scrollDirection: Axis.vertical, child: Text(_log)),
              ]),
        ),
      ),
    );
  }
}

希望这个指南对你有所帮助!如果你有任何问题或需要进一步的帮助,请随时联系我。


更多关于Flutter实时通信插件pusher_channels_flutter的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter实时通信插件pusher_channels_flutter的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter项目中使用pusher_channels_flutter插件来实现实时通信的示例代码。

首先,确保你的Flutter项目已经设置好,并且你已经添加了pusher_channels_flutter依赖到你的pubspec.yaml文件中:

dependencies:
  flutter:
    sdk: flutter
  pusher_channels_flutter: ^x.y.z  # 请替换为最新版本号

然后运行flutter pub get来安装依赖。

接下来,你需要设置Pusher Channels的配置信息,这通常包括你的应用密钥(app key)、集群(cluster)和频道名称(channel name)。

以下是一个完整的示例,展示了如何使用pusher_channels_flutter来订阅一个频道并处理事件:

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Pusher Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final PusherChannels _pusherChannels = PusherChannels(
    key: 'your-app-key', // 替换为你的Pusher应用密钥
    cluster: 'your-cluster', // 替换为你的Pusher集群
    useTLS: true,
  );

  @override
  void initState() {
    super.initState();
    
    // 订阅频道
    _pusherChannels.subscribe('your-channel-name')
      .listen((event) {
        // 处理接收到的事件
        if (event is PusherEvent) {
          print('Received event: ${event.data}');
          // 在这里更新UI或执行其他操作
          setState(() {
            // 例如,将事件数据添加到列表中显示
            // eventsList.add(event.data);
          });
        } else if (event is PusherError) {
          print('Error: ${event.message}');
        } else if (event is PusherConnectionState) {
          print('Connection state changed: ${event.currentState}');
        }
      });
  }

  @override
  void dispose() {
    // 取消订阅频道(可选,通常在应用关闭或页面销毁时执行)
    _pusherChannels.unsubscribe('your-channel-name');
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Pusher Demo'),
      ),
      body: Center(
        child: Text('Listening for events...'),
      ),
    );
  }
}

在这个示例中:

  1. 我们创建了一个PusherChannels实例,并配置了应用密钥和集群。
  2. initState方法中,我们订阅了一个名为your-channel-name的频道,并设置了一个监听器来处理接收到的事件。事件可以是PusherEvent(表示接收到一个消息)、PusherError(表示发生错误)或PusherConnectionState(表示连接状态改变)。
  3. dispose方法中,我们取消了订阅(这是可选的,但在应用关闭或页面销毁时执行是一个好习惯)。

请注意,你需要将your-app-keyyour-clusteryour-channel-name替换为你自己的Pusher配置信息。

这个示例展示了基本的订阅和事件处理。你可以根据需要扩展这个示例,例如添加UI组件来显示接收到的事件数据,或者发送事件到频道中。

回到顶部