使本地图像直接出现在聊天中,而不是等待网络图像(Streambuilder / Firebase)

Posted

技术标签:

【中文标题】使本地图像直接出现在聊天中,而不是等待网络图像(Streambuilder / Firebase)【英文标题】:Making a local image directly appear in a chat and not waiting for network image (Streambuilder / Firebase) 【发布时间】:2019-06-07 04:37:49 【问题描述】:

请为初学者解释一下;-)。

目标:当通过 WhatsApp 发送图片时,它会直接出现在聊天中(WhatsApp 不会“等待”上传)。我想要同样的效果。

我的问题是,图像是通过 imagepicker 选择的,然后在聊天中显示之前会有延迟,因为 Streambuilder -> ListView.builder 仅在将其写入数据库时​​才显示)。

目前,我有以下步骤:

    图像选择器 图像文件被压缩并上传(异步,等待...) 图像存储在 Firebase 中 文件的 url 被接收并写入 Firestore 数据库,作为消息的字段通过 _handleSubmitted Streambuilder -> Listview.builder 显示所有消息(通过 CachedNetworkImage 的图像),作为快照通过 (document["imageUrl"]) 接收

我的想法:首先,在 Listview.builder 中显示来自 imagepicker 的本地图像,然后将其替换为网络图像(平滑过渡)。

但自从我在 3 周前开始编程以来,我不知道如何去做,甚至不知道如何操作 Listview.Builder 中的列表项。

//Chat screen which lists all the chat messages, including _handleSubmitted and the _buildTextComposer

import 'dart:io';

import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:intl/intl.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_native_image/flutter_native_image.dart';
import 'package:first_app/image_large.dart';

final database = Firestore.instance
    .collection('nachrichten')
    .document('G5xlQHvb56ZqpWs7ojUV');
final reference = FirebaseDatabase.instance.reference().child('messages');

class ChatScreen extends StatefulWidget 
  @override
  State createState() => new ChatScreenState();


class ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin 
  final TextEditingController _textController = new TextEditingController();
  final ScrollController _scrollController = new ScrollController();

  bool _isComposing = false;
  bool isLoading;
  String imageUrl;
  File imageFile;

  //optional from other code
  @override
  void initState() 
    super.initState();
    imageUrl = '';
  

  Future getImage() async 
    imageFile = await ImagePicker.pickImage(source: ImageSource.camera);

    if (imageFile != null) 
      setState(() 
        isLoading = true;
      );
      uploadFile();
    
  

  Future uploadFile() async 
    //file compression
    File compressedFile = await FlutterNativeImage.compressImage(imageFile.path,
        quality: 50, percentage: 50);
    String fileName = DateTime.now().millisecondsSinceEpoch.toString();
    StorageReference reference = FirebaseStorage.instance.ref().child(fileName);
    StorageUploadTask uploadTask = reference.putFile(compressedFile);
    StorageTaskSnapshot storageTaskSnapshot = await uploadTask.onComplete;
    storageTaskSnapshot.ref.getDownloadURL().then((downloadUrl) 
      imageUrl = downloadUrl;
      setState(() 
        isLoading = false;
        _handleSubmitted(imageUrl: imageUrl);
      );
    , onError: (err) 
      setState(() 
        isLoading = false;
      );
      Fluttertoast.showToast(msg: 'This file is not an image');
    );
  

  //Builds the button text composer, including camera icon, text input and send button
  Widget _buildTextComposer() 
    return new IconTheme(
      data: new IconThemeData(color: Theme.of(context).accentColor),
      child: new Container(
          margin: const EdgeInsets.symmetric(horizontal: 0.80),
          child: new Row(children: <Widget>[
            new Container(
              margin: new EdgeInsets.symmetric(horizontal: 0.4),
              child: new IconButton(
                icon: new Icon(Icons.photo_camera),
                onPressed: getImage,
              ),
            ),
            new Flexible(
              child: Container(
                margin: const EdgeInsets.symmetric(vertical: 10.0),
                padding: EdgeInsets.all(10.0),
                decoration: new BoxDecoration(
                  border: Border.all(color: Colors.grey.shade200),
                  borderRadius: new BorderRadius.circular(20.0),
                ),

                //container with constraint limits the maximum height of the text input field
                child: new Container(
                  constraints: BoxConstraints.loose(Size.fromHeight(100.0)),
                  child: new TextField(
                    maxLines: null,
                    keyboardType: TextInputType.multiline,
                    controller: _textController,
                    onChanged: (String text) 
                      setState(() 
                        _isComposing = text.length > 0;
                      );
                    ,
//                    onSubmitted: _handleSubmitted,
                    decoration: new InputDecoration.collapsed(
                        hintText: "Nachricht schreiben..."),
                  ),
                ),
              ),
            ),
            new Container(
                margin: new EdgeInsets.symmetric(horizontal: 0.4),
                child: Theme.of(context).platform == TargetPlatform.ios
                    ? new CupertinoButton(
                        child: new Text("Send"),
                        onPressed: _isComposing
                            ? () => _handleSubmitted(text: _textController.text)
                            : null,
                      )
                    : new IconButton(
                        icon: new Icon(Icons.send),
                        onPressed: _isComposing
                            ? () => _handleSubmitted(text: _textController.text)
                            : null,
                      )),
          ]),
          decoration: Theme.of(context).platform == TargetPlatform.iOS
              ? new BoxDecoration(
                  border:
                      new Border(top: new BorderSide(color: Colors.grey[200])))
              : null),
    );
  

  //Builds the actual chat screen with Scaffold
  Widget build(BuildContext context) 
    return new Scaffold(
      appBar: new AppBar(
          title: new Text("Chat.here Gruppenchat"),
          centerTitle: true,
          elevation:
              Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0),
      body: StreamBuilder<QuerySnapshot>(
          stream: Firestore.instance
              .collection('nachrichten')
              .orderBy('timestamp', descending: true)
              //  .limit(20)
              .snapshots(),
          builder:
              (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) 
            if (!snapshot.hasData) return Text('Loading data');
            final int documentsLength = snapshot.data.documents.length;
            return Container(
                child: Column(
                  children: <Widget>[
                    new Flexible(
                        child: ListView.builder(
                      controller: _scrollController,
                      reverse: true,
                      itemCount: documentsLength,
                      itemBuilder: (context, int index) 
                        final DocumentSnapshot document =
                            snapshot.data.documents[index];
                        return new Container(
                          margin: const EdgeInsets.symmetric(
                              horizontal: 10.0, vertical: 10.0),
                          child: new Row(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: <Widget>[
                              new Container(
                                margin: const EdgeInsets.only(right: 16.0),
                                child: new CircleAvatar(
                                    child: new Text(
                                        document['author'].substring(0, 1))),
                              ),
                              new Expanded(
                                child: new Column(
                                  crossAxisAlignment: CrossAxisAlignment.start,
                                  children: <Widget>[
                                    new Row(
                                      children: <Widget>[
                                        new Text(document['author'],
                                            style: TextStyle(
                                                fontSize: 12.0,
                                                color: Colors.black45,
                                                fontWeight: FontWeight.bold)),
                                        new Text(
                                            ' ' +
                                                DateFormat("MMM. d. '|' HH:mm")
                                                    .format(
                                                        document['timestamp']),
                                            style: TextStyle(
                                                fontSize: 12.0,
                                                color: Colors.black45))
                                      ],
                                    ),

                                    (document['text'] == null)
                                        ? new GestureDetector(
                                            onTap: () 
                                              Navigator.push(
                                                context,
                                                MaterialPageRoute(
                                                    builder: (context) =>
                                                        SecondScreen(
                                                            imageUrl: document[
                                                                'imageUrl'])),
                                              );
                                            ,
                                            child: new Container(
                                              child: new ClipRRect(
                                                borderRadius:
                                                    new BorderRadius.circular(
                                                        7.0),
                                                child: new CachedNetworkImage(
                                                  imageUrl:
                                                      document['imageUrl'],
                                                  placeholder: Container(
                                                    child:
                                                        CircularProgressIndicator(
                                                      valueColor:
                                                          AlwaysStoppedAnimation<
                                                                  Color>(
                                                              Colors.orange),
                                                    ),
                                                    width: 200.0,
                                                    height: 200.0,
                                                    padding:
                                                        EdgeInsets.all(70.0),
                                                    decoration: BoxDecoration(
                                                      borderRadius:
                                                          BorderRadius.all(
                                                        Radius.circular(8.0),
                                                      ),
                                                    ),
                                                  ),
                                                ),
                                              ),
                                              margin:
                                                  EdgeInsets.only(right: 50.0),
                                            ))
                                        : new Card(
                                            margin:
                                                EdgeInsets.only(right: 50.0),
                                            //green color for messages of yourself
                                            color:
                                                document['author'] == "Matthias"
                                                    ? Color.fromRGBO(
                                                        220, 255, 202, 1.0)
                                                    : null,
                                            child: new Container(
                                                padding: EdgeInsets.all(6.0),
                                                child: new Text(
                                                  document['text'],
                                                  style:
                                                      TextStyle(fontSize: 15.0),
                                                )),
                                            shape: RoundedRectangleBorder(
                                                borderRadius:
                                                    BorderRadius.circular(7.0)),
                                          ),

                                    //Container( child: image != null ? new Image.file(image, width: 250.0) : new Text("Kein Bild"))
                                  ],
                                ),
                              ),
                            ],
                          ),
                        );
                      ,
                    )),
                    new Divider(height: 1.0),
                    new Container(
                      decoration:
                          new BoxDecoration(color: Theme.of(context).cardColor),
                      child: _buildTextComposer(),
                    ),
                  ],
                ),
                decoration: Theme.of(context).platform == TargetPlatform.iOS
                    ? new BoxDecoration(
                        border: new Border(
                            top: new BorderSide(color: Colors.grey[200])))
                    : null);
          ),
    );
  

  void _handleSubmitted(String text, String imageUrl) 
    _textController.clear();
    setState(() 
      _isComposing = false;
    );

    //creation of an own document in Firestore
    Firestore.instance.runTransaction((Transaction transaction) async 
      CollectionReference reference =
          Firestore.instance.collection('nachrichten');

      await reference.add(
        "text": text,
        "author": "Matthias",
        "imageUrl": imageUrl,
        "timestamp": DateTime.now(),
      );
    );

    _scrollController.animateTo(
      0.0,
      curve: Curves.easeOut,
      duration: const Duration(milliseconds: 300),
    );
  

