在具有多个列表的单个页面中显示来自 API 的嵌套 JSON

Show nested JSON from API in single Page with multiple lists

我是 Flutter 的新手,我正在努力处理来自 API 的嵌套 JSON 我想在一页中显示哪些数据。

我从 URL 得到这个 JSON 并在 class 中解码它,它工作正常:

{
  "service1": [
    {
      "firstname": "Peter",
      "lastname": "Smith"
    },
    {
      "firstname": "Paul",
      "lastname": "Johnson"
    }
  ],
  "service2": [
    {
      "firstname": "Mary",
      "lastname": "Williams"
    },
    {
      "firstname": "Guy",
      "lastname": "Brown"
    }
  ]
}

类:

/*------------------------------
staff.dart
------------------------------*/
import 'dart:convert';

class Staff {
  String? service;
  String? firstname;
  String? lastname;

  Staff({this.service, this.firstname, this.lastname});

  factory Staff.fromMap(Map<String, dynamic> data) => Staff(
        service: data['service'] as String?,
        firstname: data['firstname'] as String?,
        lastname: data['lastname'] as String?,
      );

  Map<String, dynamic> toMap() => {
        'service': '',
        'firstname': firstname,
        'lastname': lastname,
      };

  /// Parses the string and returns the resulting Json object.
  factory Staff.fromJson(String data) {
    return Staff.fromMap(json.decode(data) as Map<String, dynamic>);
  }

  /// Converts [Staff] to a JSON string.
  String toJson() => json.encode(toMap());
}


/*------------------------------
servicedesk.dart
------------------------------*/
import 'dart:convert';
import 'staff.dart';

class ServiceDesk {
  List<Staff>? service1;
  List<Staff>? service2;

  ServiceDesk({
    this.service1,
    this.service2,
  });

  factory ServiceDesk.fromMap(Map<String, dynamic> data) => ServiceDesk(
        service1: (data['service1'] as List<dynamic>?)
            ?.map((e) => Staff.fromMap(e as Map<String, dynamic>))
            .toList(),
        service2: (data['service2'] as List<dynamic>?)
            ?.map((e) => Staff.fromMap(e as Map<String, dynamic>))
            .toList(),
      );

  Map<String, dynamic> toMap() => {
        'service1': service1?.map((e) => e.toMap()).toList(),
        'service2': service2?.map((e) => e.toMap()).toList(),
      };

  /// Parses the string and returns the resulting Json object as [ServiceDesk].
  factory ServiceDesk.fromJson(String data) {
    var object = ServiceDesk.fromMap(json.decode(data) as Map<String, dynamic>);
    object.b1!.insert(0, Staff(service: 'Title for Service1'));
    object.b2!.insert(0, Staff(service: 'Title for Service2'));
    return object;
  }

  /// Converts to a JSON string.
  String toJson() => json.encode(toMap());
}

这就是我的代码(之间的伪代码):

  // PSEUDOCODE!!
  Widget ListWithService(List<Staff>? entry) {
    return ListView.builder(
        itemCount: entry!.length,
        padding: const EdgeInsets.all(2.0),
        itemBuilder: (context, position) {
          final item = entry[position];
          if (item.service != null) {
            return ListTile(
              title: Text(
                '${item.service}',
                style: Theme.of(context).textTheme.headline5,
              ),
            );
          } else {
            return ListTile(
              title: Text(
                '${item.firstname} ${item.lastname}',
                style: Theme.of(context).textTheme.bodyText1,
              ),
            );
          }
        });
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Service Desk'),
      ),
      body: FutureBuilder<sdclass.ServiceDesk>(
        future: getData(),
        builder: (context, snapshot) {
          if (snapshot.hasData == true) {
            return [
              ListWithService(snapshot.data.service1), 
              ListWithSercice(snapshot.data.service1);
            ] // PSEUDOCODE!!
          } else if (snapshot.hasError) {
            return const Icon(
              Icons.error_outline,
              color: Colors.red,
              size: 60,
            );
          } else {
            return const Center(
              child: CircularProgressIndicator(),
            );
          }
        }
      ),
    );
  }

最后我想要的在整页上应该是这样的:

服务标题1(标题)
彼得·史密斯
保罗·约翰逊

服务标题2(标题)
玛丽·威廉姆斯
盖伊·布朗

有人可以帮我写代码让它工作吗?

更新代码

感谢您更新示例。我在我的代码中测试了它。首先,一切看起来都很好。但是当我切换到带有 json 的屏幕时,我得到一个错误:

