Flutter即时通讯插件stream_chat_flutter_core的使用

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

Flutter即时通讯插件stream_chat_flutter_core的使用

概述

stream_chat_flutter_coreStream Chat 的官方 Flutter 核心组件,用于构建聊天应用程序。该插件提供了业务逻辑层的支持,允许开发者更灵活地自定义聊天应用的功能和界面。

快速链接

版本迁移指南

示例代码

以下是一个完整的示例代码,展示了如何使用 stream_chat_flutter_core 构建一个简单的聊天应用。

1. 添加依赖

pubspec.yaml 文件中添加 stream_chat_flutter_core 依赖:

dependencies:
  stream_chat_flutter_core: ^latest_version

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

2. 初始化客户端并连接用户

main.dart 文件中初始化 StreamChatClient 并连接用户:

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

Future<void> main() async {
  // 创建 StreamChatClient 实例,并传入你的 API Key
  final client = StreamChatClient('YOUR_API_KEY');

  // 设置当前用户。生产环境中应通过后端生成用户令牌。
  await client.connectUser(
    User(
      id: 'user-id',
      image: 'https://example.com/user-image.jpg',
    ),
    'YOUR_USER_TOKEN', // 用户令牌
  );

  runApp(
    StreamExample(
      client: client,
    ),
  );
}

class StreamExample extends StatelessWidget {
  const StreamExample({
    Key? key,
    required this.client,
  }) : super(key: key);

  final StreamChatClient client;

  @override
  Widget build(BuildContext context) => MaterialApp(
        title: 'Stream Chat Core Example',
        home: HomeScreen(),
        builder: (context, child) => StreamChatCore(
          client: client,
          child: child!,
        ),
      );
}

3. 显示频道列表

创建一个 HomeScreen 页面来显示用户参与的频道列表:

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

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  late final channelListController = StreamChannelListController(
    client: StreamChatCore.of(context).client,
    filter: Filter.and([
      Filter.equal('type', 'messaging'),
      Filter.in_(
        'members',
        [StreamChatCore.of(context).currentUser!.id],
      ),
    ]),
  );

  @override
  void initState() {
    channelListController.doInitialLoad();
    super.initState();
  }

  @override
  void dispose() {
    channelListController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(
          title: const Text('Channels'),
        ),
        body: PagedValueListenableBuilder<int, Channel>(
          valueListenable: channelListController,
          builder: (context, value, child) {
            return value.when(
              (channels, nextPageKey, error) => LazyLoadScrollView(
                onEndOfPage: () async {
                  if (nextPageKey != null) {
                    channelListController.loadMore(nextPageKey);
                  }
                },
                child: ListView.builder(
                  itemCount: (nextPageKey != null || error != null)
                      ? channels.length + 1
                      : channels.length,
                  itemBuilder: (BuildContext context, int index) {
                    if (index == channels.length) {
                      if (error != null) {
                        return TextButton(
                          onPressed: () {
                            channelListController.retry();
                          },
                          child: Text(error.message),
                        );
                      }
                      return const CircularProgressIndicator();
                    }

                    final _item = channels[index];
                    return ListTile(
                      title: Text(_item.name ?? ''),
                      subtitle: StreamBuilder<Message?>(
                        stream: _item.state!.lastMessageStream,
                        initialData: _item.state!.lastMessage,
                        builder: (context, snapshot) {
                          if (snapshot.hasData) {
                            return Text(snapshot.data!.text!);
                          }
                          return const SizedBox();
                        },
                      ),
                      onTap: () {
                        Navigator.of(context).push(
                          MaterialPageRoute(
                            builder: (context) => StreamChannel(
                              channel: _item,
                              child: const MessageScreen(),
                            ),
                          ),
                        );
                      },
                    );
                  },
                ),
              ),
              loading: () => const Center(
                child: SizedBox(
                  height: 100,
                  width: 100,
                  child: CircularProgressIndicator(),
                ),
              ),
              error: (e) => Center(
                child: Text(
                  'Oh no, something went wrong. Please check your config. $e',
                ),
              ),
            );
          },
        ),
      );
}

4. 显示消息列表

创建一个 MessageScreen 页面来显示选定频道的消息列表:

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

  @override
  _MessageScreenState createState() => _MessageScreenState();
}

