Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

Flutter实战一Flutter聊天应用(九)

$
0
0

在这篇文章中,我们将允许用户在聊天消息中发送图像,从设备检索图像文件,并将文本和图像数据存储在Google云端存储Bucket中。由于我们使用Firebase云储存,应用程序将变得更加健壮和可扩展。它能够在上传和下载期间处理网络中断,安全地存储数据,并在用户群扩展时保持相同的性能。

要将数据(如文本和照片)从移动设备上传到云端,我们需要使用firebase_storage插件。在main.dart文件中,确保导入相应的包。

import 'package:firebase_storage/firebase_storage.dart';

要访问存储在移动设备上的数据并在Flutter应用程序中使用它,我们需要Flutter的平台服务API和image_picker插件。在main.dart文件中,导入image_picker包。另外还要导入dart:mathdart: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 DescriptionPrivacy - 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配置文件,所以我们现在需要重新启动应用程序,而不是重新加载。

作者:hekaiyou 发表于2017/6/14 15:38:53 原文链接
阅读:31 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles