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),
);
},
);
}
}
},
),
);
}
Related
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 have the following build method of a statefull widget. I am trying to test updating a Firebase collection stream dynamically using a ChangeNotifier. The code is the following:
#override
Widget build(BuildContext context) {
final List<String> types = ["pop", "ballad", ''];
final SongStreamNotifier songStreamNotifier =
Provider.of<SongStreamNotifier>(context);
developer.log("Rebuilding Scaffold");
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
types.shuffle();
final songType = types.first;
songStreamNotifier.getSongsStream(songType);
},
),
body: StreamBuilder<QuerySnapshot>(
stream: songStreamNotifier.songs,
builder: (context, snapshot) {
developer.log("Rebuilding stream");
if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data!.size,
itemBuilder: (BuildContext ctx, int index) {
return Text(snapshot.data!.docs[index].get('name'));
});
} else {
return Container();
}
}),
);
}
Notifier is called correctly, I can see all log messages on how build is rerun, but the values do not change. The code of the ChangeNotifier is the following:
class SongStreamNotifier extends ChangeNotifier {
Stream<QuerySnapshot> stream = Stream.empty();
final SongService songService = SongService();
Stream<QuerySnapshot> get songs {
return stream;
}
getSongsStream(String type) {
developer.log('type: $type');
stream = songService.getSongs(type);
notifyListeners();
}
}
which is pretty straighforward: songService.getSongs filters the songs based on the songType.
I still cannot understand how providers work with updating the state of widgets. How come I see the builder method getting run but the StreamBuilder always fetches the same values?
Found what the issue was. I didn't call snapshots on the query but only on the initial collection reference. In my Repository i did this:
colRef = _db.collection("songs");
if (type != '') {
colRef.where('kind', isEqualTo: type);
}
return colRef.snapshots()
while I should have done
colRef = _db.collection("songs");
if (type != '') {
return colRef.where('kind', isEqualTo: type).snapshots();
}
return colRef.snapshots()
but I didn't know colRef was immutable.
At first, when i started writing my calls to get data from firestore, it worked. But when i tried writing more docs to my collection, it failed to bring data for the docs i recently added. Then, when i deleted the first one i added, i stopped receiveing data from firestore all together. I have tried several methods, but have all ended in failure.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class collect extends StatefulWidget {
#override
_collectState createState() => _collectState();
}
class _collectState extends State<collect>
{
Future _data;
void initState()
{
super.initState();
_data = getStuff();
}
Future getStuff()
async {
var firestore = FirebaseFirestore.instance;
QuerySnapshot qn = await firestore.collection("buses").get();
return qn.docs;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: _data,
builder: (_, snapshot)
{
if(snapshot.connectionState == ConnectionState.waiting)
{
return Center(
child:Text("Loading")
);
}
else if(snapshot.connectionState == ConnectionState.done)
{
return ListView.builder(itemCount: snapshot.data.length,itemBuilder:(_, index)
{
return Container(
child: ListTile(
title: Text(snapshot.data[index].data()["name"].toString()),
subtitle: Text(snapshot.data[index].data()["price"].toString()),
),
);
});
}
},
),
);
}
}
```![enter image description here](https://i.stack.imgur.com/L7FqF.jpg)
Define your database call as,
Future getStuff() async {
var docs;
await FirebaseFirestore.instance
.collection("buses")
.get()
.then((querySnapshot) {
docs = querySnapshot.docs;
});
return docs;
}
Then use the FutureBuilder in the build() function as,
return Scaffold(
body: Center(
child: FutureBuilder<dynamic>(
future: getStuff(),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (_, index) {
return Container(
child: ListTile(
title: Text(
snapshot.data[index].data()["name"].toString()),
subtitle: Text(
snapshot.data[index].data()["price"].toString()),
),
);
});
} else {
return CircularProgressIndicator();
}
},
),
),
);
I wrapped the FutureBuilder inside a Center just for clarity, you may remove that Center widget.
Now I am using SearchDelegate in flutter 2.0.1, this is my buildSuggestions code:
#override
Widget buildSuggestions(BuildContext context) {
var channelRequest = new ChannelRequest(pageNum: 1, pageSize: 10, name: query);
if (query.isEmpty) {
return Container();
}
return FutureBuilder(
future: ChannelAction.fetchSuggestion(channelRequest),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
List<ChannelSuggestion> suggestions = snapshot.data;
return buildSuggestionComponent(suggestions, context);
} else {
return Text("");
}
});
}
Widget buildSuggestionComponent(List<ChannelSuggestion> suggestions, BuildContext context) {
return ListView.builder(
itemCount: suggestions.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('${suggestions[index].name}'),
onTap: () async {
query = '${suggestions[index].name}';
},
);
},
);
}
when select the recommand text, I want to automatically trigger search event(when I click the suggestion text, trigger the search, fetch data from server side and render the result to UI) so I do not need to click search button. this is my search code:
#override
Widget buildResults(BuildContext context) {
var channelRequest = new ChannelRequest(pageNum: 1, pageSize: 10, name: query);
return buildResultImpl(channelRequest);
}
Widget buildResultImpl(ChannelRequest channelRequest) {
return FutureBuilder(
future: ChannelAction.searchChannel(channelRequest),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
List<Channel> channels = snapshot.data;
return buildResultsComponent(channels, context);
} else {
return Text("");
}
return Center(child: CircularProgressIndicator());
});
}
what should I do to implement it? I have tried invoke buildResults function in buildSuggestionComponent but it seems not work.
To update the data based on the query, you can make an API call to get the result when clicking on a suggestion, then use a StreamController to stream the results to the buildResults() method and call showResults().
I'm creating a simple app here for demonstration:
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(home: Home()));
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
final _controller = StreamController.broadcast();
#override
dispose() {
super.dispose();
_controller.close();
}
Future<void> _showSearch() async {
await showSearch(
context: context,
delegate: TheSearch(context: context, controller: _controller),
query: "any query",
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Search Demo"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: _showSearch,
),
],
),
);
}
}
class TheSearch extends SearchDelegate<String> {
TheSearch({this.context, this.controller});
BuildContext context;
StreamController controller;
final suggestions =
List<String>.generate(10, (index) => 'Suggestion ${index + 1}');
#override
List<Widget> buildActions(BuildContext context) {
return [IconButton(icon: Icon(Icons.clear), onPressed: () => query = "")];
}
#override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: AnimatedIcon(
icon: AnimatedIcons.menu_arrow,
progress: transitionAnimation,
),
onPressed: () {
close(context, null);
},
);
}
#override
Widget buildResults(BuildContext context) {
return StreamBuilder(
stream: controller.stream,
builder: (context, snapshot) {
if (!snapshot.hasData)
return Container(
child: Center(
child: Text('Empty result'),
));
return Column(
children: List<Widget>.generate(
snapshot.data.length,
(index) => ListTile(
onTap: () => close(context, snapshot.data[index]),
title: Text(snapshot.data[index]),
),
),
);
},
);
}
#override
Widget buildSuggestions(BuildContext context) {
final _suggestions = query.isEmpty ? suggestions : [];
return ListView.builder(
itemCount: _suggestions.length,
itemBuilder: (content, index) => ListTile(
onTap: () {
query = _suggestions[index];
// Make your API call to get the result
// Here I'm using a sample result
controller.add(sampleResult);
showResults(context);
},
title: Text(_suggestions[index])),
);
}
}
final List<String> sampleResult =
List<String>.generate(10, (index) => 'Result ${index + 1}');
I have done it through a simple workaround
Simply add this line after your database call
query = query
But be careful of the call looping
I have a Future that returns data from SQLite in a Flutter. I want to convert this list into ListView. How can I do that?
Future<List<Caste>> castes() async {
final Database db = database;
final List<Map<String, dynamic>> maps = await db.query('caste');
return List.generate(maps.length, (i) {
return Caste.n(
id: maps[i]['id'],
name: maps[i]['name'],
);
});
class CasteListView extends ListView {
#override
Widget build(BuildContext context) {
return ListView(
children: [CasteListItem(1, "name"), CasteListItem(2, "Meghya")],
);
}
}
class CasteListItem extends ListTile {
final int id;
final String name;
CasteListItem(this.id, this.name);
#override
Widget build(BuildContext context) {
return ListTile(
onTap: () {
print(name);
},
leading: CircleAvatar(
child: Text(name[0]),
),
title: Text(name),
);
}
}
How can I convert?
For loading the castes use a FutureBuilder. With this you can define what is showing while the query is still executing and what to show when you have the results.
For showing the data use ListView.builder() or ListView.seperated(). There you can define how to build the widget for each item.
Code could look like:
class CasteListView extends ListView {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: castes(),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Caste> castes = snapshot.data;
return ListView.separated(
itemBuilder: (context, oindex) => CasteListItem(caste.id, caste.name),
separatorBuilder: (context, index) => Divider(),
itemCount: castes.length
);
}
return Text("Loading...");
}
);
}
}