I've successfully displayed my Firestore Data as a ListView() unfortunately I can't get it to display as a GridView()
I've tried many different methods but can't seem to find the right fit - I'd like a two columns GridView
Here is my ListView code :
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _usersStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return ListView(
shrinkWrap: true,
children: snapshot.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
return ListTile(
title: Text(data['num']),
subtitle: Text(data['num']),
);
}).toList(),
);
},
);
Turns out it was easier than I thought :
I just had to change ListView to GridView.count for it to display as a GridView & set crossAxisCount: 2 for two columns
class UserGrid extends StatefulWidget {
#override
_UserGridState createState() => _UserGridState();
}
class _UserGridState extends State<UserGrid> {
final Stream<QuerySnapshot> _usersStream =
FirebaseFirestore.instance.collection('User Data').snapshots();
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _usersStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return GridView.count(
shrinkWrap: true,
crossAxisCount: 2,
children: [
...snapshot.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
return ListTile(
title: Text(data['num']),
subtitle: Text(data['num']),
);
}).toList(),
ElevatedButton(onPressed: () {}, child: Text("Hello")),
],
);
});
}
}
SliverGrid must also be a part of CustomScrollView
class SliverGridWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
///no.of items in the horizontal axis
crossAxisCount: 4,
),
///Lazy building of list
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
/// To convert this infinite list to a list with "n" no of items,
/// uncomment the following line:
/// if (index > n) return null;
return listItem(Utils.getRandomColor(), "Sliver Grid item:\n$index");
},
/// Set childCount to limit no.of items
/// childCount: 100,
),
)
],
),
);
}
Related
I have a collection called Todos in Firestore with 3 possible properties (id, text and checked).
So far i have succeeded on creating and saving todo's. Now i want them in a Listview but it returns an error on hot restart:
════════ Exception caught by widgets library ═══════════════════════════════════
type 'Null' is not a subtype of type 'String'
The relevant error-causing widget was
StreamBuilder<QuerySnapshot<Object?>>
My code for displaying the ListView:
final Stream<QuerySnapshot> _todostream =
FirebaseFirestore.instance.collection('Todos').snapshots();
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Styles.bgColor,
floatingActionButton: FloatingActionButton(
onPressed: createNewTask,
child: const Icon(Icons.add),
),
body: StreamBuilder(
stream: _todostream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
return ListView(
children: snapshot.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
return ListTile(title: Text(data['text']));
}).toList(),
);
},
),
);
}
}
I hoped to see a listview with the results of my collection, it contains 2 items.
It would be better to accept null and check if it contains data or not.
body: StreamBuilder(
stream: _todostream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
if (snapshot.hasData) {
final data = snapshot.data?.docs.map((e) => e.data()).toList();
if (data == null) {
return Text("got null data");
}
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
final map = data[index] as Map?;
return ListTile(title: Text("${map?['text']}"));
});
}
return Text("NA");
},
),
i need assistance on how i could possibly query a current logged in / signed in users data so i can display their specific data using the snippet of code below -
class AddStore extends StatelessWidget {
AddStore({Key? key}) : super(key: key);
final CollectionReference _user =
FirebaseFirestore.instance.collection("users");
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: _user.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> streamSnapshot) {
if (!streamSnapshot.hasData) {
return const SizedBox(
height: 250,
child: Center(
child: CircularProgressIndicator(),
),
);
} else {
return ListView.builder(
itemCount: streamSnapshot.data!.docs.length,
itemBuilder: ((context, index) {
final DocumentSnapshot documentSnapshot =
streamSnapshot.data!.docs[index];
return Column(
children: [
Text(documentSnapshot['fullName']),
],
);
}));
}
}));
}
}
You need to add the current user id i.e FirebaseAuth.instance.currentUser!.uid while querying the data from users collection. And this results in single document so you should avoid using ListView
Change the StreamBuilder to this
StreamBuilder<DocumentSnapshot<Map<String, dynamic>>>(
stream: FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid).snapshots(),
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
}
Map<String, dynamic> data =
snapshot.data!.data()! as Map<String, dynamic>;
return Text(data['fullName']);
},
)
class StudentProvider extends ChangeNotifier {
DataBaseHelper data = DataBaseHelper();
List<StudentModel>? student = [];
List<StudentModel>? searchStudentList;
Future<List<StudentModel>> getStudentList() async {
student = await data.getStudent();
return student!;
}
void insert(StudentModel studentModel) async {
print("reached insert in provider class");
await data.insertStudent(studentModel);
notifyListeners();
}
}
The code above is the provider class of mine and I want to use the list that I get from getStudentList list method to build widgets. The problem is it returns a future because I am getting the data from the database.
body: TabBarView(children: [
Consumer<StudentProvider>(
builder: (context, StudentProvider student, ch) {
print("rebuild happened");
return ListView(
children: [
...student.getStudentList().map((e) {
return HomeScreen(student: e);
}).toList(),
],
);
}),
This is where I am trying to build widgets. I am unable to do so.
in getStudentList() you have used Future<List<StudentModel>> so that future can not access in listview.
> So please use streambuilder or remove the future from the list
class StreamBuilderUsage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: Center(
child: Consumer<StudentProvider>(
builder: (context, StudentProvider student, ch) {
return StreamBuilder(
stream: student.getStudentList().asStream(),
builder: (context, snapshot){
print(snapshot.connectionState);
if(snapshot.hasData){
var data = snapshot.data as List<String>;
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index){
return Text(data[index]);
},
);
}else{
return CircularProgressIndicator();
}
},
);
}
),
),
),
],
),
);
}
}
The answer I accepted will also work and we can use Future builder for the same.
Here is the code:-
body: TabBarView(
children: [
Consumer<StudentProvider>(
builder: (context, StudentProvider student, ch) {
print("rebuild happened");
return FutureBuilder(
future: student.getStudentList(),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<StudentModel> data =
snapshot.data as List<StudentModel>;
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return HomeScreen(student: data[index]);
});
} else
return Container(child: Text("No data"));
},
);
},
),
Container(),//neglect this container
],
),
I am using streambuilder to display snapshot data but it is not displaying. The screen is just blank but When I use the future builder with get() methode it display the data but I want realtime changes. I am new to flutter please help me with this. here is code.
class TalentScreen2 extends StatelessWidget {
final Query _fetchFavUser = FirebaseRepo.instance.fetchFavUsers();
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Column(
children: [
Text('Talent Screen 2(Favourites)'),
Expanded(child: _retrieveData(context))
],
),
),
);
}
Widget _retrieveData(BuildContext context) => StreamBuilder<QuerySnapshot>(
stream: _fetchFavUser.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) return const Text('Something went wrong');
if (!snapshot.hasData) return const Text('Alas! No data found');
if (snapshot.connectionState == ConnectionState.waiting)
return Center(
child: CircularProgressIndicator(
strokeWidth: 2.0,
));
if (snapshot.connectionState == ConnectionState.done)
return theUserInfo(snapshot.data.docs);
return Container();
});
Widget theUserInfo(List<QueryDocumentSnapshot> data) {
return ListView.builder(
shrinkWrap: true,
itemCount: data.length,
itemBuilder: (BuildContext context, int index) {
var uid = data[index]['uid'];
TalentHireFavModel userData = TalentHireFavModel.fromMap(
data[index].data(),
);
return Card(
child: Column(
children: <Widget>[
Text(data[index]['orderBy']),
// 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(userData.uid, context),
],
),
);
});
}
Future<DocumentSnapshot> fetch(data) async =>
await FirebaseRepo.instance.fetchWorkerUserData(data);
Widget _iconButton(uid, context) {
return IconButton(
icon: Icon(Icons.favorite),
onPressed: () {
BlocProvider.of<TalentFavCubit>(context).removeTalentFav(uid);
});
}
}
and here is the firestore query methode where I am just applying simple query to fetch all documents and display them. I want real-time changes
Query fetchFavUsers() {
var data = _firestore
.collection('workerField')
.doc(getCurrentUser().uid)
.collection('favourites')
// .where('uid', isNotEqualTo: getCurrentUser().uid)
.orderBy('orderBy', descending: true);
return data;
}
The solution is to just return the function. Get that method out of if statement and place it in just return statement.
I have the following widget that uses PageView to display each book in a separate page.
class Books extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _BooksState();
}
}
class _BooksState extends State<Books> {
PageController controller = PageController();
String title = 'Title of the AppBar';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: PageView.builder(
controller: controller,
scrollDirection: Axis.horizontal,
itemBuilder: (context, position) {
return Center(position.toString());
// return BooksStream();
},
onPageChanged: (x) {
setState(() {
title = '$x';
});
},
),
);
}
}
I also have this example widget from FlutterFire docs to get all documents from a firestore collection:
class BooksStream extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('Books').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return new Text('Loading...');
default:
return new ListView(
children:
snapshot.data.documents.map((DocumentSnapshot document) {
return new ListTile(
title: new Text(document['title']),
subtitle: new Text(document['author']),
);
}).toList(),
);
}
},
);
}
}
In the PageView.builder, when I return BooksStream() it displays all books on every page which is totally normal.
But how would I use the StreamBuilder to display each book on a separate page in PageView?
Thanks.
You can return PageView.builder in your StreamBuilder builder field like that:
class BooksStream extends StatelessWidget {
PageController controller = PageController();
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('Books').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return new Text('Loading...');
default:
return PageView.builder( // Changes begin here
controller: controller,
scrollDirection: Axis.horizontal,
itemCount: snapshot.data.documents.length,
itemBuilder: (context, position) {
final document = snapshot.data.documents[position];
return ListTile(
title: new Text(document['title']),
subtitle: new Text(document['author']));
}
);
}
},
);
}
}
And then just inside the body of your Scaffold you can instantiate your BooksStream.