dart Flutter,null安全使用包含null值的外部数据的最佳方法是什么

xt0899hw  于 2023-05-11  发布在  Flutter
关注(0)|答案(1)|浏览(149)

我还在学习Flutter,并且玩得很开心,这是我开发过的最快的移动的应用程序。我在null安全性方面遇到了一些麻烦:Null check operator used on a null value下面的第一个widget负责在ListTile上呈现一个值列表,第二个是主widget,它创建了Tabs,API响应包含null值,我仍然希望能够使用这些数据,在仍然具有null安全性的情况下,使用这些数据的最佳方法是什么?或者我如何修改下面的代码来帮助实现null安全?
更新:我意识到在响应中有多个值为null。我不能张贴整个React,因为它是太大了,但我已经离开了一部分波纹管。我已经更新了这篇文章,以更好地适应这个问题。

// First widget
import 'package:flutter/material.dart';
import 'package:valoranttools/models/agent_contracts.dart';
import 'package:valoranttools/services/data.dart';

class AgentContractItem extends StatefulWidget {
  const AgentContractItem({super.key});

  @override
  State<AgentContractItem> createState() => _AgentContractItemState();
}

class _AgentContractItemState extends State<AgentContractItem> {
  late Future<Contract> contractData;

  @override
  void initState() {
    super.initState();
    contractData = fetchContracts();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<Contract>(
        future: contractData,
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            var contracts = snapshot.data!;
            print(contracts);

            return ListView.builder(
              itemCount: contracts.data.length,
              itemBuilder: (context, index) {
                var contract = contracts.data[index];

                return ListTile(
                  title: Text(contract.displayName),
                );
              },
            );
          } else if (snapshot.hasError) {
            return Text('${snapshot.error}');
          }

          // By default, show a loading spinner.
          return const Center(
            child: CircularProgressIndicator(),
          );
        });
  }
}
// Second widget or main tabs widget
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:valoranttools/Agents/agent_contract_item.dart';
import 'package:valoranttools/Agents/agent_item.dart';
import 'package:valoranttools/services/data.dart';

import 'package:valoranttools/models/agent.dart';

class AgentTabScreen extends StatefulWidget {
  const AgentTabScreen({super.key});

  @override
  State<AgentTabScreen> createState() => _AgentTabScreenState();
}

class _AgentTabScreenState extends State<AgentTabScreen> {
  late Future<Agent> agentData;

