How to display nested dependent dynamic data in drop down with flutter? - flutter

I have a json data, I want to display it in drop down such that initial one drop down will shown with name and upon selecting on that it should display child drop down and each child drop down has its own children as well.
class LocationDetail {
final String name;
final String id;
final String categoryId;
final List<LocationDetail>? childrens;
const LocationDetail({
required this.name,
required this.id,
required this.categoryId,
this.childrens,
});
Map<String, dynamic> toMap() {
return {
'name': name,
'id': id,
'categoryId': categoryId,
'childrens': childrens,
};
}
factory LocationDetail.fromMap(Map<String, dynamic> map) {
List<LocationDetail> childrens = [];
if (map['childrens'] != null) {
map['childrens'].forEach((v) {
childrens.add(LocationDetail.fromMap(v));
});
}
return LocationDetail(
name: map['name'] as String,
id: map['id'] as String,
categoryId: map['categoryId'] as String,
childrens: childrens,
);
}
}
Here is the location.json data
[
{
"name": "NewYork",
"id": "NY",
"categoryId": "A",
"childrens": [
{
"name": "Rental Car 1",
"id": "NY-Rental Car1",
"categoryId": "T1"
},
{
"name": "Rental Car 2",
"id": "NY-Rental Car2",
"categoryId": "T2",
"childrens": [
{
"name": "Rental Car 21",
"id": "NY-Rental Car21",
"categoryId": "T21"
},
{
"name": "Rental Car 22",
"id": "NY-Rental Car22",
"categoryId": "T22"
}
]
}
]
}
]
This is how I am reading json and converting to List of locationDetail
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: FutureBuilder<List<LocationDetail>>(
future: _getJsonData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
// all the drop down will come here
} else if (snapshot.hasError) {
return Center(
child: Text(snapshot.error.toString()),
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
Future<List<LocationDetail>> _getJsonData() async {
final contents = await rootBundle.loadString(
'assets/location.json',
);
List<dynamic> dataList = jsonDecode(contents);
List<LocationDetail> locationList =
dataList.map((v) => LocationDetail.fromMap(v)).toList();
return locationList;
}
}
Expected Output:
Initially only one drop down should show which contain only NewYork.
Upon selection on NewYork should show one more drop down which contains Rental Car 1 and Rental Car 2.
If Rental Car 1 is selected nothing should happened since that is the last node.
If Rental Car 2 is selected one more drop down should show to select Rental Car 21 or Rental Car 22.
Same should happened till we reach to end.
P.S Thanks

Related

How to fix this kind of error Unhandled Exception: NoSuchMethodError: The method 'map' was called on null?

I'm trying to make a simple ListView with GetX but it gives me this error when starting the app "Unhandled Exception: NoSuchMethodError: The method 'map' was called on null.", I'm new to flutter and dart, that's why I'm starting with the "easiest" and for work reasons they ask me to add GetX
Home
class HomePage extends GetView<HomeController> {
const HomePage({super.key});
#override
Widget build(BuildContext context) {
// final homeController = Get.put(HomeController());
var title = "HomePage";
return Scaffold(
body: Obx(() {
HomeController controller = Get.find<HomeController>();
return controller.regionList.isEmpty
? const Center(
child: Text('No hay regiones'),
)
: ListView.builder(
itemCount: controller.regionList.length,
itemBuilder: (context, index) => ListTile(
title: Text(
controller.regionList[index].name,
)));
}),
);
}
}
Controller
class HomeController extends GetxController {
//late Regiones model;
var regionList = <Regiones>[].obs;
Future<List<Regiones>> getRegiones() async {
var response = await rootBundle.loadString('assets/response.json');
var results = (jsonDecode(response)['regions'] ?? []) as List;
return results.map((x) => Regiones.fromJson(x)).toList();
//return Regiones.fromJson(jsonDecode(response));
}
//Json['regions'] == null ? Null :
#override
Future<void> onInit() async {
// TODO: implement onInit
super.onInit();
regionList.assignAll(await getRegiones());
}
}
Json
{
"name": "Chile",
"regions": [
{
"name": "Arica y Parinacota",
"romanNumber": "XV",
"number": "15",
"abbreviation": "AP",
"communes": [
{ "name": "Arica", "identifier": "XV-1" },
{ "name": "Camarones", "identifier": "XV-2" },
{ "name": "General Lagos", "identifier": "XV-3" },
{ "name": "Putre", "identifier": "XV-4" }
]
},
{
...
Model
Regiones regionesFromJson(String str) => Regiones.fromJson(json.decode(str));
String regionesToJson(Regiones data) => json.encode(data.toJson());
class Regiones {
Regiones({
required this.name,
required this.regions,
});
String name;
List<Region> regions;
factory Regiones.fromJson(Map<String, dynamic> json) => Regiones(
name: json["name"],
regions:
List<Region>.from(json["regions"].map((x) => Region.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"name": name,
"regions": List<dynamic>.from(regions.map((x) => x.toJson())),
};
}
class Region {
Region({
required this.name,
required this.romanNumber,
required this.number,
required this.abbreviation,
required this.communes,
});
String? name;
String? romanNumber;
String? number;
String? abbreviation;
List<Commune> communes;
factory Region.fromJson(Map<String, dynamic> json) => Region(
name: json["name"],
romanNumber: json["romanNumber"],
number: json["number"],
abbreviation: json["abbreviation"],
communes: List<Commune>.from(
json["communes"].map((x) => Commune.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"name": name,
"romanNumber": romanNumber,
"number": number,
"abbreviation": abbreviation,
"communes": List<dynamic>.from(communes.map((x) => x.toJson())),
};
}
class Commune {
Commune({
required this.name,
required this.identifier,
});
String name;
String identifier;
factory Commune.fromJson(Map<String, dynamic> json) => Commune(
name: json["name"],
identifier: json["identifier"] ?? '',
);
Map<String, dynamic> toJson() => {
"name": name,
"identifier": identifier,
};
}
You call ['regions'] in two place:
1:
var results = (jsonDecode(response)['regions'] ?? []) as List;
2: inside Regiones.fromJson
so in your HomeController instead of this:
return results.map((x) => Regiones.fromJson(x)).toList();
try this:
return results.map((x) => Region.fromJson(x)).toList();
and then make your getRegiones return Future<List> like this:
Future<List<Regione>> getRegiones() async {
...
}

Get strapi datas into Flutter

*After many documentations readed, I saw that Flutter is not compatible with strapi v4, to use it with Flutter, you have to use a
strapi project under v4.
I'm trying to connect my Flutter app to Strapi.
I followed the official Strapi tuto for flutter and some videos on Youtube about it but I'm stuck to read datas.
I have this error while my view begins:
_TypeError (type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'Iterable')
This is my full code for this view:
import 'dart:convert';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:strapitests/user.dart';
class MyList extends StatefulWidget {
const MyList({Key? key}) : super(key: key);
#override
State<MyList> createState() => _MyListState();
}
class _MyListState extends State<MyList> {
List<User> users = [];
Future getAll() async {
var data = await http.get(Uri.parse("http://10.0.2.2:1337/api/apis"));
var jsonData = json.decode(data.body);
for (var u in jsonData) {
users.add(
u['name'],
);
}
return users;
}
#override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder(
future: getAll(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.data == null) {
return Container(
child: const Center(
child: Text("Loading..."),
),
);
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(snapshot.data[index].name),
subtitle: Text(snapshot.data[index].email),
);
},
);
}
},
),
);
}
}
And this is my 'User' class:
class User {
String name;
String email;
String password;
User(this.name, this.email, this.password);
}
While i make a 'GET' on my browser, the result is:
"data": [
{
"id": 1,
"attributes": {
"name": "john",
"password": "dfdf",
"email": "test#gmail.com",
"createdAt": "2022-05-23T20:38:27.725Z",
"updatedAt": "2022-05-23T20:38:28.466Z",
"publishedAt": "2022-05-23T20:38:28.464Z"
}
},
{
"id": 2,
"attributes": {
"name": "text",
"password": "mp",
"email": "mail",
"createdAt": "2022-05-23T20:47:56.717Z",
"updatedAt": "2022-05-23T20:47:56.717Z",
"publishedAt": "2022-05-23T20:47:56.712Z"
}
},
{
"id": 3,
"attributes": {
"name": "name",
"password": "mp",
"email": "mail",
"createdAt": "2022-05-23T20:52:07.911Z",
"updatedAt": "2022-05-23T20:52:07.911Z",
"publishedAt": "2022-05-23T20:52:07.910Z"
}
}
],
Thanks for helping!
First, you will need to decode your users from JSON. Since this is a simple class, you can just write a quick fromJson constructor for your User class:
class User {
String name;
String email;
String password;
User(this.name, this.email, this.password);
factory User.fromJson(Map<String, dynamic> json) {
final attributes = json['attributes'];
return User(
attributes['name'],
attributes['email'],
attributes['password'],
);
}
}
Next, the data you're receiving is a map, which cannot be iterated through with a for-loop.
Instead, iterate over the list keyed by "data", and decode each element with the User.fromJson constructor we just defined:
Future<List<User>> getAll() async {
var data = await http.get(Uri.parse("http://10.0.2.2:1337/api/apis"));
var jsonData = json.decode(data.body);
final users = jsonData['data'];
return users.map((userJson) => User.fromJson(userJson)).toList();
}
Finally, since you're using a FutureBuilder, you actually don't need this to be a stateful widget, and you don't need to store users as a property on your class. You can simply use the list returned in the snapshot - Though you'll need change your code so that the Future is a final member so that the widget doesn't construct a new future on each build:
class MyList extends StatelessWidget {
late final Future<List<User>> users = getAll();
#override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder(
future: users,
// ...
),
);
}
}
Also — and this is beside the point in terms of your question — but it's a good idea to look into ways of avoiding storing passwords on your server. If you do store passwords, definitely avoid returning them in any API responses for a production app :).
Here are a couple of good articles on the topic:
https://auth0.com/blog/hashing-passwords-one-way-road-to-security/
https://auth0.com/blog/adding-salt-to-hashing-a-better-way-to-store-passwords/

