How to load image to the Card from data retrieved from async task in flutter? - firebase

I'm new to flutter development. I need to load images into a card depending on data loaded via async task.
I have an async task which returns Future> user data quired from the sqlite local database. With retrieved data, I build a ListView to show users using Card. But inside the card, I'm trying to show an image which will be downloaded from Firebase Storage depending on the data retrieved from the local database. But the image URL is null.
Widget build(BuildContext context) {
var allCards = DBProvider.db.getAllCards();
return FutureBuilder<List<User>>(
future: DBProvider.db.getAllCards(),
builder: (BuildContext context, AsyncSnapshot<List<User>> snapshot) {
if (snapshot.hasError) return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return new Text('Loading...');
default:
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
User user = snapshot.data[index];
return Card(
elevation: 8.0,
margin:
new EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0),
child: Column(
children: <Widget>[
Stack(
children: <Widget>[
Container(
child: Image(
image: CachedNetworkImageProvider(FirebaseStorage().ref().child('employer_logo').child('00001').child('google-banner.jpg').getDownloadURL().toString()),
fit: BoxFit.cover,
),
),
Positioned(
bottom: 0,
left: 0,
child: Container(
padding: EdgeInsets.all(10),
child: Text(
'Google Incorperation',
style: TextStyle(
fontSize: 20, color: Colors.white),
),
),
)
],
),
Container(
decoration: BoxDecoration(
color: Colors.white10,
),
child: ListTile(
title: Text(user.fname + " " + user.lname,
style: TextStyle(
color: Colors.blue[400], fontSize: 20)),
subtitle: Text(user.designation,
style: TextStyle(
color: Colors.blue[300], fontSize: 16)),
onTap: () => {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Profile(
user.fname,
user.lname,
user.uid,
user.designation,
user.mobile,
user.employerId)))
},
),
)
],
),
);
},
);
}
},
);
}
I expect to show images downloaded from firebase storage

This would be my first answer, and there are probably many ways to improve my answer here. But I will give it a go: Actually, you will have to look up a lot on Futuresand Streams, because it is quite a big part in many a app. If your app needs any content on the web, it will need Futures, or it's bigger counterpart Stream. In this case, where you want to set up a Listview with probably multiple images, I would go for a Stream. Also, I would save all my database logic in a seperate file. However, if you don't want to modify your code too much now, I would use a FutureBuilder.
I've seen you already use one of them in your code. But in this case, use:
...
int maxsize = 10e6.round(); // This is needed for getData. 10e^6 is usually big enough.
return new Card (
FutureBuilder<UInt8List> ( // I also think getting Data, instead of a DownloadUrl is more practical here. It keeps the data more secure, instead of generating a DownloadUrl which is accesible for everyone who knows it.
future: FirebaseStorage().ref().child('entire/path/can/go/here')
.getData(maxsize),
builder: (BuildContext context, AsyncSnapshot<UInt8List> snapshot) {
// When this builder is called, the Future is already resolved into snapshot.data
// So snapshot.data contains the not-yet-correctly formatted Image.
return Image.memory(data, fit: BoxFit.Cover);
},
),

Widget build(BuildContext context) {
var allCards = DBProvider.db.getAllCards();
return FutureBuilder<List<User>>(
future: DBProvider.db.getAllCards(),
builder: (BuildContext context, AsyncSnapshot<List<User>> snapshot) {
if (snapshot.hasError) return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return new Text('Loading...');
default:
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
User user = snapshot.data[index];
int maxsize = 10e6.round();
return Card(
elevation: 8.0,
margin:
new EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0),
child: Column(
children: <Widget>[
Stack(
children: <Widget>[
Container(
child: FutureBuilder<dynamic>(
future: FirebaseStorage()
.ref()
.child('employer_logo')
.child('00001')
.child('google-banner.jpg')
.getDownloadURL(),
builder: (BuildContext context,
AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState !=
ConnectionState.waiting) {
return Image(
image: CachedNetworkImageProvider(
snapshot.data.toString()),
fit: BoxFit.cover,
);
}
else {
return Text('Loading image....');
}
},
),
),
Positioned(
bottom: 0,
left: 0,
child: Container(
padding: EdgeInsets.all(10),
child: Text(
'Google Incorperation',
style: TextStyle(
fontSize: 20, color: Colors.white),
),
),
)
],
),
Container(
decoration: BoxDecoration(
color: Colors.white10,
),
child: ListTile(
title: Text(user.fname + " " + user.lname,
style: TextStyle(
color: Colors.blue[400], fontSize: 20)),
subtitle: Text(user.designation,
style: TextStyle(
color: Colors.blue[300], fontSize: 16)),
onTap: () => {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Profile(
user.fname,
user.lname,
user.uid,
user.designation,
user.mobile,
user.employerId)))
},
),
)
],
),
);
},
);
}
},
);
}

