Create List View with Stream Builder - flutter

Want to create a list view from live data from server using stream,
so create fake stream of data and then save its snapshot in a list to test result, when use that list in ListTile and run app get following error:
The following assertion was thrown building ListTile(dirty):
No Material widget found.
ListTile widgets require a Material widget ancestor.
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SafeArea(
child: Center(
child: StreamBuilderPage(),
),
),
);
}
}
class StreamBuilderPage extends StatefulWidget {
#override
_StreamBuilderPageState createState() => _StreamBuilderPageState();
}
class _StreamBuilderPageState extends State<StreamBuilderPage> {
List<int> items = [];
#override
Widget build(BuildContext context) {
return StreamBuilder(
//Error number 2
stream: NumberCreator().stream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.connectionState == ConnectionState.done) {
return Text('done');
} else if (snapshot.hasError) {
return Text('Error!');
} else {
items.add(snapshot.data);
print(items); //print every second: [0] then [0,1] then [0,1,2] ...
return ListView.builder(
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index].toString()),
);
},
itemCount: items.length,
);
}
},
);
}
}
class NumberCreator {
NumberCreator() {
Timer.periodic(Duration(seconds: 1), (timer) {
//add count to stream
_controller.sink.add(_count);
_count++;
});
}
var _count = 1;
final _controller = StreamController<int>();
Stream<int> get stream => _controller.stream;
dispose() {
_controller.close();
}
}
What actually cause this error?
Thanks community.

Try this
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: StreamBuilderPage(),
);
}
}
class StreamBuilderPage extends StatefulWidget {
#override
_StreamBuilderPageState createState() => _StreamBuilderPageState();
}
class _StreamBuilderPageState extends State<StreamBuilderPage> {
List<int> items = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: StreamBuilder(
//Error number 2
stream: NumberCreator().stream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.connectionState == ConnectionState.done) {
return Text('done');
} else if (snapshot.hasError) {
return Text('Error!');
} else {
items.add(snapshot.data);
print(
items); //print every second: [0] then [0,1] then [0,1,2] ...
return ListView.builder(
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index].toString()),
);
},
itemCount: items.length,
);
}
},
),
),
),
);
}
}
class NumberCreator {
NumberCreator() {
Timer.periodic(Duration(seconds: 1), (timer) {
//add count to stream
_controller.sink.add(_count);
_count++;
});
}
var _count = 1;
final _controller = StreamController<int>();
Stream<int> get stream => _controller.stream;
dispose() {
_controller.close();
}
}

I think you have to embed ListTile in a Material specific widget such as scaffold. I had a similar issue a few days ago and somewhere the message tells you which widgets may be wrapped around to prevent this error

StreamBuilder(
//Error number 2
stream: FunctionWhichReturnSomething().stream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.connectionState == ConnectionState.done) {
return Text('done');
} else if (snapshot.hasError) {
return Text('Error!');
} else {
items.add(snapshot.data);
print(
items); //print every second: [0] then [0,1] then [0,1,2] ...
return ListView.builder(
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index].toString()),
);
},
itemCount: items.length,
);
}
},
)

Related

How to use querySnapshot in a listview builder? (flutter)

