How to use querySnapshot in a listview builder? (flutter) - 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());
}
},
),
);
}
}

Related

Create ListView Builder from StreamBuilder with ISAR Database

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();
},
),
);

how can i get length of list in widget

I can't get the length of the list in the widget although I wrote everything correct
this is my cubit
class AppCubit extends Cubit<NewsStates> {
AppCubit() : super(NewsInit());
static AppCubit get(context) => BlocProvider.of(context);
MianTeams? mianTeams;
void getTimes() {
emit(NewsLoding());
DioHelper.getData(
Query: {"action": "get_teams", "league_id": "141", "APIkey": api})
.then((value) {
mianTeams = MianTeams.fromJson(value.data);
////////////////+++++ i can get the length here in cubit
print(mianTeams!.team.length);
emit(NewsSucsess());
}).catchError((onError) {
print(onError.toString());
emit(NewsErorr(onError.toString()));
});
}
this is my model
class MianTeams {
List<TeamsModel> team = [];
MianTeams.fromJson(List<dynamic> json) {
json.forEach((e) {
team.add(TeamsModel.fromJson(e));
});
}
}
class TeamsModel {
String? teamKey;
String? teamName;
String? teamBadge;
List<Players> players = [];
TeamsModel.fromJson(Map<String, dynamic> json) {
teamKey = json['team_key'];
teamName = json['team_name'];
teamBadge = json['team_badge'];
json['players'].forEach((e) {
players.add(Players.fromJson(e));
});
}
}
but in Widget I can't get the length to set the itemCount of List View separated
this is my statelessWidget
class Teams extends StatelessWidget {
const Teams({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocConsumer<AppCubit, NewsStates>(
listener: (context, state) {},
builder: (context, state) {
// print(AppCubit.get(context).mianTeams!.team.length);
return Scaffold(
appBar: AppBar(),
body: ListView.separated(
itemBuilder: (context, index) => Divider(),
separatorBuilder: (context, index) => Divider(),
itemCount: AppCubit.get(context).mianTeams!.team.length),
);
});
}
}
I don't know what's wrong and I think I wrote everything correctly
The problem is that mianTeams is null until after getTimes() is called and the response is received from your API. So when the ListView tries to build initially, it's trying to get the length of a null list.
Instead of storing mianTeams directly as a property of the Cubit, you should make it a property of one of your State classes. Probably NewsSuccess - then the list will always be available in that state. And if the Cubit is in a different state, you can display a loading indicator or something.
Your Cubit might then look something like this:
class AppCubit extends Cubit<NewsStates> {
AppCubit() : super(NewsInit());
static AppCubit get(context) => BlocProvider.of(context);
void getTimes() {
emit(NewsLoding());
DioHelper.getData(
Query: {"action": "get_teams", "league_id": "141", "APIkey": api})
.then((value) {
final mianTeams = MianTeams.fromJson(value.data);
emit(NewsSucsess(mianTeams: mianTeams));
}).catchError((onError) {
print(onError.toString());
emit(NewsErorr(onError.toString()));
});
}
}
And your widget might be something like:
class Teams extends StatelessWidget {
const Teams({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocConsumer<AppCubit, NewsStates>(
listener: (context, state) {},
builder: (context, state) {
return Scaffold(
appBar: AppBar(),
body: _buildBody(state),
});
}
Widget _buildBody(NewsStates state) {
if (state is NewsError) {
// For example
return Text("Error: ${state.message}");
} else if (state is NewsLoding) {
// For example
return const CircularProgressIndicator();
} else if (state is NewsSuccess) {
return ListView.separated(
itemBuilder: (context, index) => Divider(),
separatorBuilder: (context, index) => Divider(),
itemCount: state.mianTeams.team.length),
);
} else {
// Handle any other possible states you have
}
}
}
You can use the state from builder
itemCount: state.mianTeams?.team.length,

Fetch field from firestore as String in flutter

I want to fetch a field value from firestore and assign it to String variable.
So how can I do this?
When I fetch data it comes as a map.
So in which way I can fetch data as string
screena.dart
class ScreenA extends StatefulWidget {
const ScreenA({Key? key}) : super(key: key);
#override
State<ScreenA> createState() => _ScreenAState();
}
class _ScreenAState extends State<ScreenA> {
List<String> docIDs = [];
Future getDocId() async {
final userId = AuthService.firebase().currentUser!.id;
await FirebaseFirestore.instance
.collection('user')
.where('user_id', isEqualTo: userId)
.get()
.then(
(snapshot) => snapshot.docs.forEach(
(document) {
docIDs.add(document.reference.id);
},
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: getDocId(),
builder: (context, snapshot) {
return ListView.builder(
shrinkWrap: true,
itemCount: docIDs.length,
itemBuilder: (context, index) {
return ListTile(title: Gettt(docId: docIDs[index]));
},
);
},
),
);
}
}
gettt.dart
class Gettt extends StatelessWidget {
final String docId;
Gettt({Key? key, required this.docId}) : super(key: key);
#override
Widget build(BuildContext context) {
CollectionReference users = FirebaseFirestore.instance.collection('user');
return FutureBuilder<DocumentSnapshot>(
future: users.doc(docId).get(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data =
snapshot.data!.data() as Map<String, dynamic>;
return Text('Locker Id: ${data['first_name']}');
}
return const Text('Loading');
},
);
}
}
here's my code
sorry for the last time
I can get a list of items
But I want to without getting a widget I need to get a String
How to do it?
after get document, you can get field example[dart]:

future.wait can not return any data

class AdView extends StatefulWidget {
const AdView({Key? key, required String id}) : super(key: key);
final id = '2';
#override
_AdViewState createState() => _AdViewState();
}
class _AdViewState extends State<AdView> {
final _adService = NewsService();
late Future<Categories> _futureCategories;
late Future<AdBanner> _futureBanners;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
getData() async {
_futureCategories = _adService.getAllCategories();
_futureBanners = _adService.getAds('2');
AdBanner foos;
Categories bars;
await Future.wait<void>([
_futureBanners.then((result) => foos = result),
_futureCategories.then((result) => bars = result),
]);
}
return FutureBuilder(
future: getData(),
builder: (BuildContext context, AsyncSnapshot<dynamic> shot) {
// (BuildContext context, AsyncSnapshot<List<dynamic>> shot) {
if (shot.hasData) {
return ListView.builder(
itemCount: 2,
itemBuilder: (BuildContext context, int index) {
// return bannerListTile(advertisements, index, context);
return const Text('index');
});
} else if (shot.hasError) {
return NewsError(
errorMessage: '${shot.hasError}',
);
} else {
return const NewsLoading(
text: 'loading...',
);
}
});
}
}
I am trying to combine two different API and fetch the results but in this structure I cannot get any data and run only ProgressBarIndicator.
If I am use regular FutureBuilder the JSON calls works without any problem. My goal is get data from two different API's like shot.data[0].value and shot.data[1].value
you made mistake in defining the getData() function.
remove getData from build method and put outside build method because the build is itself a method, you cant define a method inside a method in Dart.
class AdView extends StatefulWidget {
const AdView({Key? key, required String id}) : super(key: key);
final id = '2';
#override
_AdViewState createState() => _AdViewState();
}
class _AdViewState extends State<AdView> {
final _adService = NewsService();
late Future<Categories> _futureCategories;
late Future<AdBanner> _futureBanners;
#override
void initState() {
super.initState();
}
Future getData() async {
_futureCategories = _adService.getAllCategories();
_futureBanners = _adService.getAds('2');
AdBanner foos;
Categories bars;
await Future.wait<void>([
_futureBanners.then((result) => foos = result),
_futureCategories.then((result) => bars = result),
]);
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getData(),
builder: (BuildContext context, AsyncSnapshot<dynamic> shot) {
// (BuildContext context, AsyncSnapshot<List<dynamic>> shot) {
if (shot.hasData) {
return ListView.builder(
itemCount: 2,
itemBuilder: (BuildContext context, int index) {
// return bannerListTile(advertisements, index, context);
return const Text('index');
});
} else if (shot.hasError) {
return NewsError(
errorMessage: '${shot.hasError}',
);
} else {
return const NewsLoading(
text: 'loading...',
);
}
});
}
}

Flutter FutureBuilder snapshot returns Instance of 'Object' instead of data

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);
},),
),},
},)