Is it possible to add containers programmatically to a pageview list in flutter? - firebase

i wanted my list of pageview containers to be created according to the number of documents in a collection in my cloud Firestore so that when clicked i can display data from each document on each page, is there a way to do this,thanks

You can use a StreamBuilder with a PageView.builder inside as the builder. For example, let Object be the type of the documents you are getting from Firestore:
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(8.0),
child: StreamBuilder<List<Object>>(
stream: firestoreService.getObjectsList, // something that returns an Object
builder: (context, snapshot) {
if (snapshot.data == null) {
return Center(child: CircularProgressIndicator());
} else {
return Scrollbar(
child: PageView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
// return something for each object in each page
}),
);
}
}),
));
}

Related

Reading FirebaseFirestore collection items and saving them to list

I am having a trouble reading collection from firebase and saving values in a list.
I basically have a collection called 'brands' where I have car brands like this:
Firebase 'brands' collection screenshot
I need these car brands to be saved as a list like this, to be able to use it in a dropdown menu as items:
<String>[
'ferrari',
'mercedes',
'porsche',
]
I have tried using StreamBuilder (below) but it requires to return a widget and I do not actually need a widget to be returned, so below StreamBuilder is just an experiment "in progress".
Do you have any ideas?
final stream = FirebaseFirestore.instance
.collection('accounts')
.doc('dealers')
.collection(user!.uid)
.doc(dealerName)
.collection('brands')
.snapshots();
StreamBuilder(
stream: stream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
return Text('Error in receiving snapshot: ${snapshot.error}');
}
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
backgroundColor: Theme.of(context).primaryColor,
),
);
}
return ListView.builder(
padding: EdgeInsets.all(8),
reverse: true,
itemCount: snapshot.data.docs!.length,
itemBuilder: (BuildContext context, int index) {
return Text(
snapshot.data.docs[index]['brandName'],
);
},
);
},
);
Once you get the data from firebase, loop through it and add the car brands to your list. Try this:
List<String> myBrands = [];
final dataRef = await FirebaseFirestore.instance
.collection('accounts')
.doc('dealers')
.collection(user!.uid)
.doc(dealerName)
.collection('brands')
.get();
dataRef.docs.forEach((doc) {
myBrands.add(doc.data()['brandName']);
});
You should then be able to use the myBrands list for your dropdown menu.

Merging stream in flutter firetore

I am using two quires to fetch data from the firestore.
Query 1.
_firestore
.collection('chats')
.doc(getCurrentUser().uid)
.collection('chatUsers')
.orderBy('timestamp');
with all the querysnapshot document from query 1. I am fetching last message and document id, and displaying the last message in listTile. With document id i am passing the id to fetch other data from other collection like name photo etc.
Query 2.
Future<DocumentSnapshot> fetchUserData(String uid) async => await _firestore
.collection('users')
.doc(uid).get();
So for this I need to use nested stream builder. First stream builder to fetch all data. Second stream builder to fetch user requested data from all data. what will be the best approach?
This is how i am using query 1 in my widgets for the query 2 I have to implement it inside the ListView.builder which will be the nested stream. please guide me with the best approach to this.
SafeArea(
child: Scaffold(
body: StreamBuilder<QuerySnapshot>(
stream: _fetchUserChatRoom.snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
return _tiles(snapshot.data.docs);
} else if (snapshot.hasError) {
return Icon(Icons.error_outline);
} else {
return CircularProgressIndicator();
}
}),
),
);
}
Widget _tiles(List<QueryDocumentSnapshot> docs) {
return ListView.builder(
itemCount: docs.length,
itemBuilder: (BuildContext context, int index) {
var data = ChatModel.fromMap(docs[index].data());
return GestureDetector(
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (_) => ChatScreen(uid: docs[index].id))),
child: ListTile(
leading: CircleAvatar(),
title: Text(data.message),
subtitle: Text(data.timestamp.toString()),
trailing: Text('time'),
),
);
});
You can either use async and await in your ListView.builder, however, I imaging this could slowdown you app and cause a lot of firestore calls.
Widget _tiles(List<QueryDocumentSnapshot> docs) {
return ListView.builder(
itemCount: docs.length,
itemBuilder: (BuildContext context, int index) async {
var data = ChatModel.fromMap(docs[index].data());
var userData = await fetchUserData(data[index].uid);
return GestureDetector(
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (_) => ChatScreen(uid: docs[index].id))),
child: ListTile(
leading: CircleAvatar(),
title: Text(data.message),
subtitle: Text(data.timestamp.toString()),
trailing: Text('time'),
),
);
});
Other options (which I use) is to use a Provider class with all the contacts. You can fill the Provider when the app initializes with all the users in your firestore. After that you can use each user data anywhere in your app.

