在这篇文章中,我们将允许用户在聊天消息中发送图像,从设备检索图像文件,并将文本和图像数据存储在Google云端存储Bucket中。由于我们使用Firebase云储存,应用程序将变得更加健壮和可扩展。它能够在上传和下载期间处理网络中断,安全地存储数据,并在用户群扩展时保持相同的性能。
要将数据(如文本和照片)从移动设备上传到云端,我们需要使用firebase_storage
插件。在main.dart
文件中,确保导入相应的包。
import 'package:firebase_storage/firebase_storage.dart';
要访问存储在移动设备上的数据并在Flutter应用程序中使用它,我们需要Flutter的平台服务API和image_picker
插件。在main.dart
文件中,导入image_picker
包。另外还要导入dart:math
和dart:io
库,用于在设备上生成随机文件名和处理文件操作。
import 'package:image_picker/image_picker.dart';
import 'dart:math';
import 'dart:io';
此时,我们的应用程序可以让用户发送和接收消息。现在,我们将在用户界面中添加一个按钮来撰写消息,使用户可以从应用访问设备的相机。然后我们将给UI处理二进制数据的能力。
需要注意的是,如果我们正在iOS设备上进行测试,那么需要添加一个设置,让图像选择器插件访问相机和设备上存储的图像。要启用访问,需要将以下条目添加到应用程序的Info.plist
文件的主字典中。
<dict>
<key>NSCameraUsageDescription</key>
<string>Share images with other chat users</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Share images with other chat users</string>
...
</dict>
<string>
元素可以包含任何文本值。我们还可以在Xcode中添加这些设置,使用Privacy - Camera Usage Description
和Privacy - Photo Library Usage Description
属性的新行。
用户需要一种访问存储在设备上的图像的方法,我们将在用于撰写聊天消息的UI旁边创建一个按钮。应用程序UI的这一部分由ChatScreenState
中的私有方法_buildTextComposer()
定义。添加一个IconButton
小部件,用于访问相机和存储的图像到_buildTextComposer
方法。输入字段和发送按钮的现有Row
控件应该是父级,将IconButton
控件包装在新的Container
控件中,Container
控件让我们能自定义按钮的边距间距,使其在输入字段旁边更好看。
class ChatScreenState extends State<ChatScreen> {
//...
Widget _buildTextComposer() {
return new IconTheme(
data: new IconThemeData(color: Theme.of(context).accentColor),
child: new Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
child: new Row(
children: <Widget> [
new Container(
margin: new EdgeInsets.symmetric(horizontal: 4.0),
child: new IconButton(
icon: new Icon(Icons.photo_camera),
onPressed: (){}
),
),
new Flexible(
//...
),
new Container(
//...
)
]
)
)
);
}
//...
}
在icon
属性中,使用Icons.photo_camera
常量创建一个新的Icon
实例,此常量可以让控件使用Flutter素材图标库提供的“相机”图标。
现在修改相机按钮的onPressed
回调。我们需要引用Google云端存储Bucket,并在用户选择之后立即开始上传图像。对于按钮的onPressed
属性,我们将调用async
函数并将其与几个await
表达式组合,以特定顺序执行与图像相关的任务。
class ChatScreenState extends State<ChatScreen> {
//...
Widget _buildTextComposer() {
//...
child: new IconButton(
icon: new Icon(Icons.photo_camera),
onPressed: () async {
await _ensureLoggedIn();
File imageFile = await ImagePicker.pickImage();
}
),
//...
}
//...
}
在允许用户选择、上传和发送图像之前,确保通过执行私有_ensureLoggedIn()
方法登录。然后等待用户选择一个图像,并从pickImage()
方法获取一个名为imageFile
的新的File
对象。此方法由之前导入的Flutter Image Picker插件提供。它不需要参数,并返回一个名为path
的String值作为图像文件的URL。接下来,修改_sendMessage()
以添加imageUrl
参数。
class ChatScreenState extends State<ChatScreen> {
//...
void _sendMessage({ String text, String imageUrl }) {
reference.push().set({
'text': text,
'imageUrl': imageUrl,
'senderName': googleSignIn.currentUser.displayName,
'senderPhotoUrl': googleSignIn.currentUser.photoUrl,
});
analytics.logEvent(name: 'send_message');
}
//...
}
创建一个用于将文件上传到Google Cloud Storage的实例变量,初始化它以获取对File
对象的引用,并将其传递给put()
方法。给每个文件一个唯一的名称,使用image_
作为前缀,然后是随机整数。
class ChatScreenState extends State<ChatScreen> {
//...
Widget _buildTextComposer() {
//...
child: new IconButton(
icon: new Icon(Icons.photo_camera),
onPressed: () async {
await _ensureLoggedIn();
File imageFile = await ImagePicker.pickImage();
int random = new Random().nextInt(100000);
StorageReference ref = FirebaseStorage.instance.ref().child("image_$random.jpg");
StorageUploadTask uploadTask = ref.put(imageFile);
Uri downloadUrl = (await uploadTask.future).downloadUrl;
}
),
//...
}
//...
}
put()
方法由Firebase的Cloud Storage API定义,它需要一个File
对象作为参数,并将其上传到Google云端存储Bucket。之前导入的Flutter Firebase Storage插件提供对该API的访问,并定义了StorageUploadTask
类。上传完成后,您可以获取图像的downloadURL
,现在调用_sendMessage()并传递downloadURL来发送图像。
class ChatScreenState extends State<ChatScreen> {
//...
Widget _buildTextComposer() {
//...
child: new IconButton(
icon: new Icon(Icons.photo_camera),
onPressed: () async {
await _ensureLoggedIn();
File imageFile = await ImagePicker.pickImage();
int random = new Random().nextInt(100000);
StorageReference ref = FirebaseStorage.instance.ref().child("image_$random.jpg");
StorageUploadTask uploadTask = ref.put(imageFile);
Uri downloadUrl = (await uploadTask.future).downloadUrl;
_sendMessage(imageUrl: downloadUrl.toString());
}
),
//...
}
//...
}
这个URL用于应用程序UI从Google云端存储下载图像到本地,让发送人在聊天对话中查看他们发送的图像。发送或接收的每个聊天消息都需要能够显示图像和文本。接下来,我们将增强ChatMessage
类,以检测用户何时发送图像并使用其URL获取图像。
class ChatMessage extends StatelessWidget {
//...
@override
Widget build(BuildContext context) {
return new SizeTransition(
//...
child: new Container(
margin: const EdgeInsets.symmetric(vertical: 10.0),
child: new Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
//...
new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(
snapshot.value['senderName'],
style: Theme.of(context).textTheme.subhead),
new Container(
margin: const EdgeInsets.only(top: 5.0),
child: snapshot.value['imageUrl'] != null ?
new Image.network(
snapshot.value['imageUrl'],
width: 250.0,
):
new Text(snapshot.value['text']),
)
]
)
]
)
)
);
}
}
在上面的代码中,如果数据库行的值字段是imageUrl
,那么应用程序将创建一个Image
窗口控件,并将其与图像的内容填充。为了一致性,将宽度设置为特定数量的逻辑像素。如果消息不包含图像,则应用程序将在完成此步骤之前创建一个Text
窗口控件。
因为我们在更改了iOS的Info.plist
配置文件,所以我们现在需要重新启动应用程序,而不是重新加载。