I'm trying to fetch documents from my firebase DB and use them to create a social media feed. Here I'm trying to get the length of the fetched collection but I cannot manage to call the variable. Any help would be appreciated. Example code
class LoadDataFromFirestore extends StatefulWidget {
#override
_LoadDataFromFirestoreState createState() => _LoadDataFromFirestoreState();
}
class _LoadDataFromFirestoreState extends State<LoadDataFromFirestore> {
#override
void initState() {
super.initState();
CollectionReference _collectionRef =
FirebaseFirestore.instance.collection('fish');
Future<void> getData() async {
// Get docs from collection reference
QuerySnapshot querySnapshot = await _collectionRef.get();
// Get data from docs and convert map to List
final allData = querySnapshot.docs.map((doc) => doc.data()).toList();
print(allData);
}
}
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: querySnapshot.docs.length,
itemBuilder: (BuildContext context, int index) {
return _postView();
},
),
);
}
}
First of all it is not ok to call future function in initstate, you need to use FutureBuilder like this:
class LoadDataFromFirestore extends StatefulWidget {
#override
_LoadDataFromFirestoreState createState() => _LoadDataFromFirestoreState();
}
class _LoadDataFromFirestoreState extends State<LoadDataFromFirestore> {
late CollectionReference _collectionRef;
#override
void initState() {
super.initState();
_collectionRef = FirebaseFirestore.instance.collection('fish');
}
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<QuerySnapshot>(
future: _collectionRef.get(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading....');
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
QuerySnapshot? querySnapshot = snapshot.data;
return ListView.builder(
itemCount: querySnapshot?.docs?.length ?? 0,
itemBuilder: (BuildContext context, int index) {
var data = querySnapshot?.docs?[index].data();
print("data = $data");
return _postView();
},
);
}
}
},
),
);
}
}
inside listview's builder you can use data to parse your data and use it.
You can use FutureBuilder like this:
class LoadDataFromFirestore extends StatefulWidget {
const LoadDataFromFirestore({super.key});
#override
State<LoadDataFromFirestore> createState() => _LoadDataFromFirestoreState();
}
class _LoadDataFromFirestoreState extends State<LoadDataFromFirestore> {
//TODO change Map<String, dynamic> with your data type with fromJson for example
Future<List<Map<String, dynamic>>> _getData() async {
final querySnapshot = await FirebaseFirestore.instance.collection('fish').get();
return querySnapshot.docs.map((doc) => doc.data()).toList();
}
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<Map<String, dynamic>>>(
future: _getData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
return _postView(/* Ithink you have to pass here your item like snapshot.data[index]*/);
},
);
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
);
}
}

Retrieving Firestore data in ListView but Failing

Currently struggling to make a ListView data retrieved from Firestore.
I am trying to get "kids name" saved under in the firestore as linked photo.
Firestore
No error message is shown up but the data is not retrieved correctly and shown blank screen...hope anyone can correct my code!
and here is my code:
class kidsNamePick extends StatefulWidget {
#override
_kidsNamePickState createState() => _kidsNamePickState();
}
class _kidsNamePickState extends State<kidsNamePick> {
List<Memo> kidsnamelist = [];
Future<void>fetchMemo()async{
final kidsnames = await FirebaseFirestore.instance.collection('useraccount').doc(FirebaseAuth.instance.currentUser!.uid)
.collection('kidsname').get();
final docs = kidsnames.docs;for (var doc in docs){
Memo fetchMemo = Memo(kidsname: doc.data()['kids name'],
);
kidsnamelist.add(fetchMemo);}
setState(() {
});}
#override
void initState(){
super.initState();
fetchMemo();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Add/Select Kids'),
),
body: ListView.builder(
itemCount: kidsnamelist.length,
itemBuilder: (context, index){
return ListTile(
title: Text(kidsnamelist[index].kidsname),
);
},
)
);
}
}
The best way to call future method is using FutureBuilder, first change your fetchMemo to this:
Future<List<Memo>> fetchMemo() async {
try {
final kidsnames = await FirebaseFirestore.instance
.collection('useraccount')
.doc(FirebaseAuth.instance.currentUser!.uid)
.collection('kidsname')
.get();
final docs = kidsnames.docs;
return docs
.map((doc) => Memo(
kidsname: doc.data()['kids name'],
))
.toList();
} catch (e) {
return [];
}
}
then change your build method to this:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Add/Select Kids'),
),
body: FutureBuilder<List<Memo>>(
future: fetchMemo(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading....');
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
List<Memo> data = snapshot.data ?? [];
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(data[index].kidsname),
);
},
);
}
}
},
),
);
}

snapshot.data.data() is null

