Firebase
Firebase是由Google提供的一套后端服务平台,主要为移动端(如 Android、iOS)和Web应用开发者提供强大、易用的基础设施,帮助你更快速地构建应用、提高质量并扩展用户群体。Flutter和Firebase的组合能够应该是全栈工程师的一种选择。
Add Firebase to your Flutter app
第一步 安装所需的命令行工具
- 如果您尚未安装 Firebase CLI,请先安装。
- 运行以下命令,使用您的 Google 账号登录 Firebase:firebase login
- 从任何目录运行以下命令来安装 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/docs/flutte...
blog.logrocket.com/how-to-buil...
firebase.google.com/docs/databa...