Flutter和Firebae简单的聊天应用

Firebase

Firebase是由Google提供的一套后端服务平台,主要为移动端(如 Android、iOS)和Web应用开发者提供强大、易用的基础设施,帮助你更快速地构建应用、提高质量并扩展用户群体。Flutter和Firebase的组合能够应该是全栈工程师的一种选择。

Add Firebase to your Flutter app

第一步 安装所需的命令行工具

  1. 如果您尚未安装 Firebase CLI,请先安装。
  2. 运行以下命令,使用您的 Google 账号登录 Firebase:firebase login
  3. 从任何目录运行以下命令来安装 FlutterFire CLI:dart pub global activate flutterfire_cli

第二步 将应用配置为使用 Firebase

使用FlutterFire CLI将您的Flutter 应用配置为连接到Firebase。从Flutter项目目录运行以下命令,启动应用配置工作流:flutterfire configure 这个步骤也就是将Flutter项目和Firebase项目关联起来。

第三步 将firebase plgin增加到项目配置表中

Firebase添加项目

创建命名通俗易懂的项目

如下图所示:

点击继续,可以关闭启用Gemini in Firebase,点击继续,可以关闭为此项目启用Google Analytics,Firebase项目就成功创建了。

Firebase项目启用认证

按照上图中的1,2,3,4步骤开启需要登陆方法。

Firebase增加用户

按照上图中的1,2,3步骤增加邮箱和密码的用户。我这里新增了两个用户来实现和演示用户消息从未读变为已读的过程。

Firebase Realtime Database

Firebase Realtime Database是Firebase提供的一个基于云端的NoSQL数据库,支持数据的实时同步。它特别适合需要多客户端协同的应用,比如聊天、协作编辑、在线游戏等。存储的是非结构化的数据,数据格式以json数据格式。json嵌套不能超过32层级,json数据格式层级过多会有性能问题,建议数据格式尽量扁平化。json中的数据和不能保存聚合的数据格式。

Firebase Realtime Structure

Firebase数据结构如下图所示,chats代表两个人之间的聊天,两个人的uid关联下面的messages代表聊天列表,一条聊天记录内容包括发送者,发送时间,发送文本内容,readBy包裹的内容代表接受者,以及接受者是否查看。users代表用户的数据,uid包裹用户的信息,name代表名称,friends代表朋友列表。

代码结构图

示例代码:

dart 复制代码
class Friend{
  final String uid;
  final String name;
  Friend({required this.uid, required this.name});
}
ini 复制代码
import 'package:flutter/foundation.dart';

import 'friend.dart';

class UserUid with ChangeNotifier{
  String? _uid;
  String? get uid => _uid;
  Friend? _friend;
  Friend? get friend=> _friend;

  void login(String uid) {
    _uid = uid;
    notifyListeners();
  }

  void logout() {
    _uid = null;
    notifyListeners();
  }

  void setFriend(Friend? friend) {
    _friend = friend;
    notifyListeners();
  }


}
less 复制代码
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import '../constants.dart';
import '../models/user_uid.dart';

final _auth = FirebaseAuth.instance;

String generateChatId(String uid1, String uid2) {
  final uids = [uid1, uid2]..sort();
  return '${uids[0]}_${uids[1]}';
}

Future<void> sendMessage(String myUid, String otherUid, String text) async {
  final chatId = generateChatId(myUid, otherUid);
  final ref = FirebaseDatabase.instance.ref('chats/$chatId/messages').push();

  await ref.set({
    'senderId': myUid,
    'text': text,
    'timestamp': ServerValue.timestamp,
    'readBy': {otherUid: false},
  });
}

class ChatScreen extends StatefulWidget {
  static const String id = 'chat_screen';

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

class _ChatScreenState extends State<ChatScreen> {
  late String messageText;
  late TextEditingController controller = TextEditingController();