  @override
  void initState() {
    super.initState();
    agentData = fetchAgents();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<Agent>(
      future: agentData,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          var agents = snapshot.data!;

          return DefaultTabController(
            length: 2,
            child: Scaffold(
              appBar: AppBar(
                title: Text('Agents & Contracts'),
                bottom: const TabBar(
                  tabs: [
                    Tab(
                      icon: Icon(FontAwesomeIcons.userNinja),
                    ),
                    Tab(
                      icon: Icon(FontAwesomeIcons.bookMedical),
                    ),
                  ],
                ),
              ),
              body: TabBarView(children: [
                GridView.count(
                  primary: false,
                  padding: const EdgeInsets.all(20.0),
                  crossAxisSpacing: 10.0,
                  crossAxisCount: 2,
                  children: agents.data
                      .map((agent) => AgentItem(agent: agent))
                      .toList(),
                ),
                const AgentContractItem()
              ]),
            ),
          );
        } else if (snapshot.hasError) {
          return Text('${snapshot.error}');
        }

        // By default, show a loading spinner.
        return const Center(
          child: CircularProgressIndicator(),
        );
      },
    );
  }
}
// Portion of API reponse
"data": [
        {
            "uuid": "cae6ab4a-4b4a-69a0-3c7a-48b17e313f52",
            "displayName": "Gekko Contract",
            "displayIcon": null,
            "shipIt": false,
            "freeRewardScheduleUuid": "32cb6884-4f18-65e6-f84d-8ea09821c74b",
            "content": {
                "relationType": "Agent",
                "relationUuid": "e370fa57-4757-3604-3648-499e1f642d3f",
                "chapters": [
                    {
                        "isEpilogue": false,
                        "levels": [
                            {
                                "reward": {
                                    "type": "Spray",
                                    "uuid": "cd56a893-44f1-2b56-a477-08a9652c66f8",
                                    "amount": 1,
                                    "isHighlighted": false
                                },
                                "xp": 20000,
                                "vpCost": 200,
                                "isPurchasableWithVP": true,
                                "doughCost": 0,
                                "isPurchasableWithDough": false
                            },
                            {
                                "reward": {
                                    "type": "PlayerCard",
                                    "uuid": "0d62fdfb-482e-2ddf-87eb-7ca7c88f222f",
                                    "amount": 1,
                                    "isHighlighted": false
                                },
                                "xp": 30000,
                                "vpCost": 200,
                                "isPurchasableWithVP": true,
                                "doughCost": 0,
                                "isPurchasableWithDough": false
                            },
                            {
                                "reward": {
                                    "type": "Title",
                                    "uuid": "e64d8d84-46a5-6a9b-ee09-08af910b3235",
                                    "amount": 1,
                                    "isHighlighted": false
                                },
                                "xp": 40000,
                                "vpCost": 200,
                                "isPurchasableWithVP": true,
                                "doughCost": 0,
                                "isPurchasableWithDough": false
                            },
                            {
                                "reward": {
                                    "type": "Spray",
                                    "uuid": "93a69ca4-4422-1e2f-d7e0-3baa04b745fd",
                                    "amount": 1,
                                    "isHighlighted": false
                                },
                                "xp": 50000,
                                "vpCost": 200,
                                "isPurchasableWithVP": true,
                                "doughCost": 0,
                                "isPurchasableWithDough": false
                            },
                            {
                                "reward": {
                                    "type": "Character",
                                    "uuid": "e370fa57-4757-3604-3648-499e1f642d3f",
                                    "amount": 1,
                                    "isHighlighted": false
                                },
                                "xp": 60000,
                                "vpCost": 200,
                                "isPurchasableWithVP": true,
                                "doughCost": 0,
                                "isPurchasableWithDough": false
                            }
                        ],
                        "freeRewards": null
                    },
                    {
                        "isEpilogue": false,
                        "levels": [
                            {
                                "reward": {
                                    "type": "EquippableCharmLevel",
                                    "uuid": "cdfc181f-4e27-3bb0-633c-4f8e52348cd0",
                                    "amount": 1,
                                    "isHighlighted": false
                                },
                                "xp": 75000,
                                "vpCost": 0,
                                "isPurchasableWithVP": false,
                                "doughCost": 0,
                                "isPurchasableWithDough": false
                            },
                            {
                                "reward": {
                                    "type": "Spray",
                                    "uuid": "eb91c1e4-4553-a885-e9cc-1a9db6a8b344",
                                    "amount": 1,
                                    "isHighlighted": false
                                },
                                "xp": 100000,
                                "vpCost": 0,
                                "isPurchasableWithVP": false,
                                "doughCost": 0,
                                "isPurchasableWithDough": false
                            },
                            {
                                "reward": {
                                    "type": "Title",
                                    "uuid": "e79d9585-4ea7-f8f1-3d4e-31a1e2c71577",
                                    "amount": 1,
                                    "isHighlighted": false
                                },
                                "xp": 150000,
                                "vpCost": 0,
                                "isPurchasableWithVP": false,
                                "doughCost": 0,
                                "isPurchasableWithDough": false
                            },
                            {
                                "reward": {
                                    "type": "PlayerCard",
                                    "uuid": "35e11e04-46d3-9e5f-c2a5-e5beb9a59e97",
                                    "amount": 1,
                                    "isHighlighted": false
                                },
                                "xp": 200000,
                                "vpCost": 0,
                                "isPurchasableWithVP": false,
                                "doughCost": 0,
                                "isPurchasableWithDough": false
                            },
                            {
                                "reward": {
                                    "type": "EquippableSkinLevel",
                                    "uuid": "acab9281-445b-e301-19c6-fe91e3ef27fa",
                                    "amount": 1,
                                    "isHighlighted": false
                                },
                                "xp": 250000,
                                "vpCost": 0,
                                "isPurchasableWithVP": false,
                                "doughCost": 0,
                                "isPurchasableWithDough": false
                            }
                        ],
                        "freeRewards": null
                    }
                ],
                "premiumRewardScheduleUuid": null,
                "premiumVPCost": -1
            },
            "assetPath": "ShooterGame/Content/Contracts/Characters/Aggrobot/Contract_Aggrobot_DataAssetV2"
        },
    ]
}
// Model
import 'dart:convert';

Contract contractFromJson(String str) => Contract.fromJson(json.decode(str));

String contractToJson(Contract data) => json.encode(data.toJson());

class Contract {
    int status;
    List<Datum> data;

    Contract({
        required this.status,
        required this.data,
    });

    factory Contract.fromJson(Map<String, dynamic> json) => Contract(
        status: json["status"],
        data: List<Datum>.from(json["data"].map((x) => Datum.fromJson(x))),
    );

    Map<String, dynamic> toJson() => {
        "status": status,
        "data": List<dynamic>.from(data.map((x) => x.toJson())),
    };
}

class Datum {
    String uuid;
    String displayName;
    String? displayIcon;
    bool shipIt;
    String freeRewardScheduleUuid;
    Content content;
    String assetPath;

    Datum({
        required this.uuid,
        required this.displayName,
        this.displayIcon,
        required this.shipIt,
        required this.freeRewardScheduleUuid,
        required this.content,
        required this.assetPath,
    });

    factory Datum.fromJson(Map<String, dynamic> json) => Datum(
        uuid: json["uuid"],
        displayName: json["displayName"],
        displayIcon: json["displayIcon"],
        shipIt: json["shipIt"],
        freeRewardScheduleUuid: json["freeRewardScheduleUuid"],
        content: Content.fromJson(json["content"]),
        assetPath: json["assetPath"],
    );

    Map<String, dynamic> toJson() => {
        "uuid": uuid,
        "displayName": displayName,
        "displayIcon": displayIcon,
        "shipIt": shipIt,
        "freeRewardScheduleUuid": freeRewardScheduleUuid,
        "content": content.toJson(),
        "assetPath": assetPath,
    };
}