class _MessageScreenState extends State<MessageScreen> {
  final StreamMessageInputController messageInputController =
      StreamMessageInputController();
  late final ScrollController _scrollController;
  final messageListController = MessageListController();

  @override
  void initState() {
    super.initState();
    _scrollController = ScrollController();
  }

  @override
  void dispose() {
    messageInputController.dispose();
    _scrollController.dispose();
    super.dispose();
  }

  void _updateList() {
    _scrollController.animateTo(
      0,
      duration: const Duration(milliseconds: 200),
      curve: Curves.easeOut,
    );
  }

  @override
  Widget build(BuildContext context) {
    final channel = StreamChannel.of(context).channel;
    return Scaffold(
      appBar: AppBar(
        title: StreamBuilder<Iterable<User>>(
          initialData: channel.state?.typingEvents.keys,
          stream: channel.state?.typingEventsStream.map((it) => it.keys),
          builder: (context, snapshot) {
            if (snapshot.hasData && snapshot.data!.isNotEmpty) {
              return Text('${snapshot.data!.first.name} is typing...');
            }
            return const SizedBox();
          },
        ),
      ),
      body: SafeArea(
        child: Column(
          children: [
            Expanded(
              child: LazyLoadScrollView(
                onEndOfPage: () async {
                  messageListController.paginateData!();
                },
                child: MessageListCore(
                  messageListController: messageListController,
                  emptyBuilder: (BuildContext context) => const Center(
                    child: Text('Nothing here yet'),
                  ),
                  loadingBuilder: (BuildContext context) => const Center(
                    child: SizedBox(
                      height: 100,
                      width: 100,
                      child: CircularProgressIndicator(),
                    ),
                  ),
                  messageListBuilder: (
                    BuildContext context,
                    List<Message> messages,
                  ) =>
                      ListView.builder(
                    controller: _scrollController,
                    itemCount: messages.length,
                    reverse: true,
                    itemBuilder: (BuildContext context, int index) {
                      final item = messages[index];
                      final client = StreamChatCore.of(context).client;
                      if (item.user!.id == client.uid) {
                        return Align(
                          alignment: Alignment.centerRight,
                          child: Padding(
                            padding: const EdgeInsets.all(8),
                            child: Text(item.text!),
                          ),
                        );
                      } else {
                        return Align(
                          alignment: Alignment.centerLeft,
                          child: Padding(
                            padding: const EdgeInsets.all(8),
                            child: Text(item.text!),
                          ),
                        );
                      }
                    },
                  ),
                  errorBuilder: (BuildContext context, error) {
                    print(error.toString());
                    return const Center(
                      child: SizedBox(
                        height: 100,
                        width: 100,
                        child: Text('Oh no, an error occurred. Please see logs.'),
                      ),
                    );
                  },
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8),
              child: Row(
                children: [
                  Expanded(
                    child: TextField(
                      controller: messageInputController.textFieldController,
                      decoration: const InputDecoration(
                        hintText: 'Enter your message',
                      ),
                    ),
                  ),
                  Material(
                    type: MaterialType.circle,
                    color: Colors.blue,
                    clipBehavior: Clip.hardEdge,
                    child: InkWell(
                      onTap: () async {
                        if (messageInputController.text.isNotEmpty) {
                          await channel.sendMessage(
                            messageInputController.message,
                          );
                          messageInputController.clear();
                          if (mounted) {
                            _updateList();
                          }
                        }
                      },
                      child: const Padding(
                        padding: EdgeInsets.all(8),
                        child: Center(
                          child: Icon(
                            Icons.send,
                            color: Colors.white,
                          ),
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

5. 扩展功能

你可以通过扩展 StreamChatClient 来添加更多功能:

extension on StreamChatClient {
  String get uid => state.currentUser!.id;
}

结论

通过以上步骤,你已经成功集成了 stream_chat_flutter_core 插件,并实现了一个简单的聊天应用。你可以根据需求进一步自定义和扩展这个应用。更多详细信息可以参考官方文档


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

1 回复

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


当然,下面是一个关于如何在Flutter项目中使用stream_chat_flutter_core插件来实现即时通讯功能的代码示例。这个示例将涵盖基本的设置、用户认证、频道创建和消息发送等核心功能。

1. 添加依赖

首先,在pubspec.yaml文件中添加stream_chat_flutter_core及其依赖项:

dependencies:
  flutter:
    sdk: flutter
  stream_chat_flutter_core: ^latest_version  # 请替换为最新版本号
  stream_chat_flutter: ^latest_version  # UI组件库,如果需要的话

2. 配置Stream Chat

在你的Flutter应用的入口文件(通常是main.dart)中,配置Stream Chat:

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

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // 初始化Stream Chat客户端
  final client = StreamChatClient(
    apiKey: 'your_api_key', // 替换为你的Stream API Key
    appId: 'your_app_id',   // 替换为你的Stream App ID
  );

  // 设置全局客户端实例(可选,但推荐)
  StreamChat.of(context).client = client;

  runApp(MyApp());
}

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

3. 用户认证

假设你已经有一个用户系统,可以使用Stream Chat的connectUser方法进行用户认证:

class ChatScreen extends StatefulWidget {
  @override
  _ChatScreenState createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> {
  late StreamChatClient client;

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

    client = StreamChat.of(context).client!;

    // 假设用户已经登录,这里使用模拟数据
    final user = User(
      id: 'user-123',
      name: 'John Doe',
      imageURL: 'https://example.com/avatar.png',
    );

    client.connectUser(user, token: 'user-token-123').then((session) {
      // 用户连接成功后的逻辑
      print('User connected: ${session.user.id}');
    }).catchError((error) {
      // 处理错误
      print('Error connecting user: $error');
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Chat Screen')),
      body: Center(child: Text('Connecting user...')), // 暂时显示连接中的状态
    );
  }
}

4. 创建频道并发送消息

在成功连接用户后,可以创建一个频道并向其中发送消息:

class _ChatScreenState extends State<ChatScreen> {
  late StreamChatClient client;
  late Channel? channel;

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

    client = StreamChat.of(context).client!;

    // 用户连接逻辑(同上)
    client.connectUser(User(
      id: 'user-123',
      name: 'John Doe',
      imageURL: 'https://example.com/avatar.png',
    ), token: 'user-token-123').then((session) {
      // 创建频道
      channel = client.channel('messaging', 'channel-id-123',
          extraData: {'name': 'General Chat'});

      // 发送消息
      channel!.sendMessage(
        Message(
          text: 'Hello, this is a test message!',
          user: session.user,
        ),
      ).then((message) {
        // 消息发送成功后的逻辑
        print('Message sent: ${message.id}');
      }).catchError((error) {
        // 处理错误
        print('Error sending message: $error');
      });
    }).catchError((error) {
      // 处理错误
      print('Error connecting user: $error');
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Chat Screen')),
      body: Center(
        child: channel != null
            ? Text('Channel created and message sent!')
            : Text('Connecting user and creating channel...'),
      ),
    );
  }
}

5. 显示聊天界面(可选)

如果你希望显示一个完整的聊天界面,可以使用stream_chat_flutter包提供的UI组件。这里是一个简单的例子:

import 'package:stream_chat_flutter/stream_chat_flutter.dart';

class ChatScreen extends StatefulWidget {
  @override
  _ChatScreenState createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> {
  late StreamChatClient client;
  late Channel? channel;

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

    client = StreamChat.of(context).client!;

    // 用户连接和频道创建逻辑(同上)
    // ...

    // 假设这些步骤已经完成,直接跳转到聊天界面
    Future.wait([userConnectionFuture, channelCreationFuture]).then((_) {
      // 使用Stream Chat的UI组件显示聊天界面
      Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => ChatScreenWidget(
            channel: channel!,
          ),
        ),
      );
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Loading Chat...')),
    );
  }
}

class ChatScreenWidget extends StatelessWidget {
  final Channel channel;

  ChatScreenWidget({required this.channel});

  @override
  Widget build(BuildContext context) {
    return ChatScreen(
      channel: channel,
      messagesController: MessagesController(
        channel: channel,
      ),
    );
  }
}

请注意,上述代码中的userConnectionFuturechannelCreationFuture是模拟的Future对象,你需要根据实际情况替换为实际的Future逻辑。

以上代码提供了一个基本的框架,展示了如何在Flutter应用中使用stream_chat_flutter_core插件进行即时通讯。根据具体需求,你可能需要调整和完善这些代码。

回到顶部