最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

flutter - How to setup Factory Album to map custom REST API json body - Stack Overflow

programmeradmin0浏览0评论

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 badges
Add a comment  | 

1 Answer 1

Reset to default 0

Here'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());
          },
        ),
      ),
    );
  }
}
发布评论

评论列表(0)

  1. 暂无评论