class Profile extends StatefulWidget {
#override
_ProfileState createState() => _ProfileState();
}
class _ProfileState extends State<Profile> {
Future<DocumentSnapshot> getData() async {
return await FirebaseFirestore.instance
.collection('user')
.doc(FirebaseAuth.instance.currentUser.uid)
.get();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Profile'),
),
body: FutureBuilder(
future: getData(),
builder: (context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data.data() == null)
return Center(child: Text((snapshot.data.data()).toString()));
else
return ListView.builder(
shrinkWrap: true,
itemCount: 1,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(snapshot.data.data()['email']),
);
});
} else if (snapshot.connectionState == ConnectionState.none) {
return Text("No data");
} else
return CircularProgressIndicator();
},
),
);
}
}
This code is always showing 'null' . What is the reason for which snapshot.data.data() is null?
Im new to future builder and Im not understanding what could be the reason for this error.
just use snapshot.data==null or !snapshot.exists

Create stream builder

Get this errors:
1.A value of type 'Stream' can't be returned from function 'stream' because it has a return type of 'Stream'. (return_of_invalid_type at blahblah)
2.The argument type 'Stream' can't be assigned to the parameter type 'Stream'. (argument_type_not_assignable at blahblah)
why?
here is code to create stream builder base on this video from flutter team
void main() {
runApp(MyApp());
//listen to subscribe to stream
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SafeArea(
child: Center(
child: Stream(),
),
),
);
}
}
class Stream extends StatefulWidget {
#override
_StreamState createState() => _StreamState();
}
class _StreamState extends State<Stream> {
#override
Widget build(BuildContext context) {
return StreamBuilder(
//Error number 2
stream: NumberCreator().stream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.connectionState == ConnectionState.done) {
return Text('done');
} else if (snapshot.hasError) {
return Text('Error!');
} else {
return Text(snapshot.data);
}
},
);
}
}
class NumberCreator {
NumberCreator() {
Timer.periodic(Duration(seconds: 1), (timer) {
//add count to stream
_controller.sink.add(_count);
_count++;
});
}
var _count = 1;
final _controller = StreamController<int>();
//error number 1
Stream<int> get stream => _controller.stream;
dispose() {
_controller.close();
}
}
and Do we need statefulWidget to create stramBuilder?
The name of your widget class is causing problem because
class Stream extends StatefulWidget
is causing conflict with the name used by StreamBuildWidget parameter
stream: NumberCreator().stream,
So Try Changing your widgetName to something like StreamPage etc.
and also change
return Text(snapshot.data);
to
return Text(snapshot.data.toString());
even this is throwing error Text widget expects the String not int
Corrected Version of code
class StreamBuilderPage extends StatefulWidget {
#override
_StreamBuilderPageState createState() => _StreamBuilderPageState();
}
class _StreamBuilderPageState extends State<StreamBuilderPage> {
#override
Widget build(BuildContext context) {
return StreamBuilder(
//Error number 2
stream: NumberCreator().stream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.connectionState == ConnectionState.done) {
return Text('done');
} else if (snapshot.hasError) {
return Text('Error!');
} else {
return Text(snapshot.data.toString());
}
},
);
}
}

Connection state always waiting

While fetching data from database in flutter snapShot.ConnectionState is always waiting and the circular progress indicator keeps on loading.
I am not getting any errors and I am using FutureBuilder to build my widget.
Class where I build my widget
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/event_provider.dart';
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: Provider.of<EventProviders>(context).fetchAndSetEvents(),
builder: (ctx, dataSnapshot) {
if (dataSnapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return Consumer<EventProviders>(
child: Text('Not found'),
builder: (ctx, eventData, ch) {
if (eventData.events.length <= 0) {
return ch;
} else {
return ListView.builder(
itemCount: eventData.events.length,
itemBuilder: (ctx, index) {
return Container(
child: Text(eventData.events[index].eventName),
);
},
);
}
},
);
}
},
);
}
}
My future class
Future<void> fetchAndSetEvents() async {
final dataList = await DBHelper.getData('user_events');
_events = dataList
.map(
(data) => EventProvider(
eventName: data['event'],
eventDate: data['date'],
id: data['id'],
),
)
.toList();
notifyListeners();
}
}
Some help will be highly appreciated
Set listen: false
future: Provider.of<EventProviders>(context, listen: false).fetchAndSetEvents(),