First of all, I am a complete beginner (not only in dart/flutter), but in programming in general. I am currently working on a chat app.
Goal: When I am picking an image within the ChatScreen - analog to e.g. WhatsApp - it should immediately appear as a message (placeholder with loading symbol, while it is uploaded to Firestore).
Problem: Everything is working fine, but here is one issue: When I am picking an image via the imagepicker, it get's uploaded but there's a delay until it is displayed as a message in the chat. imagepicker -> write to Firestore -> Streambuilder (listView.builder).
My attempt: I am using CachedNetworkImage, but unfortunately, it keep's showing the placeholder (CircularProgressIndicator), but not the actual image. If I am just displaying the image, everything works fine (besides the delay).
Moreover, I would highly appreciate some ideas to make the app run faster / improve the code ;-).
//Chat screen which lists all the chat messages, including _handleSubmitted and the _buildTextComposer
import 'dart:math';
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:transparent_image/transparent_image.dart';
import 'package:flutter_image/network.dart';
import 'package:flutter_native_image/flutter_native_image.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: 100, percentage: 100);
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: new 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))
],
),
//can be deleted. just to test the picture.
(document['text'] == null)
? new Container(
child: new ClipRRect(
borderRadius:
new BorderRadius.circular(
7.0),
child: CachedNetworkImage(
placeholder: Container(
child:
CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<
Color>(
Colors.orange),
),
width: 200.0,
height: 200.0,
padding: EdgeInsets.all(70.0),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius:
BorderRadius.all(
Radius.circular(8.0),
),
),
),
imageUrl:
'https://wuppertal-total.de/wp-content/uploads/2019/01/stamp_schmidt_fdp.jpg',
width: 200.0,
height: 200.0,
fit: BoxFit.cover,
),
),
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)),
),
],
),
),
],
),
);
},
)),
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(),
//"timestamp": FieldValue.serverTimestamp()
});
});
//Let the chat list jump to the newest chat message at the bottom
_scrollController.animateTo(
0.0,
curve: Curves.easeOut,
duration: const Duration(milliseconds: 300),
);
}
}
it worked for my case. I used cached_network_image: ^2.0.0
flutter clean
Quit app on emulator, re-run the app.
For me the solution was to downgrade the version to
cached_network_image: ^1.1.1
and run:
flutter clean
In the newest version it didn't work at all, even after switching the order as described in the other answer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With