LateInitializationError: Field '___' has not been initialized

I got LateInitializationError: Field '_subjects#100178445' has not been initialized Error for Following Code.
Json Response as below.
{
"success": 1,
"subject": [
{
"subject_id": "5e32874c714fa",
"subject_name": "Account",
"image": "upload/subject/Account.png",
"active": "1",
"standard_id": "5d1594e283e1a",
"medium_id": "5d15938aa1344"
},
{
"subject_id": "5da9ff659fb7c",
"subject_name": "Biology",
"image": "upload/subject/03_logo-1164x484.png",
"active": "1",
"standard_id": "5d1594e283e1a",
"medium_id": "5d15938aa1344"
},
{
"subject_id": "5da9ff990b1c6",
"subject_name": "Chemisty",
"image": "upload/subject/02_logo-1168x490.png",
"active": "1",
"standard_id": "5d1594e283e1a",
"medium_id": "5d15938aa1344"
},
{
"subject_id": "5de76afbd064e",
"subject_name": "Computer",
"image": "upload/subject/07_logo-1169x486.png",
"active": "1",
"standard_id": "5d1594e283e1a",
"medium_id": "5d15938aa1344"
},
{
"subject_id": "5d788906c431b",
"subject_name": "Devsatya Paperset March 2020",
"image": "upload/subject/04_logo-1174x491.png",
"active": "1",
"standard_id": "5d1594e283e1a",
"medium_id": "5d15938aa1344"
}
]
}
Model class as bellow in subject_model.dart file.
// To parse this JSON data, do
//
// final subjectByUser = subjectByUserFromJson(jsonString);
import 'dart:convert';
SubjectByUser subjectByUserFromJson(String str) =>
SubjectByUser.fromJson(json.decode(str));
String subjectByUserToJson(SubjectByUser data) => json.encode(data.toJson());
class SubjectByUser {
SubjectByUser({
required this.success,
required this.subject,
});
int success;
List<Subject> subject;
factory SubjectByUser.fromJson(Map<String, dynamic> json) => SubjectByUser(
success: json["success"],
subject:
List<Subject>.from(json["subject"].map((x) => Subject.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"success": success,
"subject": List<dynamic>.from(subject.map((x) => x.toJson())),
};
}
class Subject {
Subject({
required this.subjectId,
required this.subjectName,
required this.image,
required this.active,
required this.standardId,
required this.mediumId,
});
String subjectId;
String subjectName;
String image;
String active;
String standardId;
String mediumId;
factory Subject.fromJson(Map<String, dynamic> json) => Subject(
subjectId: json["subject_id"],
subjectName: json["subject_name"],
image: json["image"],
active: json["active"],
standardId: json["standard_id"],
mediumId: json["medium_id"],
);
Map<String, dynamic> toJson() => {
"subject_id": subjectId,
"subject_name": subjectName,
"image": image,
"active": active,
"standard_id": standardId,
"medium_id": mediumId,
};
}
I created Function as below in apimanager.dart file
class ApiManager {
static const String subjectUrl =
"http://192.168.43.160/sahjanand/api/subject/get_by_user_plan?user_id=609cab2cd5b6c&order_id=1620889722609cd07a601af469889697609cab2cd5b6c&standard_id=5d1594e283e1a&medium_id=5d15938aa1344";
static Future<List<SubjectByUser>> getSubjectByUser() async {
try {
final response = await http.get(Uri.parse(subjectUrl));
if (response.statusCode == 200) {
final List<SubjectByUser> subjects =
subjectByUserFromJson(response.body) as List<SubjectByUser>;
print(subjects);
return subjects;
} else {
return <SubjectByUser>[];
}
} catch (e) {
// ignore: deprecated_member_use
return <SubjectByUser>[];
}
}
}
And view code in homepage.dart file as bellow.
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late List<SubjectByUser> _subjects;
late bool _loading;
#override
void initState() {
// TODO: implement initState
super.initState();
_loading = true;
ApiManager.getSubjectByUser().then((subjects) {
setState(() {
_subjects = subjects;
_loading = false;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_loading ? 'Loading...' : 'Subjects'),
),
body: Center(
child: ListView.builder(
itemCount: null == _subjects ? 0 : _subjects.length,
itemBuilder: (context, index) {
SubjectByUser subjectByUser = _subjects[index];
return ListTile(
title: Text(subjectByUser.success.toString()),
subtitle: Text(subjectByUser.subject.length.toString()),
);
}),
),
);
}
}
i got the LateInitializationError: Field '_subjects#100178445' has not been initialized Error.
So please help me how i solve this error.
You are calling _subjects in ListView.builder to compare, but by that time _subjects are not initalized, So it throws an error, You can initialise
_subjects as empty list
final _subjects = <SubjectByUser>[];
then once you got data, you can add all to it.
ApiManager.getSubjectByUser().then((subjects) {
setState(() {
_subject..clear()..addAll(subjects);
_loading = false;
});
});
and in itemCount, you can simply have
itemCount: _subjects.length,
Try something like:
child: _subjects != null && _subjects.length.length > 0 ? ListView.builder(...) : CircularProgressIndicator();

'_InternalLinkedHashMap<String, dynamic>' has no instance method 'cast' with matching arguments [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
Everytime this runs...
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:rona/Models/Global_model.dart';
class NetworkHelper {
static const String url = 'https://covid-193.p.rapidapi.com/statistics';
static List<GlobalDataModel> parse(String responseBody) {
final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
return parsed
.map<GlobalDataModel>((json) => GlobalDataModel.fromMap(json))
.toList();
}
static Future<List<GlobalDataModel>> getGlobalData() async {
try {
final response = await http.get(url, headers: {
"x-rapidapi-host": "covid-193.p.rapidapi.com",
"x-rapidapi-key": "1d2e200e8amsh264b6230392cdfcp119d06jsn3f9a7a0e8fd0",
"useQueryString": "true"
});
if (response.statusCode == 200) {
List<GlobalDataModel> list = parse(response.body);
return list;
} else {
throw Exception("Error");
}
} catch (e) {
throw Exception(e.toString());
}
}
}
I get the error below. I have looked at other similar problems from other people but none of them seem to work.
E/flutter (26845): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: Exception: NoSuchMethodError: Class '_InternalLinkedHashMap<String, dynamic>' has no instance method 'cast' with matching arguments.
E/flutter (26845): Receiver: _LinkedHashMap len:5
E/flutter (26845): Tried calling: cast<Map<String, dynamic>>()
E/flutter (26845): Found: cast<RK, RV>() => Map<RK, RV>
My corresponding model file looks like this...
import 'package:rona/Networking/Global_data.dart';
class GlobalDataModel {
String continent;
String country;
int population;
int activeCases;
int criticalCases;
int totalCases;
int recovered;
int totalDeaths;
GlobalDataModel({
this.continent,
this.country,
this.population,
this.activeCases,
this.criticalCases,
this.recovered,
this.totalCases,
this.totalDeaths,
});
Future<dynamic> getGlobalData() async {
await NetworkHelper.getGlobalData().then((data) {
print('Data: ${data.length}');
});
}
factory GlobalDataModel.fromMap(Map<String, dynamic> map) {
return GlobalDataModel(
continent: map['response']['continent'] as String,
country: map['response']['country'] as String,
population: map['response']['population'] as int,
activeCases: map['reponse']['cases']['active'] as int,
criticalCases: map['response']['cases']['critical'] as int,
recovered: map['response']['cases']['recovered'] as int,
totalCases: map['response']['cases']['total'] as int,
totalDeaths: map['response']['deaths']['total'] as int,
);
}
}
And the json looks something like this...
{
"get": "statistics",
"parameters": [],
"errors": [],
"results": 227,
"response": [
{
"continent": "Asia",
"country": "China",
"population": 1439323776,
"cases": {
"new": "+9",
"active": 244,
"critical": 5,
"recovered": 80153,
"1M_pop": "59",
"total": 85031
},
"deaths": {
"new": null,
"1M_pop": "3",
"total": 4634
},
"tests": {
"1M_pop": "62814",
"total": 90410000
},
"day": "2020-08-30",
"time": "2020-08-30T17:00:11+00:00"
},
{
"continent": "Europe",
"country": "Italy",
"population": 60447003,
"cases": {
"new": "+1365",
"active": 24205,
"critical": 86,
"recovered": 208536,
"1M_pop": "4437",
"total": 268218
},
"deaths": {
"new": "+4",
"1M_pop": "587",
"total": 35477
},
"tests": {
"1M_pop": "142130",
"total": 8591341
},
"day": "2020-08-30",
"time": "2020-08-30T17:00:11+00:00"
},
{
"continent": "Europe",
"country": "Spain",
"population": 46757684,
"cases": {
"new": "+3829",
"active": 0,
"critical": 751,
"recovered": 0,
"1M_pop": "9744",
"total": 455621
},
"deaths": {
"new": "+15",
"1M_pop": "620",
"total": 29011
},
"tests": {
"1M_pop": "182161",
"total": 8517446
},
"day": "2020-08-30",
"time": "2020-08-30T17:00:11+00:00"
}]
Please help me out, i have been on this for a while now.
dummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytextdummytext
You are supplying the type arguments to cast incorrectly. You give it the type arguments of the keys and values that you want, not of the Map itself:
final parsed = json.decode(responseBody).cast<String, dynamic>();
For converted JSON strings, though, it's generally safe to just do a normal cast using the as operator:
final parsed = json.decode(responseBody) as Map<String, dynamic>;
Or an implicit cast by giving parsed an explicit type:
final Map<String, dynamic> parsed = json.decode(responseBody);
You can copy paste run full code below
To return List<GlobalDataModel>, you can use List<GlobalDataModel>.from( parsed["response"]
code snippet
static List<GlobalDataModel> parse(String responseBody) {
final Map<String, dynamic> parsed = json.decode(responseBody);
return List<GlobalDataModel>.from(
parsed["response"].map((x) => GlobalDataModel.fromJson(x)));
}
...
factory GlobalDataModel.fromJson(Map<String, dynamic> map) {
return GlobalDataModel(
continent: map['continent'] as String,
country: map['country'] as String,
population: map['population'] as int,
activeCases: map['cases']['active'] as int,
criticalCases: map['cases']['critical'] as int,
recovered: map['cases']['recovered'] as int,
totalCases: map['cases']['total'] as int,
totalDeaths: map['deaths']['total'] as int,
);
working demo
full code
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
class NetworkHelper {
static const String url = 'https://covid-193.p.rapidapi.com/statistics';
static List<GlobalDataModel> parse(String responseBody) {
final Map<String, dynamic> parsed = json.decode(responseBody);
return List<GlobalDataModel>.from(
parsed["response"].map((x) => GlobalDataModel.fromJson(x)));
}
static Future<List<GlobalDataModel>> getGlobalData() async {
try {
final response = await http.get(url, headers: {
"x-rapidapi-host": "covid-193.p.rapidapi.com",
"x-rapidapi-key": "1d2e200e8amsh264b6230392cdfcp119d06jsn3f9a7a0e8fd0",
"useQueryString": "true"
});
if (response.statusCode == 200) {
print(response.body);
List<GlobalDataModel> list = parse(response.body);
return list;
} else {
throw Exception("Error");
}
} catch (e) {
throw Exception(e.toString());
}
}
}
class GlobalDataModel {
String continent;
String country;
int population;
int activeCases;
int criticalCases;
int totalCases;
int recovered;
int totalDeaths;
GlobalDataModel({
this.continent,
this.country,
this.population,
this.activeCases,
this.criticalCases,
this.recovered,
this.totalCases,
this.totalDeaths,
});
Future<dynamic> getGlobalData() async {
await NetworkHelper.getGlobalData().then((data) {
print('Data: ${data.length}');
});
}
factory GlobalDataModel.fromJson(Map<String, dynamic> map) {
return GlobalDataModel(
continent: map['continent'] as String,
country: map['country'] as String,
population: map['population'] as int,
activeCases: map['cases']['active'] as int,
criticalCases: map['cases']['critical'] as int,
recovered: map['cases']['recovered'] as int,
totalCases: map['cases']['total'] as int,
totalDeaths: map['deaths']['total'] as int,
);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Future<List<GlobalDataModel>> _future;
#override
void initState() {
_future = NetworkHelper.getGlobalData();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: FutureBuilder(
future: _future,
builder: (context, AsyncSnapshot<List<GlobalDataModel>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('none');
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
case ConnectionState.active:
return Text('');
case ConnectionState.done:
if (snapshot.hasError) {
return Text(
'${snapshot.error}',
style: TextStyle(color: Colors.red),
);
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Card(
elevation: 6.0,
child: Padding(
padding: const EdgeInsets.only(
top: 6.0,
bottom: 6.0,
left: 8.0,
right: 8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(snapshot.data[index].country
.toString()),
Spacer(),
Text(snapshot.data[index].totalCases
.toString()),
],
),
));
});
}
}
}));
}
}

How to parse nested json with FutureProvider in Flutter

I am trying to parse a nested JSON document in my app. The JSON structure looks like this:
[
{
"id": 1,
"content": [
{
"type": "text",
"value": "This is a Text1"
},
{
"type": "latex",
"value": "\frac00"
},
{
"type": "text",
"value": "This is a Text2"
},
{
"type": "latex",
"value": "\frac00"
},
{
"type": "text",
"value": "This is a Text3"
}
]
},
{
"id": 2,
"content": [
{
"type": "text",
"value": "This is a Text"
}
]
}
]
And here are my model classes:
class Tutorial {
String id;
List<Content> content;
Tutorial({this.id, this.content});
Tutorial.fromJson(Map<String, dynamic> json) {
id = json['id'];
if (json['content'] != null) {
content = new List<Content>();
json['content'].forEach((v) {
content.add(new Content.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
if (this.content != null) {
data['content'] = this.content.map((v) => v.toJson()).toList();
}
return data;
}
}
class Content {
String type;
String value;
Content({this.type, this.value});
Content.fromJson(Map<String, dynamic> json) {
type = json['type'];
value = json['value'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['type'] = this.type;
data['value'] = this.value;
return data;
}
}
This is how I retrieve that Json and make the response object:
import 'package:Mathzi/pages/courses/models/tutorialModel.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'dart:async' show Future;
import 'dart:convert' as convert;
class TutorialService {
Future<List> fetchTutorial() async {
var response = await rootBundle.loadString('assets/tutorial.json');
final jsonResponse = convert.jsonDecode(response) as List;
return jsonResponse.map((tutorial) => Tutorial.fromJson(tutorial)).toList();
}
}
And here are my Screen Widget tree:
final TutorialService tutorialService = TutorialService();
#override
Widget build(BuildContext context) {
return FutureProvider(
create: (context) => tutorialService.fetchTutorial(),
catchError: (context, error) => print(error.toString()),
child: SizeTransition(
axis: Axis.vertical,
sizeFactor: animation,
child: GestureDetector(
//behavior: HitTestBehavior.opaque,
onTap: onTap,
child: SizedBox(
height: 50.0,
width: MediaQuery.of(context).size.width,
child: TutParagraph()
),
),
),
);
}
And my TutParagraph.dart:
import 'package:Mathzi/pages/courses/models/tutorialModel.dart';
import 'package:catex/catex.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'models/tutorialModel.dart';
class TutParagraph extends StatelessWidget {
const TutParagraph({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
List<Content> parag = Provider.of<List<Content>>(context);
return (parag == null)
? Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: parag.length,
itemBuilder: (context, index) {
if (parag[index].type.toString() == "text")
return Text(parag[index].value.toString());
else if (parag[index].type.toString() == "latex")
return CaTeX(parag[index].value.toString());
else
return null;
},
);
}
}
if the type is equal to text I use a Text() widget to display it and if it is latex I use CaTex()
When I run my code it gives me this error message:
Error:
Could not find the correct Provider<List> above this
TutParagraph Widget
To fix, please:
Ensure the Provider<List> is an ancestor to this
TutParagraph Widget * Provide types to Provider<List> *
Provide types to Consumer<List> * Provide types to
Provider.of<List>() * Ensure the correct context is being
used.
The best solution is to try to cast and explicitly tell the type of object the List uses to avoid this sort of problems instead of let it infere it
class TutorialService {
Future<List<Tutorial>> fetchTutorial() async { //Tell the trturn type of the List
var response = await rootBundle.loadString('assets/tutorial.json');
final jsonResponse = convert.jsonDecode(response) as List;
return jsonResponse.map<Tutorial>((tutorial) => Tutorial.fromJson(tutorial)).toList();
//Cast the type in the map method <Tutorial>
}
}
Again in the FutureProvider
FutureProvider<List<Tutorial>>( //perhaps it can infere it correctly now that the return type explicitly says is a List<Tutorial>, but lets add it anyway just in case
create: (context) => tutorialService.fetchTutorial(),
catchError: (context, error) => print(error.toString()),
child: ...
)
And in TutParagraph
class TutParagraph extends StatelessWidget {
const TutParagraph({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
List<Tutorial> tutorial = Provider.of<List<Tutorial>>(context); //it should find the FutureProvider
List<Content> parag = (tutorial?.isEmpty ?? true) ? null : tutorial[0].content; //but this will only give you the list of the first element of the tutorial List
return (parag == null)
? Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: parag.length,
itemBuilder: (context, index) {
if (parag[index].type.toString() == "text")
return Text(parag[index].value.toString());
else if (parag[index].type.toString() == "latex")
return CaTeX(parag[index].value.toString());
else
return null;
},
);
}
}
Now if you want to retrieve only a List<Content> you should try to change the logic of tutorialService.fetchTutorial() to return only that type of list, because the Provider doesn't know what types are inside of Tutorial and obviously if you have a List<Tutorial> it doesn't know the List<Content> of what index of the list of Tutorial you really want