  @override
  void initState() {
    // TODO: implement initState
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: null,
        actions: <Widget>[
          IconButton(
              icon: Icon(Icons.close),
              onPressed: () {
                //Implement logout functionality
                print(
                    'logout pressed email is ${_auth.currentUser!.toString()}');
                _auth.signOut();
                Navigator.pop(context);
              }),
        ],
        title: Text(context.read<UserUid>().friend!.name),
        backgroundColor: Colors.lightBlueAccent,
      ),
      body: SafeArea(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            MessageStream(),
            Container(
              decoration: kMessageContainerDecoration,
              child: Row(
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  Expanded(
                    child: TextField(
                      controller: controller,
                      onChanged: (value) {
                        //Do something with the user input.
                        messageText = value;
                      },
                      decoration: kMessageTextFieldDecoration,
                    ),
                  ),
                  FloatingActionButton(
                    onPressed: () {
                      //Implement send functionality.
                      sendMessage(_auth.currentUser!.uid, context.read<UserUid>().friend!.uid, messageText);
                      controller.clear();
                    },
                    child: Text(
                      'Send',
                      style: kSendButtonTextStyle,
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class MessageStream extends StatelessWidget {
  Stream<DatabaseEvent> getChatStream(String chatId) {
    return FirebaseDatabase.instance
        .ref('chats/$chatId/messages')
        .orderByChild('timestamp')
        .onValue;
  }



  @override
  Widget build(BuildContext context) {
    return StreamBuilder<DatabaseEvent>(
        stream: getChatStream(generateChatId(context.read<UserUid>().friend!.uid, _auth.currentUser!.uid)),
        builder: (value, snap) {
          List<MessageBubble> texts = [];
          if (!snap.hasData) {
            return Center(
              child: CircularProgressIndicator(
                backgroundColor: Colors.blueAccent,
              ),
            );
          }

          final data = snap.data?.snapshot.value;
          if (data == null || data is! Map) {
            return Center(child: Text("No messages yet"));
          }
          var messages = data.entries.toList();

          for (var message in messages) {
            var messageText = message.value['text'];
            var messageSender = message.value['senderId'];
            print(message.value);
            texts.add(
              MessageBubble(
                messageSender: messageSender == _auth.currentUser!.uid ? "Me" : context.read<UserUid>().friend!.name,
                messageText: messageText,
                isMe: messageSender == _auth.currentUser!.uid,
              ),
            );
          }
          return Expanded(
              child: ListView(
            padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 10.0),
            children: texts,
          ));
        });
  }
}

class MessageBubble extends StatelessWidget {
  MessageBubble(
      {required this.messageText,
      required this.messageSender,
      required this.isMe});

  final String messageText;
  final String messageSender;
  final bool isMe;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.all(10.0),
      child: Column(
        crossAxisAlignment:
            isMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
        children: [
          Text(
            messageSender,
            style: TextStyle(fontSize: 12.0, color: Colors.black54),
          ),
          Material(
            elevation: 5.0,
            borderRadius: isMe
                ? BorderRadius.only(
                    topLeft: Radius.circular(30.0),
                    bottomRight: Radius.circular(30.0),
                    bottomLeft: Radius.circular(30.0))
                : BorderRadius.only(
                    bottomRight: Radius.circular(30.0),
                    bottomLeft: Radius.circular(30.0),
                    topRight: Radius.circular(30.0)),
            color: isMe ? Colors.lightBlueAccent : Colors.white,
            child: Padding(
              padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0),
              child: Text(
                messageText,
                style: TextStyle(
                  color: isMe ? Colors.white : Colors.black54,
                  fontSize: 20.0,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}
dart 复制代码
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:provider/provider.dart';

import '../models/friend.dart';
import '../models/user_uid.dart';
import 'chat_screen.dart';

final _auth = FirebaseAuth.instance;

class FriendListScreen extends StatefulWidget {
  static const String id = 'friend_list_screen';
  const FriendListScreen({super.key});

  @override
  State<FriendListScreen> createState() => _FriendListScreenState();
}

class _FriendListScreenState extends State<FriendListScreen> {
  Future<List<Friend>> getFriends() async {
    final ref = FirebaseDatabase.instance.ref();
    final snapshot = await ref
        .child('users/${_auth.currentUser!.uid}/friends')
        .get();
    print(snapshot.value! as Map<dynamic, dynamic>);
    List<Friend> friends = [];
    if (snapshot.exists) {
      Map<dynamic, dynamic> userMap = snapshot.value! as Map<dynamic, dynamic>;

      for (final entry in userMap.entries) {
        final key = entry.key;

        final user = await ref.child('users/$key').get();
        print('$key');
        print("${(user.value! as Map<dynamic, dynamic>)['name']}");
        friends.add(
          Friend(
            uid: key,
            name: (user.value! as Map<dynamic, dynamic>)['name'],
          ),
        );
      }
    } else {
      print('No data available.');
    }
    print('friends length is ${friends.length}');
    return friends;
  }

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

  @override
  Widget build(BuildContext context1) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Friends"),
        backgroundColor: Colors.blueAccent,
      ),
      body: FutureBuilder<List<Friend>>(
        future: getFriends(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(child: CircularProgressIndicator()); // 加载中
          }

          if (snapshot.hasError) {
            return Center(child: Text('出错啦: ${snapshot.error}'));
          }

          final messages = snapshot.data ?? [];

          if (messages.isEmpty) {
            return Center(child: Text('暂无消息'));
          }

          return ListView.builder(
            itemCount: messages.length,
            itemBuilder: (context, index) {
              final msg = messages[index];
              return Card(
                color: Colors.blueAccent,
                child: ListTile(title: Text(msg.name), onTap: () {
                  context.read<UserUid>().setFriend(msg);
                  Navigator.pushNamed(context, ChatScreen.id);
                },),
              );
            },
          );
        },
      ),
    );
  }
}
less 复制代码
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_chat/screens/friend_list_screen.dart';
import 'package:provider/provider.dart';

import '../models/user_uid.dart';



class LoginScreen extends StatefulWidget {
  static const String id = 'login_screen';
  @override
  _LoginScreenState createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  late String email = "";
  late String password = '';
  final _auth = FirebaseAuth.instance;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: Padding(
        padding: EdgeInsets.symmetric(horizontal: 24.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            Hero(
              tag: 'logo',
              child: Container(
                height: 200.0,
                child: Image.asset('images/logo.png'),
              ),
            ),
            SizedBox(
              height: 48.0,
            ),
            TextField(
              onChanged: (value) {
                //Do something with the user input.
                email = value;
              },
              decoration: InputDecoration(
                hintText: 'Enter your email',
                contentPadding:
                    EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0),
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.all(Radius.circular(32.0)),
                ),
                enabledBorder: OutlineInputBorder(
                  borderSide:
                      BorderSide(color: Colors.lightBlueAccent, width: 1.0),
                  borderRadius: BorderRadius.all(Radius.circular(32.0)),
                ),
                focusedBorder: OutlineInputBorder(
                  borderSide:
                      BorderSide(color: Colors.lightBlueAccent, width: 2.0),
                  borderRadius: BorderRadius.all(Radius.circular(32.0)),
                ),
              ),
            ),
            SizedBox(
              height: 8.0,
            ),
            TextField(
              onChanged: (value) {
                //Do something with the user input.
                password = value;
              },
              decoration: InputDecoration(
                hintText: 'Enter your password.',
                contentPadding:
                    EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0),
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.all(Radius.circular(32.0)),
                ),
                enabledBorder: OutlineInputBorder(
                  borderSide:
                      BorderSide(color: Colors.lightBlueAccent, width: 1.0),
                  borderRadius: BorderRadius.all(Radius.circular(32.0)),
                ),
                focusedBorder: OutlineInputBorder(
                  borderSide:
                      BorderSide(color: Colors.lightBlueAccent, width: 2.0),
                  borderRadius: BorderRadius.all(Radius.circular(32.0)),
                ),
              ),
            ),
            SizedBox(
              height: 24.0,
            ),
            Padding(
              padding: EdgeInsets.symmetric(vertical: 16.0),
              child: Material(
                color: Colors.lightBlueAccent,
                borderRadius: BorderRadius.all(Radius.circular(30.0)),
                elevation: 5.0,
                child: MaterialButton(
                  onPressed: () async {
                    //Implement login functionality.
                    print('email is $email password is $password');
                    try {
                      var user = await _auth.signInWithEmailAndPassword(
                          email: email, password: password);
                      // Navigator.pushNamed(context, ChatScreen.id);

                      if (user != null) {
                        print('user uid is ${user.user!.uid}');
                        print('user is $user');
                        context.read<UserUid>().login(user.user!.uid);
                        Navigator.pushNamed(context, FriendListScreen.id);
                      }
                    } catch (e) {
                      print(e);
                    }
                  },
                  minWidth: 200.0,
                  height: 42.0,
                  child: Text(
                    'Log In',
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

参考资料

firebase.google.com/

firebase.google.com/docs/flutte...

blog.logrocket.com/how-to-buil...

firebase.google.com/docs/databa...

firebase.google.com/docs/databa...

firebase.blog/posts/2013/...

相关推荐
木叶丸21 分钟前
Flutter 生命周期完全指南
android·flutter·ios
用户74279877375934 分钟前
[flutter翻书效果] 用flutter实现一个书籍翻页效果
flutter
攀登的牵牛花4 小时前
Electron+Vue+Python全栈项目打包实战指南
前端·electron·全栈
小蜜蜂嗡嗡1 天前
flutter封装vlcplayer的控制器
前端·javascript·flutter
你听得到111 天前
从需求到封装:手把手带你打造一个高复用、可定制的Flutter日期选择器
前端·flutter
哲科软件2 天前
跨平台开发的抉择:Flutter vs 原生安卓(Kotlin)的优劣对比与选型建议
android·flutter·kotlin
天涯海风2 天前
Kuikly 与 Flutter 的全面对比分析,结合技术架构、性能、开发体验等核心维度
flutter·kuikly
aiprtem2 天前
基于Flutter的web登录设计
前端·flutter
coder_pig2 天前
跟🤡杰哥一起学Flutter (三十四、玩转Flutter手势✋)
前端·flutter·harmonyos