I have a flutter code which runs fine, but at the same time it throws Unexpected null value error in debug console. Can someone please help why this is happening.
import 'package:flutter/material.dart';
import 'dart:convert';
//add this library to get data from the internet
import 'package:http/http.dart' as http;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _jsonString =
'{ "count": 7, "result": [ { "iconId": 1, "id": 1, "name": "Kitchen", "timestamp": 1586951631 }, { "iconId": 2, "id": 2, "name": "android", "timestamp": 1586951646 }, { "iconId": 3, "id": 3, "name": "mobile", "timestamp": 1586951654 }, { "iconId": 4, "id": 4, "name": "bathroom", "timestamp": 1586951665 }, { "iconId": 5, "id": 5, "name": "parking", "timestamp": 1586974393 }, { "iconId": 6, "id": 6, "name": "theatre", "timestamp": 1586974429 }, { "iconId": 7, "id": 7, "name": "bedroom", "timestamp": 1586974457 } ] }';
Future<String> _getDataFromWeb() async {
// http.Response response = await http.get(
// Uri.parse("http://localhost/api/Ticket/GetTickets?username=myuser"),
// headers: {
// 'Content-Type': 'application/json',
// 'Accept': 'application/json'
// },
// );
// if (response.statusCode == 200) {
// // If you are sure that your web service has json string, return it directly
// return response.body;
// } else {
// // create a fake response against any stuation that the data couldn't fetch from the web
// return _jsonString;
// }
return _jsonString;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Your App Title"),
),
body: FutureBuilder<String>(
future: _getDataFromWeb(),
builder: (context, snapshot) {
Map jsonMap = json.decode(snapshot.data!);
return GridView.builder(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemCount: jsonMap['count'],
itemBuilder: (BuildContext c, int i) {
Map resultItem = jsonMap['result'][i];
return Card(
child: Center(child: Text("${resultItem['name']}")),
);
},
);
},
),
);
}
}
Please review the above code and help me find out the problem. A runtime null is generated from somewhere, and it is throwing repeated errors in console. You can run the above code and see if the problem happens.
Loading future takes some time, you need to await until the data is ready,
builder: (context, snapshot) {
if(snapshot.hasData){
Map jsonMap = json.decode(snapshot.data!);
return GridView.builder(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemCount: jsonMap['count'],
itemBuilder: (BuildContext c, int i) {
Map resultItem = jsonMap['result'][i];
return Card(
child: Center(child: Text("${resultItem['name']}")),
);
},
);}
return CircularProgressIndicator();
}
Highly recommend checking this doc example handling error and other states.
Related
I have a local JSON file with a 1000 list. How to apply the Lazy Loading or scrollController function in the Listview.builder?
P/S: I have tried this Flutter ListView lazy loading, but I don't know how to modify it. For example: List items = List.generate(100, (position) => 'Hello $position'); how to change this part to the data that loaded from local JSON file?
Below is post.json file
{
"Food": [
{
"name": "Cake",
"id": "0001",
"description": [
{
"category": "AAA",
"Size": "Regular",
"topping": "Chocolate with Sprinkles"
},
{
"category": "AAA",
"Size": "Small",
"topping": "Chocolate only"
},
{
"category": "BBB",
"Size": "Regular",
"topping": "Maple"
},
{
"category": "BBB",
"Size": "Small",
"topping": "Blueberry"
},
{
"category": "CCC",
"Size": "Medium",
"topping": "Strawberry"
},
{
"category": "CCC",
"Size": "small",
"topping": "banana"
},
{
....another 1000 list
}
]
},
{
"name": "Raised",
"id": "0002",
"description": ... // same structure as above
}
]
}
Below is the Main file
void main() {
runApp(const MyApp());
}
// Load local Json file
Future<Post> getFoodList () async {
final getResponse = await rootBundle.loadString('assets/post.json');
var data = jsonDecode(getResponse);
return Post.fromJson(data);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: FutureBuilder<Post>(
future: getFoodList(),
builder: (context, snapshot) {
if (snapshot.hasData) {
var getFoodName = snapshot.data!.food;
return SingleChildScrollView(
child: Wrap(
children: [
for (final food in getFoodName!)
GestureDetector(
child: Text(foodName.name.toString()),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => foodDetail(food: food)));
},
),
],
),
);
} else {
return Text('Loading');
}
},
),
),
],
),
);
}
}
Below is the food detail page. As there will be a 1000 lists of food details, how do I apply lazy loading, scrollController?
class FoodDetail extends StatefulWidget {
final Food food;
const FoodDetail({super.key, required this.food});
#override
State<FoodDetail> createState() => _FoodDetailState();
}
class _FoodDetailState extends State<FoodDetail> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(8.0),
child: SingleChildScrollView(
child: Column(
children: [
ListView.builder(
itemCount: widget.food.description!.length,
itemBuilder: (context, index) {
return Column(
children: [
Text(widget.food.description![index].category.toString())
Text(widget.food.description![index].topping.toString())
],
);
}
),
],
),
)));
}
}
How to apply Lazy Loading or other better option to load more than 5000 of topping list in the Listview.builder inside another Listview.builder?
Below is post.json which will be loaded from local assets
{
"Food": [
{
"name": "Cake",
"id": "0001",
"description": [
{
"category": "AAA",
"Size": "Regular",
"topping": []
},
{
"category": "BBB",
"Size": "Small",
"topping": []
},
{
"category": "CCC",
"Size": "Medium",
"topping": [
{
"ingredient": "Chocolate with Sprinkles",
"price": "$70"
},
{
"ingredient": "Maple",
"price": "$99"
},
{
"ingredient": "Blueberry",
"price": "$123"
}, ... // more than 5000 of topping list
]
}
]
},
{
"name": "Raised",
"id": "0002",
"description": ... // same structure as above
}
]
}
Below is the Main file to display List of foods
void main() {
runApp(const MyApp());
}
// Load local Json file
Future<Post> getFoodList() => Future.delayed(Duration(seconds: 1), () async {
final getResponse = await rootBundle.loadString('assets/post.json');
var data = jsonDecode(getResponse);
return Post.fromJson(data);
});
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: FutureBuilder<Post>(
future: getFoodList(),
builder: (context, snapshot) {
if (snapshot.hasData) {
var getFoodName = snapshot.data!.food;
return SingleChildScrollView(
child: Wrap(
children: [
for (final food in getFoodName!)
GestureDetector(
child: Text(foodName.name.toString()),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => foodDetail(food: food)));
},
),
],
),
);
} else {
return Text('Loading');
}
},
),
),
],
),
);
}
}
Food Detail page - display more than 5000 of topping list
class FoodDetail extends StatefulWidget {
final Food food;
const FoodDetail({super.key, required this.food});
#override
State<FoodDetail> createState() => _FoodDetailState();
}
class _FoodDetailState extends State<FoodDetail> {
late ScrollController controller;
// This example is taken from Flutter ListView lazy loading
List<String> items = List.generate(100, (position) => 'Hello $position');
#override
void initState() {
super.initState();
controller = ScrollController()..addListener(_scrollListener);
}
#override
void dispose() {
controller.removeListener(_scrollListener);
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(8.0),
child: SingleChildScrollView(
child: Column(
children: [
// This is the first ListView.build to display category and size under description
ListView.builder(
itemCount: widget.food.description!.length,
itemBuilder: (context, index) {
// IF category is AAA || BBB, display the size
if (widget.food.description![index].category == "AAA" || widget.food.description![index].category == "BBB") {
return Text(widget.food.description![index].size.toString());
}
// IF category is CCC display the List of Topping (Note: this topping list is more than 5000)
else {
// This is the second ListView.build to display the topping information
// How do I apply ScollController or Lazy Loading to load 5000 list of topping?
return ListView.builder(
controller: controller,
itemCount: widget.food.description![index].topping!.length,
itemBuilder: (context, position) {
return Column(
children: [
Text(widget.food.description![index].topping![position].ingredient.toString())
Text(widget.food.description![index].topping![position].price.toString())
],
);
});
}
}),
],
),
)));
}
// This example is taken from Flutter ListView lazy loading
void _scrollListener() {
if (controller.position.extentAfter < 500) {
setState(() {
items.addAll(List.generate(42, (index) => 'Inserted $index'));
});
}
}
}
Output of the workflow:
I think your best bet is to try to try to refactor it in a way to use only a single ListView.builder. To do this you need to flatten the data you are working with in some way. Here is an example that also uses nested data that transforms it in a way so a single ListView displays it. I'm not sure if it's the best way to do it but I think you could do something similar for your code maybe. This might give you an idea of how to do it:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
final data = const [
{
"name": "A",
"subnames": ["A1", "A2", "A3", "A4", "A5"]
},
{
"name": "B",
"subnames": ["B1", "B2", "B3", "B4", "B5"]
},
{
"name": "C",
"subnames": ["C1", "C2", "C3", "C4", "C5"]
},
];
const MyApp({super.key});
#override
Widget build(BuildContext context) {
final newData = data
.map((e) => [
{"type": 1, "name": e["name"]},
for (final name in (e["subnames"] as List))
{"type": 2, "name": name}
])
.expand((e) => e.toList())
.toList();
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Padding(
padding: const EdgeInsets.all(8.0),
child: ListView.builder(
itemCount: newData.length,
itemBuilder: (context, index) {
if (newData[index]['type'] == 1) {
return Text(
newData[index]['name'],
style: const TextStyle(color: Colors.red),
);
} else {
return Text(
newData[index]['name'],
style: const TextStyle(color: Colors.blue),
);
}
}),
)),
);
}
}
Output:
I am getting error The method '[]' can't be unconditionally invoked because the receiver can be 'null'.
Mydata inside _categoriesList future
[
{
"business_category_id": 1,
"business_category": "Manufacturer",
"business_category_image": "164423936662011a06c450d.png"
},
{
"business_category_id": 2,
"business_category": "Distributor",
"business_category_image": "164423937762011a11033aa.png"
},
{
"business_category_id": 3,
"business_category": "Wholesaler",
"business_category_image": "164423938762011a1bb2e3c.png"
},
{
"business_category_id": 4,
"business_category": "Retailer",
"business_category_image": "164423940062011a28189e5.png"
},
{
"business_category_id": 5,
"business_category": "Reseller",
"business_category_image": "164423941362011a3554148.png"
},
{
"business_category_id": 6,
"business_category": "Service Provider",
"business_category_image": "164423942462011a4096996.png"
}
]
my code inside futurebuilder. I am having problem accessing snapshot.data[index]
FutureBuilder(
future: _categoriesList,
builder: (_, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: 6,
itemBuilder: (_, index) {
return Text(snapshot.data[index]); // error The method '[]' can't be unconditionally invoked because the receiver can be 'null'
});
}
},
)
Here is the example for ListView.builder:
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future <List<Data>> fetchData() async {
final response =
await http.get('https://jsonplaceholder.typicode.com/albums');
if (response.statusCode == 200) {
List jsonResponse = json.decode(response.body);
return jsonResponse.map((data) => new Data.fromJson(data)).toList();
} else {
throw Exception('Unexpected error occured!');
}
}
class Data {
final int userId;
final int id;
final String title;
Data({this.userId, this.id, this.title});
factory Data.fromJson(Map<String, dynamic> json) {
return Data(
userId: json['userId'],
id: json['id'],
title: json['title'],
);
}
}
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Future <List<Data>> futureData;
#override
void initState() {
super.initState();
futureData = fetchData();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter API and ListView Example',
home: Scaffold(
appBar: AppBar(
title: Text('Flutter ListView'),
),
body: Center(
child: FutureBuilder <List<Data>>(
future: futureData,
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Data> data = snapshot.data;
return
ListView.builder(
itemCount: data.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 75,
color: Colors.white,
child: Center(child: Text(data[index].title),
),);
}
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default show a loading spinner.
return CircularProgressIndicator();
},
),
),
),
);
}
}
Specify your data-type for list in FutureBuilder first.
FutureBuilder<List<String>>()
//
Check if you are getting data. print the data using snapshot.data.
FutureBuilder<List<String>>(
future: _categoriesList,
builder: (_, snapshot) {
if (snapshot.hasData) {
print("snapshot:: data >>>> ${snapshot.data}");
return ListView.builder(
itemCount: 6,
itemBuilder: (_, index) {
return Text(snapshot.data[index]); // error The method '[]' can't be unconditionally invoked because the receiver can be 'null'
});
}
},
)
Assuming you have a Category class, with variables for the CategoryId,CategoryName,CategoryImage with all the needed methods as shown in https://stackoverflow.com/a/71029334/12371668
//create an instance of the category class before the build() method
final category = Category();
#override
Widget build(BuildContext context) {
// other code
FutureBuilder(
future: category.fetchData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Category> categories = snapshot.data();
return ListView.builder(
itemCount: categories.length,
itemBuilder: (context, index) {
var category = categories[index];
return Text(category.CategoryName);
});
}
},
)
}
I'm getting the below JSON as response from my URL. Not sure what's wrong I'm getting the below mentioned error.
Here is my json. I have attached the code from main.dart file too.
{
"data": [
{
"id": 1,
"attributes": {
"Title": "Name One",
"Story": "Body Text of One",
"createdAt": "2022-01-03T17:04:32.919Z",
"updatedAt": "2022-01-03T18:22:02.016Z",
"publishedAt": "2022-01-03T18:22:02.014Z"
}
},
{
"id": 2,
"attributes": {
"Title": "Name Two",
"Story": "Body Text of two",
"createdAt": "2022-01-03T17:04:32.919Z",
"updatedAt": "2022-01-03T18:22:02.016Z",
"publishedAt": "2022-01-03T18:22:02.014Z"
}
},
{
"id": 3,
"attributes": {
"Title": "Name Three",
"Story": "Body Text of three",
"createdAt": "2022-01-03T17:04:32.919Z",
"updatedAt": "2022-01-03T18:22:02.016Z",
"publishedAt": "2022-01-03T18:22:02.014Z"
}
}
],
"meta": {
"pagination": {
"page": 1,
"pageSize": 25,
"pageCount": 1,
"total": 3
}
}
}
This is my main.dart file. I'm trying to get the data and create a ListView.
// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:async';
void main() => runApp(App());
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const Homepage(),
);
}
}
class Homepage extends StatefulWidget {
const Homepage({Key? key}) : super(key: key);
#override
_HomepageState createState() => _HomepageState();
}
class _HomepageState extends State<Homepage> {
Future<List<Story>> _getStories() async {
var response = await http
.get(Uri.parse('https://my.api.com/stories'));
if (response.statusCode == 200) {
List StoriesList = jsonDecode(response.body);
List<Story> stories = [];
for (var storyMap in StoriesList) {
stories.add(Story.fromJson(storyMap));
}
return stories;
} else {
throw Exception('Failed to load stories');
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('News Stories'),
),
body: Container(
child: FutureBuilder(
future: _getStories(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.data == null) {
return Container(
child: Center(
child: CircularProgressIndicator(),
),
);
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(snapshot.data[index].title),
);
},
);
}
},
),
),
);
}
}
class Story {
final String title;
final String body;
Story({required this.title, required this.body});
factory Story.fromJson(Map<String, dynamic> json) {
return Story(
title: json['Title'],
body: json['Story'],
);
}
}
I'm not sure what is wrong here. I'm getting this error.
_TypeError (type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'List<dynamic>')
There are some change in data decode
if (response.statusCode == 200) {
Map responseData = jsonDecode(response.body)/// the json should be Map not List, it is reason causing error
List StoriesList = responseData["data"];
List<Story> stories = [];
for (var storyMap in StoriesList) {
stories.add(Story.fromJson(storyMap["attributes"]));/// Another thing need to be changed according to the json you provided
}
return stories;
} else {
throw Exception('Failed to load stories');
}
This is my list view widget. There are two list view builders, one inside another. I added shrinkWrap property and physics property. Nothing is rendered.I have another doubt when to use list view, single child scroll view and custom scroll view.
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text("Listviews"),
backgroundColor: Colors.blue,
),
body: ListView.builder(
shrinkWrap: true,
itemCount: data == null ? 0 : data.length,
itemBuilder: (BuildContext context, int index) {
if (data[index]["type"] == "single") {
var innerData = data[index]["data"];
return Container(
child: ListView.builder(
shrinkWrap: true,
itemCount: innerData == null ? 0 : innerData.length,
itemBuilder: (BuildContext context, int index) {
String title = innerData[index]["title"];
return Text("$title");
},
),
);
}
},
),
);
}
This is the output screen
This is my json response:
[
{
"type": "single",
"data": [
{
"title": "Fresh Vegetables"
},
{
"title": "Fresh Fruits"
},
{
"title": "Cuts and Sprouts"
},
{
"title": "Exotic Center"
}
]
}
]
I want to do like the flipkart home page. I want to build widgets based on the response. What is the widgets should I use?
Use physics property inside listViewBuilder
shrinkWrap: true,
physics: ClampingScrollPhysics(), /// listView scrolls
I some how copy pasted your code and made some modifications and it worked for me just check the code i have modified :
I have loaded your json locally mentioned below:
[
{
"type": "single",
"data": [
{
"title": "Fresh Vegetables"
},
{
"title": "Fresh Fruits"
},
{
"title": "Cuts and Sprouts"
},
{
"title": "Exotic Center"
}
]
}
]
According to you json class i have created a model class where you can access the specific object from the listview using this model class :
// To parse this JSON data, do
//
// final data = dataFromJson(jsonString);
import 'dart:convert';
List<Data> dataFromJson(String str) => List<Data>.from(json.decode(str).map((x) => Data.fromJson(x)));
String dataToJson(List<Data> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
class Data {
String type;
List<Datum> data;
Data({
this.type,
this.data,
});
factory Data.fromJson(Map<String, dynamic> json) => Data(
type: json["type"],
data: List<Datum>.from(json["data"].map((x) => Datum.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"type": type,
"data": List<dynamic>.from(data.map((x) => x.toJson())),
};
}
class Datum {
String title;
Datum({
this.title,
});
factory Datum.fromJson(Map<String, dynamic> json) => Datum(
title: json["title"],
);
Map<String, dynamic> toJson() => {
"title": title,
};
}
And just check the main file where i have made the changes :
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:sample_testing_project/models.dart';
main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<Data> data = List();
bool _isLoading = false;
#override
void initState() {
// TODO: implement initState
super.initState();
loadYourData();
}
Future<String> loadFromAssets() async {
return await rootBundle.loadString('json/parse.json');
}
loadYourData() async {
setState(() {
_isLoading = true;
});
// Loading your json locally you can make an api call, when you get the response just pass it to the productListFromJson method
String jsonString = await loadFromAssets();
final datamodel = dataFromJson(jsonString);
data = datamodel;
setState(() {
_isLoading = false;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: new Scaffold(
appBar: AppBar(
title: Text("Listviews"),
backgroundColor: Colors.blue,
),
body: ListView.builder(
shrinkWrap: true,
itemCount: data == null ? 0 : data.length,
itemBuilder: (BuildContext context, int index) {
if (data[index].type == "single") {
var innerData = data[index].data;
return Container(
child: ListView.builder(
shrinkWrap: true,
itemCount: innerData == null ? 0 : innerData.length,
itemBuilder: (BuildContext context, int index) {
String title = innerData[index].title;
return Container(
width: MediaQuery.of(context).size.width,
child: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text("$title"),
),
),
);
},
),
);
}
},
),
),
);
}
}