SetState is causing Futurebuilder to reload the data on every tap

I am using future builder and stream builder to fetch data from firebase and show them on screen.
I have favourite button as well. when I click on favourite_borderLine iconButton. It fetch data from firebase then change the state to favourite_border iconButton.
It also change the state of every other listview.Builder what I want is just to change the icon state on every click not fetching the whole data from database.
This is the initial state
when I tap on favourite icon, Suppose I tapped on first icon then it start loading.
and then all the icons are changed :(
I just want to change the clicked icon state not all icons and do not want the fetch data on click just change the state of button.Here is code.
class TalentScreen1 extends StatefulWidget {
#override
_TalentScreen1State createState() => _TalentScreen1State();
}
class _TalentScreen1State extends State<TalentScreen1> {
bool toggle = false;
#override
Widget build(BuildContext context) {
return BlocProvider<TalentFavCubit>(
create: (context) => TalentFavCubit(),
child: SafeArea(
child: Scaffold(
body: Padding(
padding: const EdgeInsets.all(20.0),
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text('Talent Screen 1 home search'),
_retriveAllDocs,
],
),
),
),
),
),
);
}
Widget get _retriveAllDocs => FutureBuilder<QuerySnapshot>(
future: FirebaseRepo.instance.fetchWorkerFormFieldsData(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return CircularProgressIndicator();
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (!snapshot.hasData) {
return Text("Nothing to show");
}
if (snapshot.connectionState == ConnectionState.done) {
final List<DocumentSnapshot> data = snapshot.data.docs;
return theUserInfo(data);
}
return Text("loading");
});
Widget theUserInfo(List<DocumentSnapshot> data) {
return ListView.builder(
shrinkWrap: true,
itemCount: data.length,
itemBuilder: (context, index) {
return FutureBuilder<DocumentSnapshot>(
future: fetch(data[index]['uid']),
builder: (BuildContext context,
AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.connectionState == ConnectionState.done) {
TalentHireFavModel userData = TalentHireFavModel.fromMap(
data[index].data(), snapshot.data.data());
return Card(
child: Column(
children: <Widget>[
Text(userData.name),
Text(userData.categories),
Text(userData.skills),
Text(userData.country),
Text(userData.phoneNo),
Text(userData.hourlyRate),
Text(userData.professionalOverview),
Text(userData.skills),
Text(userData.expert),
Text(userData.createdAt),
IconButton(
icon: toggle
? Icon(Icons.favorite_border)
: Icon(
Icons.favorite,
),
onPressed: () {
setState(() {
// Here we changing the icon.
toggle = !toggle;
});
}),
],
),
);
}
return Container();
});
});
}
//TODO: Implementation Fix Error
Widget _iconButton(uid) {
return StreamBuilder<QuerySnapshot>(
stream: FirebaseRepo.instance.fetchCurrentUserFavourites().snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
var data = snapshot.data.docs;
// print(snapshot.data.get('uid'));
if (snapshot.hasError) {
return Text('Something went wrong');
}
return IconButton(
icon: data.isEmpty == uid
? Icon(Icons.favorite)
: Icon(Icons.favorite_border),
onPressed: () =>
BlocProvider.of<TalentFavCubit>(context).addTalentFav(uid));
},
);
}
Future<DocumentSnapshot> fetch(data) async =>
await FirebaseRepo.instance.fetchWorkerUserData(data);
}
This is your broken line of code:
future: FirebaseRepo.instance.fetchWorkerFormFieldsData(),
The FutureBuilder documentation starts with:
The future must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.
A general guideline is to assume that every build method could get called every frame, and to treat omitted calls as an optimization.
And you broke the contract. I have a video that illustrates this in detail. https://www.youtube.com/watch?v=sqE-J8YJnpg
Do what the docs say. TL;DR: Do not create the Future in the parameter to FutureBuilder.