Related

How can I update Listview in Streambuilder in flutter

I have a streambuidler widget that contains a listview to display whom the current user has recently chatted with. When the current user receives a message that message should be pushed to the top of the listview, however that message is always display at the bottom of the list view, it's only display on the top if I refresh my screen.
NoSearchResultScreen() {
final Orientation orientation = MediaQuery.of(context).orientation;
print("hasAlreadyChatWithSomeone: $hasAlreadyChatWithSomeone");
return hasAlreadyChatWithSomeone
? StreamBuilder<QuerySnapshot>(
stream: (FirebaseFirestore.instance
.collection("user")
.where("id", isEqualTo: currentUserId)
.snapshots()),
builder: (context, snapshot) {
List<ProfileChatWith> chatWithList = [];
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: circularProgress(),
);
}
if (snapshot.hasData) {
final chatWithSnapshot = snapshot.data?.docs.first['chatWith'];
//print("chatWithSnapshot: $chatWithSnapshot");
for (var userChatWith in chatWithSnapshot) {
final user = ProfileChatWith(
userId: userChatWith,
currentUserId: currentUserId,
);
chatWithList.add(user);
//print("I have chatted with: $userChatWith");
}
return Container(
width: MediaQuery.of(context).size.width,
child: ListView(
//shrinkWrap: true,
children: chatWithList,
),
);
} else {
return Center(
child: circularProgress(),
);
}
},
)
: Container(
child: Center(
child: ListView(
shrinkWrap: true,
children: <Widget>[
Icon(
Icons.group,
color: Colors.greenAccent,
size: 200.0,
),
Text(
"Search Users",
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.greenAccent,
fontSize: 50.0,
fontWeight: FontWeight.bold),
)
],
),
),
);
}
Try reverse: true,
return SizedBox(
width: MediaQuery.of(context).size.width,
child: ListView(
reverse: true,
children: chatWithList,
),
);
Use Listview.builder for performance optimization
return SizedBox(
width: MediaQuery.of(context).size.width,
child: ListView.builder(
reverse: true,
itemBuilder: (BuildContext context, int index) => chatWithList[index],
),
);
The solution which worked is as below, #Leo Tran own words
I found a way to solve my question is that I will rebuild my widget whenever the data got updated.

Flutter + Firebase : How to view images depend on id?

I have an app show data from firebase ,and image in folder called category in firebase storage ,the name of image is same id field of collections.
now I want to view data and view image of same field :
child: ListView.builder(
padding: EdgeInsets.all(10),
itemCount: doctorData.length,
itemBuilder: (context, index) {
return Card(
elevation: 15,
child: ListTile(
leading: Container(
width: 60,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30)),
child: Image(
image: NetworkImage(
'gs://myproject.appspot.com/categories/categories/${doctorData[index]['id']}.png'),
)
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Detail(doctorData[index]),
),
);
},
title: Text('Dr. ${doctorData[index]['name_en']}',
style: TextStyle(fontSize: 22)),
subtitle: Text('د. ${doctorData[index]['name_ar']}',
style: TextStyle(fontSize: 22)),
),
);
}),
),
The image isn't shown , How can I view the image
I had the same issue, but after a while i figured this out. I used Futurebuilder and CachedNetworkImageProvider and they are solved my problem.
If you want to you the CachedNetworkImageProvider you need this in the pubspec.yaml.
dependencies:
cached_network_image: ^2.3.3
FutureBuilder<dynamic>(
future: FirebaseStorage().ref('yourImage.jpg').getDownloadURL(),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState != ConnectionState.waiting) {
return Image(
image: CachedNetworkImageProvider(snapshot.data.toString()),
fit: BoxFit.cover,
);
} else {
return Text('Loading image....');
}
},
),