【问题讨论】:

【参考方案1】:

从设备上的位置用Image.file 显示它,直到上传完成。

例如用

FadeInImage(
  placeholder: const FileImage(pathToFile),
  image: NetworkImage(uploadedFileUrl),
  fit: BoxFit.cover,
  width: double.infinity,
  height: 256,
),

【讨论】:

我试过这个:FadeInImage(placeholder: const Image.file(imageFile.path), image: NetworkImage(document['imageUrl']), fit: BoxFit.cover, width: double.infinity, height: 256.0, ) ......IDE 给了我这个错误:参数“Image”不能分配给类型“ImageProvider”。被调用的构造函数不是 const 构造函数。 尝试Image.file 而不是FileImage。难以记住何时使用哪个。 已经用 Image.file 试过了 - 我现在试过 FileImage - 现在错误消失了,但整个聊天(文本+图像)在开始时消失了。然后当我发布图片时,它会全部加载。不过,在上传完成之前,我不知道如何实现占位符。 整个聊天(文本+图像)消失。听起来像一个不同的问题 如果我使用的是 CachedNetworkImage(imageUrl: document['imageUrl'], placeholder: Container(child: CircularProgressIndicator()),那么聊天就完美了。唯一的问题:图像延迟发布。如何当我的代码上传完成时,我可以“倾听”并知道吗?

以上是关于使本地图像直接出现在聊天中,而不是等待网络图像(Streambuilder / Firebase)的主要内容,如果未能解决你的问题,请参考以下文章

使用 strophe js 在聊天中发送图像

如何在 iPhone 设备中本地保存图像和数据

如何设置 MKPinAnnotation 使图像底部指向位置而不是图像中心

CNN原理

使 SliverAppBar 具有图像作为背景而不是颜色

如何使matlab图形的数据提示显示图像灰度颜色而不是其RGB值?