class Content {
    RelationType? relationType;
    String? relationUuid;
    List<Chapter> chapters;
    String? premiumRewardScheduleUuid;
    int premiumVpCost;

    Content({
        this.relationType,
        this.relationUuid,
        required this.chapters,
        this.premiumRewardScheduleUuid,
        required this.premiumVpCost,
    });

    factory Content.fromJson(Map<String, dynamic> json) => Content(
        relationType: relationTypeValues.map[json["relationType"]]!,
        relationUuid: json["relationUuid"],
        chapters: List<Chapter>.from(json["chapters"].map((x) => Chapter.fromJson(x))),
        premiumRewardScheduleUuid: json["premiumRewardScheduleUuid"],
        premiumVpCost: json["premiumVPCost"],
    );

    Map<String, dynamic> toJson() => {
        "relationType": relationTypeValues.reverse[relationType],
        "relationUuid": relationUuid,
        "chapters": List<dynamic>.from(chapters.map((x) => x.toJson())),
        "premiumRewardScheduleUuid": premiumRewardScheduleUuid,
        "premiumVPCost": premiumVpCost,
    };
}

class Chapter {
    bool isEpilogue;
    List<Level> levels;
    List<Reward>? freeRewards;

    Chapter({
        required this.isEpilogue,
        required this.levels,
        this.freeRewards,
    });

    factory Chapter.fromJson(Map<String, dynamic> json) => Chapter(
        isEpilogue: json["isEpilogue"],
        levels: List<Level>.from(json["levels"].map((x) => Level.fromJson(x))),
        freeRewards: json["freeRewards"] == null ? [] : List<Reward>.from(json["freeRewards"]!.map((x) => Reward.fromJson(x))),
    );

    Map<String, dynamic> toJson() => {
        "isEpilogue": isEpilogue,
        "levels": List<dynamic>.from(levels.map((x) => x.toJson())),
        "freeRewards": freeRewards == null ? [] : List<dynamic>.from(freeRewards!.map((x) => x.toJson())),
    };
}

class Reward {
    Type type;
    String uuid;
    int amount;
    bool isHighlighted;

    Reward({
        required this.type,
        required this.uuid,
        required this.amount,
        required this.isHighlighted,
    });

    factory Reward.fromJson(Map<String, dynamic> json) => Reward(
        type: typeValues.map[json["type"]]!,
        uuid: json["uuid"],
        amount: json["amount"],
        isHighlighted: json["isHighlighted"],
    );

    Map<String, dynamic> toJson() => {
        "type": typeValues.reverse[type],
        "uuid": uuid,
        "amount": amount,
        "isHighlighted": isHighlighted,
    };
}

enum Type { PLAYER_CARD, TITLE, EQUIPPABLE_CHARM_LEVEL, CURRENCY, SPRAY, EQUIPPABLE_SKIN_LEVEL, CHARACTER }

final typeValues = EnumValues({
    "Character": Type.CHARACTER,
    "Currency": Type.CURRENCY,
    "EquippableCharmLevel": Type.EQUIPPABLE_CHARM_LEVEL,
    "EquippableSkinLevel": Type.EQUIPPABLE_SKIN_LEVEL,
    "PlayerCard": Type.PLAYER_CARD,
    "Spray": Type.SPRAY,
    "Title": Type.TITLE
});

class Level {
    Reward reward;
    int xp;
    int vpCost;
    bool isPurchasableWithVp;
    int doughCost;
    bool isPurchasableWithDough;

    Level({
        required this.reward,
        required this.xp,
        required this.vpCost,
        required this.isPurchasableWithVp,
        required this.doughCost,
        required this.isPurchasableWithDough,
    });

    factory Level.fromJson(Map<String, dynamic> json) => Level(
        reward: Reward.fromJson(json["reward"]),
        xp: json["xp"],
        vpCost: json["vpCost"],
        isPurchasableWithVp: json["isPurchasableWithVP"],
        doughCost: json["doughCost"],
        isPurchasableWithDough: json["isPurchasableWithDough"],
    );

    Map<String, dynamic> toJson() => {
        "reward": reward.toJson(),
        "xp": xp,
        "vpCost": vpCost,
        "isPurchasableWithVP": isPurchasableWithVp,
        "doughCost": doughCost,
        "isPurchasableWithDough": isPurchasableWithDough,
    };
}

enum RelationType { AGENT, EVENT, SEASON }

final relationTypeValues = EnumValues({
    "Agent": RelationType.AGENT,
    "Event": RelationType.EVENT,
    "Season": RelationType.SEASON
});

class EnumValues<T> {
    Map<String, T> map;
    late Map<T, String> reverseMap;

    EnumValues(this.map);

    Map<T, String> get reverse {
        reverseMap = map.map((k, v) => MapEntry(v, k));
        return reverseMap;
    }
}

dhxwm5r4

dhxwm5r41#

检查fetchContracts和fetchAgents中的数据。此外,Flutter 2.0确实支持null安全,因此可能是一个迁移的想法。但是,是的,在方法中添加打印语句或断点来检查空值。

相关问题