Display images of Firebase Storage with a Firestore query to get image url in Flutter

I am trying to fill a GridView.builder with images from my Firebase storage. I want to get the url from my Firestore database with a query. I couldn't find any good tutorial and tried to build the function by myself but it didn't work. In the FutureBuilder I print the snapshot variable that should have the data variable, but it has not.
Container(
margin: EdgeInsets.all(5),
padding: EdgeInsets.all(5),
height: deviceHeight * 0.35,
width: deviceWith,
color: Colors.white,
child: FutureBuilder(
future: getImages(),
builder: (context, AsyncSnapshot<dynamic>snapshot) {
print(snapshot);
if (snapshot.connectionState == ConnectionState.done) {
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.docs.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
contentPadding: EdgeInsets.all(8.0),
title: Text(snapshot.data.docs[index].data()["name"]),
leading: Image.network(
snapshot.data.docs[index].data()["url"],
fit: BoxFit.fill),
);
});
} else if (snapshot.connectionState == ConnectionState.none) {
return Text("No data");
}
return CircularProgressIndicator();
},
),
),
Future getImages() {
var data;
page.where("userID",isEqualTo: FirebaseAuth.instance.currentUser.uid).snapshots().listen((data){
return data.docs;
});
return data;
}
I have only changed the Future function from the tutorial, not the FutureBuilder variables but I know that I have to change them.
Change the future method to the following:
Future<QuerySnapshot> getImages() async {
return await page.where("userID",isEqualTo: FirebaseAuth.instance.currentUser.uid).get();
}
get() returns a Future<QuerySnapshot>

Flutter Firebase ListView - Slow Refreshes

I created a ListView that populates from a Firebase collection by using a StreamBuilder widget. It takes some time for the ListView to populate because I'm running tasks (HTTP requests) for each item of the Firebase collection and then displaying the result in the list.
When I navigate away from the page with the ListView and then return to the page (using PageView), the ListView appears to refresh entirely instead of using the last seen version. So there is a ~5 second circular progress indicator while the list re-populates every time the page is re-opened.
Questions:
What is the best way to make this ListView not complete a full 5
second refresh every time the page is re-opened? Can it use the last seen version and only update when items are added to the firebase collection?
If I were to remove the tasks (HTTP requests) that need to be ran on each item of the collection and instead simply show values directly from the Firebase collection, should the refresh time be fast enough that it is not a problem?
Is it best to create a local database (using sqflite) that syncs with the Firebase collection to prevent slow refreshes?
Code:
class AccountsPage extends StatefulWidget {
#override
_AccountsPageState createState() => _AccountsPageState();
}
class _AccountsPageState extends State<AccountsPage> {
User user;
Widget _buildListItem(BuildContext context, DocumentSnapshot document, String uuid) {
// get data from firebase
String token = document.data.values.toList()[0];
// For current document/token, make an HTTP request using the token and return relevant data
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Flexible(
child: FutureBuilder(
future: anHTTPrequest(token, uuid),
builder: (context, projectSnap) {
if (projectSnap.connectionState == ConnectionState.none ||
!projectSnap.hasData || projectSnap.data.length == 0) {
return Container();
}
return ListView.builder(
shrinkWrap: true,
itemCount: projectSnap.data.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(projectSnap.data[index]),
);
},
);
},
),
)],
);
}
#override
Widget build(BuildContext context) {
final container = StateContainer.of(context);
user = container.user;
return Container(
child: Scaffold(
body: Column(
children: <Widget>[
new Flexible(
child: StreamBuilder(
stream: Provider.of(context).collectionRef.document(user.uuid).collection('tokens').snapshots(),
builder: (context, snapshot){
if (!snapshot.hasData){
return Container(
child: Center(
child: Text("No data")
)
);
}
return ListView.builder(
padding: EdgeInsets.all(8.0),
reverse: false,
itemCount: snapshot.data.documents.length,
itemBuilder: (context, int index) {
return _buildListItem(context, snapshot.data.documents[index], user.uuid);
}
);
}
)
),
]
),
),
);
}
}

Resources