How to get data from two collections in firebase using Flutter

This is my problem:
I have a ListPost StatefulWidget where I want to display a list of widgets that contains the user's account image, the user's name, and the user's posts images(similar to Facebook feeds), however, I have gotten to the point that I need to get that data from two different collections in Firebase (see my firebase collections image below).
The good thing is that I have been able to get that data only from one collection(userFeed) and display that data in my ListPost file in different widgets, however, I do not know how to get data from another collection in Firebase using the same streamBuilder and display all that data I want to display in other widgets in my ListPost screen.
So, my specific question is:
How can I make my ListPost screen to populate data from 2 different collections in Firebase using a stream builder or another type of implementation?
This is the firebase image
This is the complete code for the ListPost screen
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'models/post_model.dart';
final _stream = Firestore.instance.collection('userFeed').snapshots();
class ListPosts extends StatefulWidget {
#override
_ListPostsState createState() => _ListPostsState();
}
class _ListPostsState extends State<ListPosts> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
//this is the Streambuilder to get the data however it only lets me to get one collection
child: StreamBuilder(
stream: _stream,
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text('Loading...');
return ListView.builder(
itemExtent: 550.0,
itemCount: snapshot.data.documents.length,
itemBuilder: (BuildContext context, int data) {
//here I get the data from the userFeed colecction
Post post = Post.fromDoc(snapshot.data.documents[data]);
return Column(
children: <Widget>[
GestureDetector(
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 10.0,
),
child: Row(
children: <Widget>[
CircleAvatar(
radius: 25.0,
backgroundColor: Colors.grey,
backgroundImage: post.imageUrl.isEmpty
? AssetImage(
'assets/images/user_placeholder.jpg')
: CachedNetworkImageProvider(post.imageUrl),
),
SizedBox(width: 8.0),
Text(
post.caption,
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
GestureDetector(
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Container(
height: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
image: DecorationImage(
image:
CachedNetworkImageProvider(post.imageUrl),
fit: BoxFit.cover,
),
),
),
],
),
),
],
);
},
);
},
),
),
);
}
}
UPDATE 05-22-2020 HOW I FIXED THE ISSUE
Credits to the user griffins, he helped me to fix this issue.
This is what I do:
I nested my StreamBuilder so I can use 2 streams at the same time
return StreamBuilder(
stream: _stream,
builder: (context, snapshot1) {
return StreamBuilder(
stream: _stream2,
builder: (context, snapshot2) {
if (!snapshot2.hasData) return const Text('Loading...');
if (!snapshot1.hasData) return const Text('Loading...');
return ListView.builder(
itemExtent: 550.0,
itemCount: snapshot2.data.documents.length,
itemBuilder: (BuildContext context, int data) {
User user = User.fromDoc(snapshot2.data.documents[data]);
Post post = Post.fromDoc(snapshot1.data.documents[data]);
return buildbody(user, post, context);
},
);
},
);
},
);
You can can make you body take a widget ListView and for the Listview children have both your lists.
example
body: ListView(
children: <Widget>[
---list1----
--list2-----
]);
or you can use a custom scroll view
return new Scaffold(
appBar: new AppBar(
title: new Text("Project Details"),
backgroundColor: Colors.blue[800]),
body:
new CustomScrollView(
slivers: <Widget>[
new SliverPadding(padding: const EdgeInsets.only(left: 10.0,right: 10.0,
top: 10.0,bottom: 0.0),
sliver: new SliverList(delegate:
new SliverChildListDelegate(getTopWidgets())),
),
new SliverPadding(padding: const EdgeInsets.all(10.0),
sliver: new SliverList(delegate: new SliverChildListDelegate(
getSfListTiles()
))),
new SliverPadding(padding: const EdgeInsets.all(10.0),
sliver: new SliverList(delegate: new SliverChildListDelegate(
getWorkStatementTiles()
))),
]
)
);
update
from #Rémi Rousselet answer You can nest StreamBuilder
StreamBuilder(
stream: stream1,
builder: (context, snapshot1) {
return StreamBuilder(
stream: stream2,
builder: (context, snapshot2) {
// do some stuff with both streams here
},
);
},
)

