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

相关推荐
ujainu37 分钟前
护眼又美观:Flutter + OpenHarmony 鸿蒙记事本一键切换夜间模式(四)
android·flutter·harmonyos
ujainu38 分钟前
让笔记触手可及:为 Flutter + OpenHarmony 鸿蒙记事本添加实时搜索(二)
笔记·flutter·openharmony
一只大侠的侠38 分钟前
Flutter开源鸿蒙跨平台训练营 Day 13从零开发注册页面
flutter·华为·harmonyos
一只大侠的侠1 小时前
Flutter开源鸿蒙跨平台训练营 Day19自定义 useFormik 实现高性能表单处理
flutter·开源·harmonyos
恋猫de小郭2 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
一只大侠的侠7 小时前
Flutter开源鸿蒙跨平台训练营 Day 10特惠推荐数据的获取与渲染
flutter·开源·harmonyos
renke336410 小时前
Flutter for OpenHarmony:色彩捕手——基于HSL色轮与感知色差的交互式色觉训练系统
flutter
子春一12 小时前
Flutter for OpenHarmony:构建一个 Flutter 四色猜谜游戏,深入解析密码逻辑、反馈算法与经典益智游戏重构
算法·flutter·游戏
铅笔侠_小龙虾13 小时前
Flutter 实战: 计算器
开发语言·javascript·flutter
微祎_14 小时前
Flutter for OpenHarmony:构建一个 Flutter 重力弹球游戏,2D 物理引擎、手势交互与关卡设计的工程实现
flutter·游戏·交互