Expected a value of type 'FutureOr<Map<String, List<Map<String, String>>>>', but got one of type '_JsonMap'

    import 'dart:convert';
    import 'dart:core';
    
    import 'package:flutter/material.dart';
    import 'package:http/http.dart' as http;
    
    class ServiceDesk extends StatelessWidget {
      const ServiceDesk({Key? key}) : super(key: key);
    
      Future<Map<String, List<Map<String, String>>>> getData() async {
        String link = "https://url-to-json-file.json";
        final res = await http
            .get(Uri.parse(link), headers: {"Accept": "application/json"});
        if (res.statusCode == 200) {
          var utf8decoded = utf8.decode(res.body.toString().codeUnits);
          var decoded = json.decode(utf8decoded);
          return decoded;
        } else {
          throw Exception('Failed to load JSON');
        }
      }
    
      Widget listViewWidget(
          Iterable<MapEntry<String, List<Map<String, String>>>> entries) {
        return ListView.builder(
            itemCount: entries.length,
            padding: const EdgeInsets.all(2.0),
            itemBuilder: (context, index) {
              final entry = entries.elementAt(index);
              final key = entry.key;
              final values = entry.value;
    
              return Column(
                children: [
                  ListTile(
                    title: Text(
                      'Title for $key',
                      style: Theme.of(context).textTheme.headline5,
                    ),
                  ),
                  for (var person in values)
                    ListTile(
                      title: Text(
                        '${person["firstname"]} ${person["lastname"]}',
                        style: Theme.of(context).textTheme.bodyText1,
                      ),
                    ),
                ],
              );
            });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('Service Desk'),
          ),
          body: FutureBuilder(
              future: getData(),
              builder: (_,
                  AsyncSnapshot<Map<String, List<Map<String, String>>>> snapshot) {
                if (snapshot.hasData == true) {
                  final entries = snapshot.data?.entries ?? {};
    
                  return listViewWidget(entries);
                } else if (snapshot.hasError) {
                  return Center(
                      child: Column(
                    mainAxisSize: MainAxisSize.min,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      const Icon(
                        Icons.error_outline,
                        color: Colors.red,
                        size: 60,
                      ),
                      Text("Fehler: ${snapshot.error}"),
                    ],
                  ));
                } else {
                  return const Center(
                    child: CircularProgressIndicator(),
                  );
                }
              }),
        );
      }
    }

在Dart语言中,你可以在列表中使用for循环,这样更容易与Flutter一起工作UI。

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'dart:convert';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const RootView(),
    );
  }
}

class RootView extends StatelessWidget {
  const RootView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: TextButton(
          child: const Text('GO TO SAFF SERVICE'),
          onPressed: () {
            Navigator.push(context, Home.route());
          },
        ),
      ),
    );
  }
}

class Home extends StatefulWidget {
  const Home({Key? key}) : super(key: key);

  static Route route() {
    return CupertinoPageRoute(builder: (_) => const Home());
  }

  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  late Future<StaffService> staffService;

  @override
  void initState() {
    staffService = getStaffService();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Services')),
      body: Center(
        child: FutureBuilder<StaffService>(
          future: staffService,
          builder: (_, snapshot) {
            if (snapshot.hasData) {
              final saff = snapshot.data!;

              return ListView(
                children: [
                  if (saff.service1.isNotEmpty)
                    StaffServiceTile(
                      title: Text(
                        'Title for Service 1',
                        style: Theme.of(context).textTheme.headline5,
                      ),
                      services: saff.service1,
                    ),
                  if (saff.service2.isNotEmpty)
                    StaffServiceTile(
                      title: Text(
                        'Title for Service 2',
                        style: Theme.of(context).textTheme.headline5,
                      ),
                      services: saff.service2,
                    ),
                ],
              );
            } else if (snapshot.hasError) {
              return const Text('Error on loaad data. Try again later.');
            } else {
              return const CircularProgressIndicator();
            }
          },
        ),
      ),
    );
  }
}

class StaffServiceTile extends StatelessWidget {
  const StaffServiceTile({
    Key? key,
    required this.title,
    required this.services,
  }) : super(key: key);

  final Widget title;
  final List<Service> services;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ListTile(title: title),
        for (var person in services)
          ListTile(
            title: Text(
              '${person.firstname} ${person.lastname}',
            ),
          ),
      ],
    );
  }
}

class StaffService {
  StaffService({this.service1 = const [], this.service2 = const []});

  List<Service> service1, service2;

  factory StaffService.fromJson(String str) {
    return StaffService.fromMap(json.decode(str));
  }

  String toJson() => json.encode(toMap());

  factory StaffService.fromMap(Map<String, dynamic> json) => StaffService(
        service1: List<Service>.from(json["service1"].map((x) => Service.fromMap(x))),
        service2: List<Service>.from(json["service2"].map((x) => Service.fromMap(x))),
      );

  Map<String, dynamic> toMap() => {
        "service1": List<dynamic>.from(service1.map((x) => x.toMap())),
        "service2": List<dynamic>.from(service2.map((x) => x.toMap())),
      };
}

class Service {
  Service({this.firstname, this.lastname});

  String? firstname, lastname;

  factory Service.fromJson(String str) => Service.fromMap(json.decode(str));

  String toJson() => json.encode(toMap());

  factory Service.fromMap(Map<String, dynamic> json) => Service(
        firstname: json["firstname"],
        lastname: json["lastname"],
      );

  Map<String, dynamic> toMap() => {
        "firstname": firstname,
        "lastname": lastname,
      };
}

Future<StaffService> getStaffService() async {
  await Future.delayed(const Duration(seconds: 2));
  return StaffService.fromMap(data); // <- use fromJson if you load data from the JSON.
}

final data = <String, List<Map<String, String>>>{
  "service1": [
    {"firstname": "Peter", "lastname": "Smith"},
    {"firstname": "Paul", "lastname": "Johnson"}
  ],
  "service2": [
    {"firstname": "Mary", "lastname": "Williams"},
    {"firstname": "Guy", "lastname": "Brown"}
  ]
};

复制并粘贴到 DartPad 中进行测试。