I'm new to Flutter and attempting to use the http package to fetch data from an external REST API. My code below successfully hits the host server (200), but throws an exception 'Failed to load album.'
Please help me setup an album that maps the custom API's json format and fetches the data to my app. The json body includes an array of objects (projects) and another object (meta). See screenshot below. I'm using this Flutter Docs example, /cookbook/networking/fetch-data, but I'm lost customizing the album to match the REST API json data. Particularly, setting up the album correctly to accommodate an array of objects (projects) and the meta object. Code examples preferred. Thanks in advance!
Example json data from REST API
{
"projects": [
{
"id": "7c635c45-ebab-44fc-8141-2157e062f97d",
"name": "Project 1",
"description": "Project description",
"notice_to_proceed_date": "2024-01-01",
"business_id": 4046,
"status": "construction",
"awarded_amount": "799109.2",
"authorized_amount": "799109.2",
"business_name": "My Business Name"
},
{
"id": "8528ebbb-54db-40e2-ba0d-1a90e3dfdfb7",
"name": "Project 2",
"description": "Project description",
"notice_to_proceed_date": "2024-03-03",
"business_id": 4046,
"status": "construction",
"awarded_amount": "31166.03",
"authorized_amount": "31166.03",
"business_name": "My Business Name"
}
],
"meta": {
"current_page": 1,
"next_page": null,
"prev_page": null,
"total_pages": 1,
"total_count": 2
}
}
My Code
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<Album> fetchAlbum() async {
final response = await http.get(
Uri.parse(''),
// Send authorization headers to the backend
headers: {
HttpHeaders.authorizationHeader: 'Bearer 7bec66...'
},
);
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
class Album {
final String id;
final String name;
final String description;
final String notice_to_proceed_date;
final int business_id;
final String status;
final String awarded_amount;
final String authorized_amount;
final String business_name;
const Album({
required this.id,
required this.name,
required this.description,
required this.notice_to_proceed_date,
required this.business_id,
required this.status,
required this.awarded_amount,
required this.authorized_amount,
required this.business_name,
});
factory Album.fromJson(Map<String, dynamic> json) {
return switch (json) {
{
'id': String id,
'name': String name,
'description': String description,
'notice_to_proceed_date': String notice_to_proceed_date,
'business_id': int business_id,
'status': String status,
'awarded_amount': String awarded_amount,
'authorized_amount': String authorized_amount,
'business_name': String business_name,
} =>
Album(
id: id,
name: name,
description: description,
notice_to_proceed_date: notice_to_proceed_date,
business_id: business_id,
status: status,
awarded_amount: awarded_amount,
authorized_amount: authorized_amount,
business_name: business_name,
),
_=> throw const FormatException('Failed to load album.'),
};
}
}
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;
@override
void initState() {
super.initState();
futureAlbum = fetchAlbum();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: Scaffold(
appBar: AppBar(
title: const Text('Fetch Data Example'),
),
body: Center(
child: FutureBuilder<Album>(
future: futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.name);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
// By default, show a loading spinner.
return const CircularProgressIndicator();
},
),
),
),
);
}
}
Link to the API docs
I'm new to Flutter and attempting to use the http package to fetch data from an external REST API. My code below successfully hits the host server (200), but throws an exception 'Failed to load album.'
Please help me setup an album that maps the custom API's json format and fetches the data to my app. The json body includes an array of objects (projects) and another object (meta). See screenshot below. I'm using this Flutter Docs example, https://docs.flutter.dev/cookbook/networking/fetch-data, but I'm lost customizing the album to match the REST API json data. Particularly, setting up the album correctly to accommodate an array of objects (projects) and the meta object. Code examples preferred. Thanks in advance!
Example json data from REST API
{
"projects": [
{
"id": "7c635c45-ebab-44fc-8141-2157e062f97d",
"name": "Project 1",
"description": "Project description",
"notice_to_proceed_date": "2024-01-01",
"business_id": 4046,
"status": "construction",
"awarded_amount": "799109.2",
"authorized_amount": "799109.2",
"business_name": "My Business Name"
},
{
"id": "8528ebbb-54db-40e2-ba0d-1a90e3dfdfb7",
"name": "Project 2",
"description": "Project description",
"notice_to_proceed_date": "2024-03-03",
"business_id": 4046,
"status": "construction",
"awarded_amount": "31166.03",
"authorized_amount": "31166.03",
"business_name": "My Business Name"
}
],
"meta": {
"current_page": 1,
"next_page": null,
"prev_page": null,
"total_pages": 1,
"total_count": 2
}
}
My Code
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<Album> fetchAlbum() async {
final response = await http.get(
Uri.parse('https://api.demo.platform.infotechinc/appia/v3/projects'),
// Send authorization headers to the backend
headers: {
HttpHeaders.authorizationHeader: 'Bearer 7bec66...'
},
);
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
class Album {
final String id;
final String name;
final String description;
final String notice_to_proceed_date;
final int business_id;
final String status;
final String awarded_amount;
final String authorized_amount;
final String business_name;
const Album({
required this.id,
required this.name,
required this.description,
required this.notice_to_proceed_date,
required this.business_id,
required this.status,
required this.awarded_amount,
required this.authorized_amount,
required this.business_name,
});
factory Album.fromJson(Map<String, dynamic> json) {
return switch (json) {
{
'id': String id,
'name': String name,
'description': String description,
'notice_to_proceed_date': String notice_to_proceed_date,
'business_id': int business_id,
'status': String status,
'awarded_amount': String awarded_amount,
'authorized_amount': String authorized_amount,
'business_name': String business_name,
} =>
Album(
id: id,
name: name,
description: description,
notice_to_proceed_date: notice_to_proceed_date,
business_id: business_id,
status: status,
awarded_amount: awarded_amount,
authorized_amount: authorized_amount,
business_name: business_name,
),
_=> throw const FormatException('Failed to load album.'),
};
}
}
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;
@override
void initState() {
super.initState();
futureAlbum = fetchAlbum();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: Scaffold(
appBar: AppBar(
title: const Text('Fetch Data Example'),
),
body: Center(
child: FutureBuilder<Album>(
future: futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.name);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
// By default, show a loading spinner.
return const CircularProgressIndicator();
},
),
),
),
);
}
}
Link to the API docs https://infotechinc.stoplight.io/docs/appia/df3aded65f065-list-all-projects
Share Improve this question asked Feb 1 at 18:13 JBeeskyJBeesky 3192 silver badges14 bronze badges1 Answer
Reset to default 0Here's the updated code.
The expected response
{
"projects": [
{
"id": "7c635c45-ebab-44fc-8141-2157e062f97d",
"name": "Project 1",
"description": "Project description",
"notice_to_proceed_date": "2024-01-01",
"business_id": 4046,
"status": "construction",
"awarded_amount": "799109.2",
"authorized_amount": "799109.2",
"business_name": "My Business Name"
},
{
"id": "8528ebbb-54db-40e2-ba0d-1a90e3dfdfb7",
"name": "Project 2",
"description": "Project description",
"notice_to_proceed_date": "2024-03-03",
"business_id": 4046,
"status": "construction",
"awarded_amount": "31166.03",
"authorized_amount": "31166.03",
"business_name": "My Business Name"
}
],
"meta": {
"current_page": 1,
"next_page": null,
"prev_page": null,
"total_pages": 1,
"total_count": 2
}
}
The Model It was renamed to match the expected data
class Project {
final String? id;
final String? name;
final String? description;
final DateTime? noticeToProceedDate;
final int? businessId;
final String? status;
final String? awardedAmount;
final String? authorizedAmount;
final String? businessName;
Project({
this.id,
this.name,
this.description,
this.noticeToProceedDate,
this.businessId,
this.status,
this.awardedAmount,
this.authorizedAmount,
this.businessName,
});
Project copyWith({
String? id,
String? name,
String? description,
DateTime? noticeToProceedDate,
int? businessId,
String? status,
String? awardedAmount,
String? authorizedAmount,
String? businessName,
}) =>
Project(
id: id ?? this.id,
name: name ?? this.name,
description: description ?? this.description,
noticeToProceedDate: noticeToProceedDate ?? this.noticeToProceedDate,
businessId: businessId ?? this.businessId,
status: status ?? this.status,
awardedAmount: awardedAmount ?? this.awardedAmount,
authorizedAmount: authorizedAmount ?? this.authorizedAmount,
businessName: businessName ?? this.businessName,
);
factory Project.fromJson(Map<String, dynamic> json) => Project(
id: json["id"],
name: json["name"],
description: json["description"],
noticeToProceedDate: json["notice_to_proceed_date"] == null
? null
: DateTime.parse(json["notice_to_proceed_date"]),
businessId: json["business_id"],
status: json["status"],
awardedAmount: json["awarded_amount"],
authorizedAmount: json["authorized_amount"],
businessName: json["business_name"],
);
Map<String, dynamic> toJson() => {
"id": id,
"name": name,
"description": description,
"notice_to_proceed_date":
"${noticeToProceedDate!.year.toString().padLeft(4, '0')}-${noticeToProceedDate!.month.toString().padLeft(2, '0')}-${noticeToProceedDate!.day.toString().padLeft(2, '0')}",
"business_id": businessId,
"status": status,
"awarded_amount": awardedAmount,
"authorized_amount": authorizedAmount,
"business_name": businessName,
};
}
API call The API call was renamed also note that projects key is a list and we get each items and map it to our project model
Future<List<Project>?> fetchProjects() async {
final response = await http.get(
Uri.parse('https://api.demo.platform.infotechinc/appia/v3/projects'),
// Send authorization headers to the backend
headers: {HttpHeaders.authorizationHeader: 'Bearer 7bec66...'},
);
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return List<Project>.from(
((jsonDecode(response.body)?['projects'] as List?) ?? [])
.map((e) => Project.fromJson(e)));
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
main.dart
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<List<Project>?> futureAlbum;
@override
void initState() {
super.initState();
futureAlbum = fetchProjects();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: Scaffold(
appBar: AppBar(
title: const Text('Fetch Data Example'),
),
body: FutureBuilder<List<Project>?>(
future: futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemBuilder: (_, i) {
final name = snapshot.data?[i].name;
return Text(name ?? "");
},
itemCount: snapshot.data?.length,
shrinkWrap: true,
);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
// By default, show a loading spinner.
return Center(child: const CircularProgressIndicator());
},
),
),
);
}
}