I am trying to create a ListView Builder from Stream builder in Flutter.
TodoList Class
import 'package:isar/isar.dart';
part 'todo_list.g.dart';
#Collection()
class TodoList {
Id id = Isar.autoIncrement;
late String todoTitle;
}
Isar Services
class IsarService {
late Future<Isar> db;
IsarService() {
db = openDB();
}
//Return IsarDB, if not found, then create
Future<Isar> openDB() async {
if (Isar.instanceNames.isEmpty) {
return await Isar.open(
[TodoListSchema],
inspector: true,
);
}
return Future.value(Isar.getInstance());
}
Stream<List<TodoList>> listenToTodoList() async* {
final isar = await db;
yield* isar.todoLists.where().watch(fireImmediately: true);
}
}
ListView Builder from above Streambuilder
class ListScreen extends StatefulWidget {
final IsarService service;
const ListScreen(this.service, {super.key});
#override
State<ListScreen> createState() => _ListScreenState();
}
class _ListScreenState extends State<ListScreen> {
//Text Controller
final _textController = TextEditingController();
final service = IsarService();
//Let's build List Title from snapshot
final List<TodoList> _todoList = [];
//Root widget of the class
#override
Widget build(BuildContext context) {
return Scaffold(
//List Screen Body Section
body: StreamBuilder(
stream: service.listenToTodoList(),
builder: (context, snapshot) {
if (snapshot.hasError) {
AlertDialog(
content: Text(snapshot.error.toString()),
);
} else if (snapshot.hasData) {
_todoList.add(snapshot.data); //Error happen in this line
}
return const CircularProgressIndicator();
},
),
);
}
}
The Error is
The argument type 'List?' can't be assigned to the parameter type 'TodoList'.
I'am trying to assign the snapshot data in final List<TodoList> _todoList = []; and use them ListView Builder
Try this code:
return Scaffold(
body: StreamBuilder(
stream: service.listenToTodoList(),
builder: (context, snapshot) {
if (snapshot.hasError) {
AlertDialog(
content: Text(snapshot.error.toString()),
);
} else if (snapshot.hasData) {
final todos = snapshot.data;
if (todos != null) {
return ListView.builder(
itemCount: todos.length();
itemBuilder: (context, index) {
final todo = todos[index];
return Text(todo.todoTitle);
}
);
} else {
return const Center(child: Text('No data found!'));
}
}
return const CircularProgressIndicator();
},
),
);
Related
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());
}
},
),
);
}
}
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),
);
},
);
}
}
},
),
);
}
I tried to fetch data as List from database but data not display in UI. How I fix this? I tried fetch data using model class and my collection name is '12words'.
UI code:
class _WordsScreenState extends State<WordsScreenState> {
List<Words12> wordList = [];
#override
void iniState() {
fetchRecords();
iniState();
}
fetchRecords() async {
var records = await FirebaseFirestore.instance.collection('12words').get();
mapRecords(records);
}
mapRecords(QuerySnapshot<Map<String, dynamic>> records) {
var _list = records.docs
.map(
(words12) => Words12(
id: words12.id,
wordName: words12['wordName'],
categoryName: words12['categoryName'],
),
)
.toList();
setState(() {
wordList = _list;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: ListView.builder(
itemCount: wordList.length,
itemBuilder: (context, index) {
return (ListTile(
title: Text(wordList[index].wordName),
subtitle: Text(wordList[index].categoryName),
));
},
));
}
Model:
First do not call async function in initState, instead of that, use FutureBuilder and also change your fetchRecords() to return a list. This is a full example of using FutureBuilder with your code:
class TestFuture extends StatefulWidget {
const TestFuture({super.key});
#override
State<TestFuture> createState() => _TestFutureState();
}
class _TestFutureState extends State<TestFuture> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: FutureBuilder<List<Words12>>(
future: fetchRecords(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading....');
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
List<Words12> data = snapshot.data ?? [];
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return (ListTile(
title: Text(data[index].wordName),
subtitle: Text(data[index].categoryName),
));
},
);
}
}
},
),
);
}
Future<List<Words12>> fetchRecords() async {
var records = await FirebaseFirestore.instance.collection('12words').get();
return mapRecords(records);
}
List<Words12> mapRecords(QuerySnapshot<Map<String, dynamic>> records) {
var _list = records.docs
.map(
(words12) => Words12(
id: words12.id,
wordName: words12['wordName'],
categoryName: words12['categoryName'],
),
)
.toList();
return _list;
}
}
i am new to flutter and trying to display data from a http post
referencing from [1]https://flutter.dev/docs/cookbook/networking/background-parsing and [2]https://flutter.dev/docs/cookbook/networking/fetch-data
i tried to display data on a futurebuilder but it keeps displaying this from the Text('${snapshot.data}')
[Instance of 'DashBoardBanner', Instance of 'DashBoardBanner', Instance of 'DashBoardBanner']
Builder
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late Future<List<DashBoardBanner>> futureBanner;
#override
void initState() {
super.initState();
futureBanner = getBannerDataFromServer();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ListView(
children: [
Card(
child: FutureBuilder(
future: getBannerDataFromServer(),
builder: (context,snapshot){
if(snapshot.connectionState == ConnectionState.done){
if (snapshot.hasData) {
return Text('${snapshot.data}');
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
}
return const CircularProgressIndicator();
},
),
)
],
)),
);
}
}
Class and postreq
class DashBoardBanner {
final String MsgId;
final String MsgKey;
final String MsgPic;
const DashBoardBanner(
{required this.MsgId, required this.MsgKey, required this.MsgPic});
factory DashBoardBanner.fromJson(Map<String, dynamic> json) {
return DashBoardBanner(
MsgId: json['MsgId'] as String,
MsgKey: json['MsgKey'] as String,
MsgPic: json['MsgPic'] as String,
);
}
}
Future<List<DashBoardBanner>> getBannerDataFromServer() async {
final queryParameters = {
"ApiFunc": 'Banner',
"UserKey": getDeviceKey(),
"Token": getDeviceToken(),
"SubmitContent": json.encode({"MobileNo": getMobileNo1()})
};
final response = await http.post(
Uri.http('somesite.net', '/capi.aspx', queryParameters),
);
if (response.statusCode == 200) {
Map<String, dynamic> data = jsonDecode(response.body);
final splitoff = jsonEncode(data['RespContent']);
return compute(parseBanner, splitoff);
} else {
throw Exception('Failed to load Data');
}
}
List<DashBoardBanner> parseBanner(String responseBody) {
final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();
return parsed
.map<DashBoardBanner>((json) => DashBoardBanner.fromJson(json))
.toList();
}
Edit : i rebuilt the file replicating reference[1] and it finally displayed the data i needed, it seems the issue stem from not having this 2nd widget which return the obj back , however how do i combine the 2nd build widget into the first without needing the whole widget as having a whole build widget to return 1 line seems pointless?
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body:Container(
child: FutureBuilder<List<DashBoardBanner>>(
future: getBannerDataFromServer(http.Client()),
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Center(
child: Text('An error has occurred!'),
);
} else if (snapshot.hasData) {
print(snapshot.data!.length);
return DashBoardBannersList(dashboardBanners: snapshot.data!); <--- original issue due to not having this
} else {
return CircularProgressIndicator();
}
},
),
),
);
}
}
class DashBoardBannersList extends StatelessWidget {
const DashBoardBannersList({Key? key, required this.dashboardBanners}) : super(key: key);
final List<DashBoardBanner> dashboardBanners;
#override
Widget build(BuildContext context) {
return Text(dashboardBanners[0].MsgId);
}
}
This error is caused because of the sound null safety
snapshot.data might be null for some requests so you can't access the array at a certain index cause it can be null.
If you know for sure snapshot.data exists you can use the ! operator to tell dart the variable is not null for sure like that:
snapshot.data![index];
You can also check if the data is null before accessing it like that:
if (snapshot.data != null) {
// do something with snapshot.data[index]
}
I recommed to read more about sound null safety here
Check the Firestore docs.
Inside snapshot.data, there's docs (every document of your collection).
The code is from there:
#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(
children: snapshot.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data = document.data()! as Map<String, dynamic>;
return ListTile(
title: Text(data['full_name']),
subtitle: Text(data['company']),
);
}).toList(),
);
},
);
}
The code above shows how to convert every doc (type DocumentSnapshot) to a JSON format (that can be represented with Map<String, dynamic>). To access to the doc id, you'll access with document.id, because it isn't inside the document.data() method.
You wanna retrieve a list of DashBoardBanner but you forget initialize the futurebuilder by adding a ListView.builder().
Try to use the following code idea :
FutureBuilder(
future: getBannerDataFromServer(http.Client()),
builder: (context, AsyncSnapshot snapshot) {
print(snapshot.hasData);
if (snapshot.hasError) {
return CircularProgressIndicator();
} else if (snapshot.hasData) {
return Expanded(
child: ListView.builder(
scrollDirection: Axis.vertical,
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, int index) {
var data = snapshot.data![index];
return DashBoardBannersList(dashboardBanners: data);
},),
),},
},)
I'm trying to update my listview widget from BloC but it's always returning an empty snapshot.BLoC is using a stream from an async SQLite query using sqflite. The sqflite query appears to be working when accessed directly.
Here's a snippet of the UI portion:
ListBloc listBloc = ListBloc();
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Expanded(child: getBoxList()),
FlatButton(
onPressed: () {
listBloc.queryBoxes();
},
child: Text('pull data'),
),
],
),
),
);
}
Widget getBoxList() {
return StreamBuilder(
stream: listBloc.boxes,
builder: (BuildContext context, AsyncSnapshot<List<Box>> snapshot) {
return getList(snapshot);
});
}
Widget getList(AsyncSnapshot<List<Box>> snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Text(snapshot.data[index].boxName);
},
);
} else {
return Text('snapshot has no data');
}
}
Hers's a snippet of the BloC:
class ListBloc {
final _boxListController = StreamController<List<Box>>.broadcast();
final DatabaseHelper _db = DatabaseHelper.instance;
get boxes => _boxListController.stream;
ListBloc() {
getAllIBoxes();
}
getAllIBoxes() async {
_boxListController.sink
.add(await _db.queryAllBoxRows(DatabaseHelper.tableBoxes));
}
dispose() {
// _itemListController.close();
_boxListController.close();
}
}
_db.queryAllBoxRows(DatabaseHelper.tableBoxes) works just fine when tested outside the BLoC.
Thank you!
-- update --
here's the queryBoxes function in BLoC. I just added this to test if the query returns data.
queryBoxes() async {
DatabaseHelper _db = DatabaseHelper.instance;
final _boxes = await _db.queryAllBoxRows(DatabaseHelper.tableBoxes);
_boxes.forEach((element) {
print(element.boxName);
});
}
-- update 2 --
Source of the stream:
getAllIBoxes() async {
_boxListController.sink
.add(await _db.queryAllBoxRows(DatabaseHelper.tableBoxes));
}
is _db.queryAllBoxRows(DatabaseHelper.tableBoxes) which is a method in my DatabaseHelper that looks like this.
Future<List<Box>> queryAllBoxRows(table) async {
Database db = await instance.database;
final _result = await db.query(table);
List<Box> box = _result.map((e) => Box.fromDatabaseJson(e)).toList();
return box;
}
You are just printing the values in the queryBoxes function. You have to inform your stream that there is data available, for that you've to add the values to the sink.
Modify your function to:
queryBoxes() async {
DatabaseHelper _db = DatabaseHelper.instance;
final _boxes = await _db.queryAllBoxRows(DatabaseHelper.tableBoxes);
_boxes.forEach((element) {
print(element.boxName);
});
_boxListController.sink.add(boxes);
}