I need field data to be updated in the UI when there is some update in that field from firestore

I want to update document field value in the UI of flutter whenever there is some change in field value in realtime.
I have tried using StreamBuilder but the only output I am getting is 'Instance QuerySnapshot'
StreamBuilder(
stream: db.collection('users').snapshots(),
initialData: 0,
builder:(BuildContext context, AsyncSnapshot snapshot) {
return new Text(snapshot.data.DocumentSnapshot,
style: TextStyle(
color: Colors.yellow,
fontWeight: FontWeight.bold,
fontSize: 12.0));
},
),`
Expected output is int value of reward field in document uid.
Because of this line stream: db.collection('users').snapshots(),
You are getting the collection, but you expected the document. Refer to the following:
StreamBuilder(
stream: db.collection('users').document(userId).snapshots(), // insert the userId
initialData: 0,
builder:(BuildContext context, DocumentSnapshot snapshot) { // change to DocumentSnapshot instead of AsyncSnapshot
return new Text(snapshot.data.documentID, // you can get the documentID hear
style: TextStyle(
color: Colors.yellow,
fontWeight: FontWeight.bold,
fontSize: 12.0));
},
),`
I have done one of my project with stream builder and it's working fine. I am putting some code snippet from there please check it out this may helps you.
Code of StreamBuilder
StreamBuilder<QuerySnapshot>(
stream: db.collection("students").snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return new Text("There is no expense");
return Expanded(
child: new ListView(
children: generateStudentList(snapshot),
),
);
},
),
code of generateStudentList
generateStudentList(AsyncSnapshot<QuerySnapshot> snapshot) {
return snapshot.data.documents
.map<Widget>(
(doc) => new ListTile(
title: new Text(doc["name"]),
subtitle: new Text(
doc["age"].toString(),
),
trailing: Container(
width: 100,
child: Row(
children: <Widget>[
IconButton(
onPressed: () {
setState(() {
_studentNameController.text = doc["name"];
_studentAgeController.text = doc["age"].toString();
docIdToUpdate = doc.documentID;
isUpdate = true;
});
},
icon: Icon(
Icons.edit,
color: Colors.blue,
),
),
IconButton(
onPressed: () {
deleteStudent(doc);
},
icon: Icon(
Icons.delete,
color: Colors.red,
),
)
],
),
),
),
)
.toList();
}
You can change fields according your needs.

Flutter: Do FutureBuilder Loads everything at the same time?

My app has a book summaries Feature, Now since I don't have money to host an API, I am using Firebase/Firestore database where I add summaries manually and then retrieve data from firebase to App.
I am using FutureBuilder for it.
Now say I have 10 summaries will FutureBuilder first load all 10 of them and then display data on screen(which is a ListView.builder and can show only 2 summaries without scrolling) or it will load only the data which need to be painted on the screen just like simple ListView.builder.
// code to retrieve data:
Future<QuerySnapshot> getFeedsfromFb() async {
var data = await Firestore.instance
.collection('feedItem')
.orderBy('feedId', descending: true).getDocuments();
return data;
}
// building widget:
Widget feed() {
return Container(
width: deviceWidth,
height: deviceHeight / 3,
child: FutureBuilder(
future: getFeedsfromFb(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: snapshot.data.documents.length > 10
? 10
: snapshot.data.documents.length,
itemBuilder: (BuildContext context, int index) {
return Container(
width: deviceWidth / 2.5,
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => FeedIntro(
snapshot.data.documents[index]['feedId'])));
},
child: Card(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
// width: 150,
height: 150,
foregroundDecoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(
snapshot.data.documents[index]['feedImage'],
),
fit: BoxFit.fill)),
),
Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(snapshot.data.documents[index]['title']),
)),
],
)),
),
);
},
);
} else if (snapshot.hasError) {
return Center(child: Text('Sorry Something went wrong!'));
} else {
return Center(
child: SizedBox(
child: CircularProgressIndicator(),
width: 50,
height: 50,
),
);
}
}),
);
}
Your FutureBuilder will load all items at the same, but only needed data by ListView.builder will be painted on the screen.

Resources