使本地图像直接出现在聊天中,而不是等待网络图像(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)的主要内容,如果未能解决你的问题,请参考以下文章