I have a Future<List> function that fetches data from an API and it returns 31 list elements for the month july.
However, when I plug this Future function into a futurebuilder, the list length is 129 elements. Code is below. What could happen here that doubles the elements?
My Future code:
Future<List<DataDay>> getMonthlyDataHighlightDays() async {
String url = "myUrl";
var response = await http.get(Uri.parse(url));
Map<String, dynamic> DataResponse = jsonDecode(response.body) as Map<String, dynamic>;
List<dynamic> monthlyList = DataResponse["day_predictions"];
List<DataDay> monthlyListFixed = [];
monthlyList.forEach((dayElement) {
DataDay dayData = DataDay.fromJson(dayElement);
dayData.hour_predictions.forEach((hourElement) {
if (hourElement.rainbow || hourElement.pattern.toLowerCase().contains("rain")) {
monthlyListFixed.add(DataDay.fromJson(dayElement));
}
});
});
log("MonthlyListLenght: " + monthlyList.length.toString(), name: "getMonthlyDataHighlightDays()");
return monthlyListFixed;
}
This is my Futurebuilder:
Future<List<DataDay>> monthData = getMonthlyDataHighlightDays();
Display(
title: "Highlights this Month",
children: [
FutureBuilder<List<DataDay>>(
future: monthData, // a previously-obtained Future<String> or null
builder: (BuildContext context, AsyncSnapshot<List<DataDay>> snapshot) {
Widget child;
log("Snapshotlength: " + snapshot.data!.length.toString(), name: "FutureBuilder<List<DataDay>>");
if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) {
List<DataDay> monthlyList = snapshot.data!;
log(monthlyList.length.toString(),name: "DataPage");
child = ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: monthlyList.length,
itemBuilder: (BuildContext context, int dayHourIndex) {
DataDay dayData = monthlyList[dayHourIndex];
DateTime date = DateTime(dayData.year, dayData.month, dayData.day);
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(printDateTimeToStringWithoutYear(date, settings)),
SizedBox(
height: 10,
child: ListView.builder(
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: dayData.hour_predictions.length,
itemBuilder: (BuildContext context, int hourIndex) {
return getDataIcon(dayData.hour_predictions[hourIndex].pattern, width: 10);
},
),
),
],
);
},
);
} else if (snapshot.hasError) {
child = Row(
children: [
const Icon(
Icons.error_outline,
color: Colors.red,
size: 60,
),
Text(snapshot.error.toString())
],
);
} else {
child = Row(children: [
SizedBox(
width: 60,
height: 60,
child: CircularProgressIndicator(),
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Awaiting result...'),
)
]);
}
return Center(
child: child,
);
})
],
)
Related
I have categories collection with category name and id fields. In foods collection, the category is a reference field. I need to display like this:click to see expected output
where title is coming from categories collection and foods are coming from foods collection.
I tried using nested streambuilder: streambuilder 1: fetch categories in a listview streambuilder 2: fetch foods in a list. Inside streambuilder 2, i have used a futurebuilder to decode the category data. If category name in food and category name from streambuilder 1 is same, the food will be displayed under that category.+
class RestaurantDetails extends StatefulWidget {
final String id;
RestaurantDetails({required this.id, super.key});
#override
State<RestaurantDetails> createState() => _RestaurantDetailsState();
}
class _RestaurantDetailsState extends State<RestaurantDetails> {
List<FoodCategory> categories = [];
List<Food> foods = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: StreamBuilder(
stream: getCategories(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
categories = snapshot.data!.docs
.map((item) => FoodCategory.fromMap(item))
.toList();
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: categories.length,
itemBuilder: ((context, cateindex) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 10),
child: Container(
height: 30,
width: MediaQuery.of(context).size.width * 1,
color: Colors.white,
child: Text(
categories[cateindex].name.toString(),
style: Theme.of(context)
.textTheme
.headline4!
.copyWith(fontSize: 20.0),
),
),
),
StreamBuilder(
stream: FirebaseFirestore.instance
.collection('foods')
.doc(widget.id)
.collection('all')
.snapshots(),
builder: (context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
foods = snapshot.data!.docs
.map((item) => Food.fromMap(item))
.toList();
return ListView.builder(
itemCount: foods.length,
physics:
const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: ((context, foodindex) {
var catepath = foods[foodindex].cid!.path;
String cateDocumentName = catepath
.substring(11, catepath.length);
return Column(
children: [
FutureBuilder(
future: FirebaseFirestore.instance
.collection('categories')
.doc(cateDocumentName)
.get(),
builder: ((context,
AsyncSnapshot<
DocumentSnapshot>
snapshot) {
if (snapshot.hasData) {
Map<String, dynamic> data =
snapshot.data!.data()
as Map<String,
dynamic>;
if (data['name'] ==
categories[cateindex]
.name) {
return Padding(
padding: const EdgeInsets
.symmetric(
vertical: 10,
horizontal: 10),
child: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius
.circular(
20),
color:
Colors.white),
height: 100,
width: MediaQuery.of(
context)
.size
.width *
1,
child: Row(
children: [
Image.network(
foods[foodindex]
.cover
.toString(),
height: 100,
width: 100,
errorBuilder:
((context,
error,
stackTrace) {
return Image
.asset(
'assets/images/food1.jpg',
height: 100,
width: 100,
);
}),
),
UIHelper
.horizontalSpaceMedium(),
Column(
crossAxisAlignment:
CrossAxisAlignment
.start,
children: [
UIHelper
.verticalSpaceSmall(),
Text(
foods[foodindex]
.name
.toString(),
style: Theme.of(
context)
.textTheme
.bodyText1,
),
UIHelper
.verticalSpaceSmall(),
Text(
'₹${foods[foodindex].price}',
style: Theme.of(
context)
.textTheme
.bodyText1!
.copyWith(
fontSize:
14.0),
),
UIHelper
.verticalSpaceMedium(),
],
)
],
)),
);
} else {
return const SizedBox();
}
} else {
return const CircularProgressIndicator
.adaptive();
}
}))
],
);
}));
} else {
return const CircularProgressIndicator
.adaptive();
}
},
)
],
);
}));
} else {
return const CircularProgressIndicator.adaptive();
}
}),
),
);
}
getCategories() {
return FirebaseFirestore.instance
.collection('categories')
.where('uid', isEqualTo: widget.id)
.snapshots();
}
}
categories data
click to see categories
food data
click to see food data
I get the output.see my output here but when data is large (i.e large number of foods inside a category) the app hangs. Is there anyother way we can achieve this? the data should load seamlessly regardless of data size.
It seems that the GridView.builder inside FutureBuilder duplicates each element a number of times equal to the list length.
Here is the code:
InformationScreen:
List<Reference> documentReference = [];
Widget showSavedDocument() => FutureBuilder(
future: _futureListResult,
builder: (context, AsyncSnapshot<ListResult> snapshot) {
if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data!.items.length,
itemBuilder: (context, index) {
final photo = snapshot.data!.items[index].getDownloadURL();
final photoName = snapshot.data!.items[index].name;
final metaData = snapshot.data!.items[index].getMetadata();
documentReference = snapshot.data!.items;
return Column(
children: [
FutureBuilder(
future: metaData,
builder: (context, AsyncSnapshot<FullMetadata> snapshot) {
if(snapshot.hasData) {
photoType = snapshot.data!.contentType!;
}
return Container();
},
),
FutureBuilder(
future: photo,
builder: (context, AsyncSnapshot<String?> snapshot) {
if (snapshot.hasData) {
final image = snapshot.data;
List<Document> documents = [];
for (int i = 0; i < documentReference.length; i++) {
Document document = Document(user!.uid, image!, photoName, photoType);
documents.add(document);
}
return DocumentGrid(documents: documents,); // <------------------------------
}
return Container();
},
),
],
);
},
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
);
}
if (snapshot.connectionState == ConnectionState.waiting || !snapshot.hasData) {
return const Loader();
}
if (snapshot.hasError) {
return Utils.showErrorMessage(snapshot.hasError.toString());
}
return Container();
},
);
DocumentGrid
import 'package:flutter/material.dart';
import 'package:app_test/constant/color.dart';
import '../constant/text.dart';
import '../model/document.dart';
class DocumentGrid extends StatelessWidget {
final List<Document> documents;
const DocumentGrid({Key? key, required this.documents}) : super(key: key);
#override
Widget build(BuildContext context) {
return buildGridView();
}
//****************************************************************************
// Create GridView
//****************************************************************************
Widget buildGridView() => GridView.builder(
itemCount: documents.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 2,
mainAxisSpacing: 2,
),
itemBuilder: (context, index) {
final photo = documents[index].photo;
final title = documents[index].title;
final type = documents[index].type;
return buildGridViewItem(photo, title, type);
},
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
);
//****************************************************************************
// Create GridView item
//****************************************************************************
Widget buildGridViewItem(String photo, String? title, String type) => Container(
width: 50,
height: 50,
color: phoneButtonColor,
child: Stack(
fit: StackFit.expand,
alignment: Alignment.center,
children: [
buildNetworkImage(photo, type),
buildBlackOpacity(title),
],
),
);
//****************************************************************************
// Create Network image
//****************************************************************************
Widget buildNetworkImage(String photo, String type) => Image.network(
fit: BoxFit.cover,
width: 100,
height: 100,
photo,
errorBuilder: (context, exception, stackTrace) {
return type == "pdf" || type != "jpg"
|| type != "jpeg" || type != "png"
? Image.asset(
fit: BoxFit.cover,
width: 100,
height: 100,
"assets/images/pdf.png",
)
: Container(
color: grey,
width: 100,
height: 100,
child: const Center(
child: Text(
errorLoadImage,
textAlign: TextAlign.center,
),
),
);
},
);
//****************************************************************************
// Create Black opacity
//****************************************************************************
Widget buildBlackOpacity(String? title) => Container(
color: Colors.black54,
padding: const EdgeInsets.symmetric(
vertical: 30,
horizontal: 20,
),
child: Column(
children: [
Expanded(
child: Center(
child: Text(
title!,
style: const TextStyle(
fontSize: 20,
color: Colors.white,
),
),
),
),
],
),
);
}
How can I solve that, thanks in advance
Problem solved
Replacing ListView by GridView
Widget showSavedDocument() => FutureBuilder(
future: _futureListResult,
builder: (context, AsyncSnapshot<ListResult> snapshot) {
if(snapshot.connectionState == ConnectionState.done && snapshot.hasData) {
return GridView.builder(
itemCount: snapshot.data!.items.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 2,
mainAxisSpacing: 2,
),
itemBuilder: (context, index) {
final instructorDocument = snapshot.data!.items;
final photo = instructorDocument[index].getDownloadURL();
final photoName = instructorDocument[index].name;
final metaData = instructorDocument[index].getMetadata();
return Column(
children: [
FutureBuilder(
future: metaData,
builder: (context, AsyncSnapshot<FullMetadata> snapshot) {
if (snapshot.hasData) {
photoType = snapshot.data!.contentType!;
}
return Container();
},
),
FutureBuilder(
future: photo,
builder: (context, AsyncSnapshot<String?> snapshot) {
if (snapshot.hasData) {
final image = snapshot.data!;
return Expanded(
child: buildGridViewItem(image, photoName, photoType),
);
}
return Container();
},
),
],
);
},
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
);
}
if(snapshot.connectionState == ConnectionState.waiting || !snapshot.hasData) {
return const Loader();
}
if(snapshot.hasError) {
return Utils.showErrorMessage(snapshot.hasError.toString());
}
return Container();
},
);
I'm trying to display items based on the selected category but I'm not finding the right way to do that.
I suppose the id of the category need to match the categoryId of the item but I'm not getting there.
Here the code for the backend_api:
Future<List<Item>> fetchItems(
{int? categoryId,
String? zipcode,
String? searchText,
String? radius}) async {
var path =
categoryId != null ? "/item/list/category/$categoryId" : "/item/list";
path += zipcode != null ? "/zipcode/$zipcode" : "";
path += "?";
if (searchText != null) {
path += "&search=$searchText";
}
if (radius != null) {
path += "&radiusInKm=$radius";
}
final http.Response response = await _httpClient.get(path);
return jsonDecode(utf8.decode(response.bodyBytes))
.map<Item>((json) => Item.fromJson(json))
.toList();
}
Here the code for displaying the items:
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
physics: const ClampingScrollPhysics(),
child: Row(
children: [
BlocBuilder<ItemCategoriesBloc, ItemCategoriesState>(
builder: ((context, state) {
if (state is ItemCategoriesLoadedState) {
List<MapEntry<int, Category>> categoryList =
List.from(state.categories.entries);
return Container(
width: 800,
child: FormBuilderChoiceChip(
decoration: const InputDecoration(border: InputBorder.none),
selectedColor: MyTheme.primary,
alignment: WrapAlignment.spaceEvenly,
direction: Axis.horizontal,
initialValue: categoryList.map((value) => value).toList(),
name: 'filter_category',
options: categoryList
.map(
(category) => FormBuilderFieldOption(
value: category.value.id,
child: Text(category.value.name),
),
)
.toList(),
//onChanged: showFilteredItems(),
),
);
}
return Container();
}),
),
],
),
),
Expanded(
child: RefreshIndicator(
onRefresh: onRefresh ?? () async {},
child: GridView.builder(
shrinkWrap: true,
physics: const AlwaysScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisSpacing: crossAxisSpacing,
childAspectRatio: aspectRatio,
crossAxisCount: crossAxisCount,
),
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
return _ItemListView(
onTap: onTap,
item: items[index],
// Todo: add ngos
);
},
),
),
),
],
);
Thank you in advance for your help!
What is the purpose of initialData in FutureBuilder? The documentation says:-
The data that will be used to create the snapshots provided until a non-null future has completed
Does that mean the placeholder data that gets shown to the user when the data is awaiting?
If that was the case then why my FutureBuilder always shows the loading state and not the initialData
My FutureBuilder()
FutureBuilder(
future: _getPosts(),
initialData: _getOfflineData(),
builder: (BuildContext context, AsyncSnapshot<List<Post>> snapshot) {
if (!snapshot.hasData ||
snapshot.data == null ||
snapshot.data.isEmpty ||
snapshot.hasError) {
// Still loading || load null data UI
return ListView(
scrollDirection: Axis.horizontal,
children: [for (var i = 0; i < 5; i++) HorizontalPostShimmer()],
);
}
return ListView.builder(
itemCount: snapshot.data.length,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
final titleExists = doesTitleExist(snapshot.data[index].title);
final title = titleExists
? snapshot.data[index].title
: _translate.translate('unknownTitle');
final imgExists =
doesImageExist(snapshot.data[index].featuredMedia);
final img = imgExists ? snapshot.data[index].featuredMedia : null;
return Container(
width: 300.0,
child: Card(
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
SAPost(data: snapshot.data[index]),
settings: RouteSettings(name: 'SAPost')));
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
img == null
? Container(
child: SaviorImageShimmer(
height: 100,
width: 100,
))
: CachedNetworkImage(
imageUrl: img,
placeholder: (context, url) =>
SaviorImageShimmer(),
errorWidget: (context, url, error) =>
Icon(Icons.error),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'$title',
style: TextStyle(
fontSize: _isMobile ? 18.0 : 22.0,
),
),
),
)
],
),
),
),
),
);
},
);
},
)
And my _getPosts()
Future<List<Post>> _getPosts() {
final Map<String, dynamic> args = {
'IDs': [1983],
'perPage': 10,
'pageNum': 1,
'from': 'horizontalSlider'
};
final Future<List<Post>> posts =
context.read(fetchSpecificPostsByCategoriesProvider(args).future);
return posts;
}
And my _getOfflineData()
List<Post> _getOfflineData() {
final List<Post> cachedPosts =
Hive.box('posts').get('horizontalSlider', defaultValue: <Post>[]);
return cachedPosts;
}
Am I doing something that my FutureBuilder() always returns the ListView about loading?
Or is initialData used in another way
In case if you would like to provide some data while other data is being fetched, you can use the initialData property to provide that data.
The documentation is as clear as it can be. My guess is your initial data is empty. Try removing the following condition from the build function and check again: snapshot.data.isEmpty.
I'm trying to make a Fetch Data on Flutter but my app gives the error:
The getter 'length' was called on null. Receiver: null Tried calling: length.
If I insert a log in result.statusCode, my value return in console.
I tried to consult other projects and documentation, but nothing works. I need the data to be applied to a label or even a text and return, but this is my main problem.
My code:
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class UserList extends StatelessWidget{
final String apiUrl = "myAPI";
Future<List<dynamic>> fetchUsers() async {
var result = await http.get(apiUrl,
headers: {HttpHeaders.authorizationHeader: "Bearer TOKEN"});
if(result.statusCode == 200){
return json.decode(result.body)['results'];
} else{
throw Exception('Não foi possível funcionar');
}
}
bool _sucess(dynamic sucess){
return sucess['sucess'];
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('User List 1'),
),
body: Container(
child: FutureBuilder<List<dynamic>>(
future: fetchUsers(),
// ignore: missing_return
builder: (BuildContext context, AsyncSnapshot snapshot) {
if(snapshot.hasData){
print(_sucess(snapshot.data[0]));
return ListView.builder(
padding: EdgeInsets.all(8),
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index){
return
Card(
child: Column(
children: <Widget>[
ListTile(
leading: CircleAvatar(
radius: 30,
backgroundImage: NetworkImage(snapshot.data[index]['picture']['large'])),
title: Text(_sucess(snapshot.data[index]).toString()),
)
],
),
);
});
}
else {
print(_sucess(snapshot.data[3]));
return ListView.builder(
padding: EdgeInsets.all(8),
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index){
return
Card(
child: Column(
children: <Widget>[
ListTile(
leading: CircleAvatar(
radius: 30,
backgroundImage: NetworkImage(snapshot.data[index]['picture']['large'])),
title: Text(_sucess(snapshot.data[index]).toString()),
)
],
),
);
});
}
},
),
),
);
}
} ```
My JSON:
{
"success": true,
"data": [
{
"id": 15014,
"itens": [
{
"data": "2020-06-23T14:38:03.000Z",
"pac": 6816608,
}
],
"podeImprimir": true
}
]
} ```
When if (snapshot.hasData) returns false, you are still calling .length on snapshot.data, which is why you're receiving an error.
...
else { // This code is executing because (snapshot.hasData) has returned false
print(_sucess(snapshot.data[3]));
return ListView.builder(
padding: EdgeInsets.all(8),
itemCount: snapshot.data.length, // This is causing the error, snapshot.data is null
itemBuilder: (BuildContext context, int index){
...
Set your itemCount some other way, like with a constant- itemCount: 1, or with a variable that is not null.