This is my json here: https://my-json-server.typicode.com/fluttirci/testJson/db
This code only works if there is an only one json object however, with this employees JSON, it doesn't work. Flutter documentation isn't very clear about this subject. They only work on one line jsons. What I wanna do is, I wanna get all that data into my phone screen. If I get it, I will show them on a table or a grid. But yet it doesn't won't work. It says type 'Null' is not a subtype of type 'int' . Here is my code:
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<Album> fetchAlbum() async {
final response = await http.get(
Uri.parse('https://my-json-server.typicode.com/fluttirci/testJson/db'));
print(response);
Map<String, dynamic> userMap = jsonDecode(response.body);
if (response.statusCode == 200) {
return Album.fromJson(userMap); //testing
} else {
throw Exception('Failed to load album');
}
}
class Album {
final int userId;
final int id;
final String title;
Album(this.userId, this.id, this.title);
Album.fromJson(Map<String, dynamic> json)
: userId = json['userId'],
id = json['id'],
title = json['title'];
Map<String, dynamic> toJson() => {
'userId': userId,
'id': id,
'title': title,
};
}
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late Future<Album> futureAlbum;
late Future<Album> user;
@override
void initState() {
super.initState();
user = fetchAlbum();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
theme: ThemeData(
brightness: Brightness.dark,
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: const Text('Fetch Data Example'),
),
body: Center(
child: FutureBuilder<Album>(
future: user,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.title);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return const CircularProgressIndicator();
},
),
),
),
);
}
}
CodePudding user response:
Try this:
Future<List<Album>> fetchAlbum() async {
final response = await http.get(
Uri.parse('https://my-json-server.typicode.com/fluttirci/testJson/db'));
print(response);
Map<String, dynamic> userMap = jsonDecode(response.body);
if (response.statusCode == 200) {
return (userMap['employees'] as List).map((e) => Album.fromJson(e)).toList()
} else {
throw Exception('Failed to load album');
}
}
then change your FutureBuilder to this:
FutureBuilder<List<Album>>(
future: user,
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Album> data = snapshot.data ?? [];
return ListView.builder(
itemBuilder: (context, index) {
return Column(children: [
Text(data[index].title ?? ""),
]);
},
itemCount: data.length,
);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return const CircularProgressIndicator();
},
)
CodePudding user response:
Your response.body return a list on employees
key. And test this model with the response
Future<List<Album>?> fetchAlbum() async {
final response = await http.get(
Uri.parse('https://my-json-server.typicode.com/fluttirci/testJson/db'));
if (response.statusCode == 200) {
final data = jsonDecode(response.body)["employees"] as List?;
return data?.map((e) => Album.fromMap(e)).toList();
} else {
throw Exception('Failed to load album');
}
}
class Album {
final int userId;
final int id;
final String title;
Album(this.userId, this.id, this.title);
Map<String, dynamic> toMap() {
final result = <String, dynamic>{};
result.addAll({'userId': userId});
result.addAll({'id': id});
result.addAll({'title': title});
return result;
}
factory Album.fromMap(Map<String, dynamic> map) {
return Album(
map['userId']?.toInt() ?? 0,
map['id']?.toInt() ?? 0,
map['title'] ?? '',
);
}
String toJson() => json.encode(toMap());
factory Album.fromJson(String source) => Album.fromMap(json.decode(source));
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late final user = fetchAlbum();
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FutureBuilder<List<Album>?>(
future: user,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data?.length,
itemBuilder: (context, index) =>
Text("${snapshot.data?[index].title}"),
);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return const CircularProgressIndicator();
},
),
),
);
}
}
CodePudding user response:
so the resultMap
should look right like this :
{
"employees": [
{
"userId": 1,
"id": 2,
"title": "Doe"
},
{
"userId": 2,
"id": 3,
"title": "Smith"
},
{
"userId": 3,
"id": 4,
"title": "Jones"
}
]
}
This is a map that only has one property, which it values as a List
of other maps
so accessing json['userId']
will try to get the userId
from that map, which doesn't exist in the map
you need to access the employees
property :
json["employees"]
then you get the value of it, which is the nested List
of maps, and now you can access an element in the List
with its index, then get the userId
:
json["employees"][0]["userId"] // 1
json["employees"][1]["userId"] // 2
json["employees"][2]["userId"] // 3
hope this gives you a better approach to what you are trying to do, and what you need to do.
so this :
return Album.fromJson(userMap);
should be replaced with this, as an example:
return Album.fromJson(userMap["employees"][0]);
here the userMap["employees"][0]
is :
{
"userId": 1,
"id": 2,
"title": "Doe"
},
and now it should make an Album
object from it.
if you want to get a List instead of the List<Map<string, dynamic>>
, you need to iterate over the whole list using the map
method or with a for loop
hope it helps