前言
作为当下风头正劲的跨端框架,flutter成为原生开发者和前端开发者争相试水的领域,笔者将通过一个仿微信聊天的应用,展现flutter的开发流程和相关工具链,旨在熟悉flutter的开发生态,同时也对自己的学习过程进行一个总结。笔者是web前端开发,相关涉及原生的地方难免有错漏之处,欢迎批评指正。项目代码库链接放在文末。
功能简介
-
聊天列表
本应用支持用户直接点对点聊天,使用webSocket实现消息提醒与同步
好友列表页:
在聊天列表展示所有好友,点击进入聊天详情,未读消息通过好友头像右上角小红点表示。
聊天页:
-
搜索页
用户可以通过搜索添加好友:
-
个人中心页
该页面可以进行个人信息的修改,包括调整昵称,头像,修改密码等等,同时可以退出登录。
工具链梳理
这里列举了本例中使用的几个关键第三方库,具体的使用细节在功能实现部分会有详解。
- 消息同步与收发
项目中使用webSocket同server进行通信,我的服务器是用node
写的,webSocket使用socket.io
来实现(详见文末链接),socket.io
官方最近也开发了基于dart的配套客户端库socket_io_client
,其与服务端配合使用。由此可来实现消息收发和server端事件通知。 - 状态管理
- 持久化状态管理
持久化状态指的是用户名、登录态、头像等等持久化的状态,用户退出app之后,不用重新登录应用,因为登录态已经保存在本地,这里使用的是一个轻量化的包shared_preferences
,将持久化的状态通过写文件的方式保存在本地,每次应用启动的时候读取该文件,恢复用户状态。 - 非持久化状态
这里使用社区广泛使用的库provider
来进行非持久化的状态管理,非持久化缓存指的是控制app展示的相关状态,例如用户列表、消息阅读态以及依赖接口的各种状态等等。笔者之前也有一篇博文对provider
进行了介绍Flutter Provider使用指南
- 网络请求
这里使用dio
进行网络请求,进行了简单的封装 - 其他
-
手机桌面消息通知小红点通过
flutter_app_badger
包来实现,效果如下:
-
修改用户头像时,获取本地相册或调用照相机,使用
image_picker
库来实现,图片的裁剪通过image_cropper
库来实现 -
网络图片缓存,使用
cached_network_image
来完成,避免使用图片时反复调用http服务
功能实现
- 应用初始化
在打开app时,首先要进行初始化,请求相关接口,恢复持久化状态等。在main.dart文件的开头,进行如下操作:
为了避免文章充斥着大段具体业务代码影响阅读体验,本文的代码部分只会列举核心内容,部分常见逻辑和样式内容会省略,完整代码详见项目仓库
import 'global.dart';
...
// 在运行runApp,之间,运行global中的初始化操作
void main() => Global.init().then((e) => runApp(MyApp(info: e)));
接下来我们查看global.dart
文件
library global;
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
...
// 篇幅关系,省略部分包引用
// 为了避免单文件过大,这里使用part将文件拆分
part './model/User.dart';
part './model/FriendInfo.dart';
part './model/Message.dart';
// 定义Profile,其为持久化存储的类
class Profile {
String user = '';
bool isLogin = false;
// 好友申请列表
List friendRequest = [];
// 头像
String avatar = '';
// 昵称
String nickName = '';
// 好友列表
List friendsList = [];
Profile();
// 定义fromJson的构造方法,通过json还原Profile实例
Profile.fromJson(Map json) {
user = json['user'];
isLogin = json['isLogin'];
friendRequest = json['friendRequest'];
avatar = json['avatar'];
friendsList = json['friendsList'];
nickName = json['nickName'];
}
// 定义toJson方法,将实例转化为json方便存储
Map<String, dynamic> toJson() => {
'user': user,
'isLogin': isLogin,
'friendRequest': friendRequest,
'avatar': avatar,
'friendsList': friendsList,
'nickName': nickName
};
}
// 定义全局类,实现初始化操作
class Global {
static SharedPreferences _prefs;
static Profile profile = Profile();
static Future init() async {
// 这里使用了shared_preferences这个库辅助持久化状态存储
_prefs = await SharedPreferences.getInstance();
String _profile = _prefs.getString('profile');
Response message;
if (_profile != null) {
try {
// 如果存在用户,则拉取聊天记录
Map decodeContent = jsonDecode(_profile != null ? _profile : '');
profile = Profile.fromJson(decodeContent);
message = await Network.get('getAllMessage', { 'userName' : decodeContent['user'] });
} catch (e) {
print(e);
}
}
String socketIODomain = 'http://testDomain';
// 生成全局通用的socket实例,这个是消息收发和server与客户端通信的关键
IO.Socket socket = IO.io(socketIODomain, <String, dynamic>{
'transports': ['websocket'],
'path': '/mySocket'
});
// 将socket实例和消息列表作为结果返回
return {
'messageArray': message != null ? message.data : [],
'socketIO': socket
};
}
// 定义静态方法,在需要的时候更新本地存储的数据
static saveProfile() => _prefs.setString('profile', jsonEncode(profile.toJson()));
}
...
global.dart文件中定义了Profile类,这个类定义了用户的持久化信息,如头像、用户名、登录态等等,Profilet类还提供了将其json化和根据json数据还原Profile实例的方法。Global类中定义了整个应用的初始化方法,首先借助shared_preferences
库,读取存储的json化的Profile数据,并将其还原,从而恢复用户状态。Global中还定义了saveProfile方法,供外部应用调用,以便更新本地存储的内容。在恢复本地状态后,init方法还请求了必须的接口,创建全局的socket实例,将这两者作为参数传递给main.dart中的runApp方法。global.dart内容过多,这里使用了part
关键字进行内容拆分,UserModel等类的定义都拆分出去了,详见笔者的另一篇博文dart flutter 文件与库的引用导出
- 状态管理
接下来我们回到main.dart中,观察MyApp类的实现:
class MyApp extends StatelessWidge