Flutter中使用包含null值的外部数据的最佳方法是什么?

huangapple go评论56阅读模式
英文:

Flutter, null safety what is the best way to use external data that contains null values

问题

以下是您要翻译的代码部分:

// 第一个部件
import 'package:flutter/material.dart';
import 'package:valoranttools/models/agent_contracts.dart';
import 'package:valoranttools/services/data.dart';

class AgentContractItem extends StatefulWidget {
  const AgentContractItem({Key? 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}');
          }

          // 默认情况下,显示加载动画。
          return const Center(
            child: CircularProgressIndicator(),
          );
        });
  }
}

// 第二个部件或主选项卡部件
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({Key? 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}');
        }

        // 默认情况下,显示加载动画。
        return const Center(
          child: CircularProgressIndicator(),
        );
      },
    );
  }
}

希望这有助于您理解代码的结构。如果您需要进一步的帮助,请随时提出。

英文:

I am still learning Flutter and have been having a blast, this has been the fastest I've developed a mobile app ever. I am having a bit of trouble with null safety: Null check operator used on a null value the first widget bellow is responsible for rendering a list of values on a ListTile, and the second is the main widget which creates the Tabs, the API response contains null values, I still want to be able to use this data what is the best way to use the data while still having null safety? Or how can I modify the bellow code to help with null safety?

Update: I've realised that in the response there are multiple values that are null. I can't post the whole response as it is way too big but I have left a portion bellow. I've updated the post to better suit this issue.

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

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

  @override
  State&lt;AgentContractItem&gt; createState() =&gt; _AgentContractItemState();
}

class _AgentContractItemState extends State&lt;AgentContractItem&gt; {
  late Future&lt;Contract&gt; contractData;

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

  @override
  Widget build(BuildContext context) {
    return FutureBuilder&lt;Contract&gt;(
        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(&#39;${snapshot.error}&#39;);
          }

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

import &#39;package:valoranttools/models/agent.dart&#39;;

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

  @override
  State&lt;AgentTabScreen&gt; createState() =&gt; _AgentTabScreenState();
}

class _AgentTabScreenState extends State&lt;AgentTabScreen&gt; {
  late Future&lt;Agent&gt; agentData;

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

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

          return DefaultTabController(
            length: 2,
            child: Scaffold(
              appBar: AppBar(
                title: Text(&#39;Agents &amp; Contracts&#39;),
                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) =&gt; AgentItem(agent: agent))
                      .toList(),
                ),
                const AgentContractItem()
              ]),
            ),
          );
        } else if (snapshot.hasError) {
          return Text(&#39;${snapshot.error}&#39;);
        }

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

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

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

class Contract {
    int status;
    List&lt;Datum&gt; data;

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

    factory Contract.fromJson(Map&lt;String, dynamic&gt; json) =&gt; Contract(
        status: json[&quot;status&quot;],
        data: List&lt;Datum&gt;.from(json[&quot;data&quot;].map((x) =&gt; Datum.fromJson(x))),
    );

    Map&lt;String, dynamic&gt; toJson() =&gt; {
        &quot;status&quot;: status,
        &quot;data&quot;: List&lt;dynamic&gt;.from(data.map((x) =&gt; 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&lt;String, dynamic&gt; json) =&gt; Datum(
        uuid: json[&quot;uuid&quot;],
        displayName: json[&quot;displayName&quot;],
        displayIcon: json[&quot;displayIcon&quot;],
        shipIt: json[&quot;shipIt&quot;],
        freeRewardScheduleUuid: json[&quot;freeRewardScheduleUuid&quot;],
        content: Content.fromJson(json[&quot;content&quot;]),
        assetPath: json[&quot;assetPath&quot;],
    );

    Map&lt;String, dynamic&gt; toJson() =&gt; {
        &quot;uuid&quot;: uuid,
        &quot;displayName&quot;: displayName,
        &quot;displayIcon&quot;: displayIcon,
        &quot;shipIt&quot;: shipIt,
        &quot;freeRewardScheduleUuid&quot;: freeRewardScheduleUuid,
        &quot;content&quot;: content.toJson(),
        &quot;assetPath&quot;: assetPath,
    };
}

class Content {
    RelationType? relationType;
    String? relationUuid;
    List&lt;Chapter&gt; chapters;
    String? premiumRewardScheduleUuid;
    int premiumVpCost;

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

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

    Map&lt;String, dynamic&gt; toJson() =&gt; {
        &quot;relationType&quot;: relationTypeValues.reverse[relationType],
        &quot;relationUuid&quot;: relationUuid,
        &quot;chapters&quot;: List&lt;dynamic&gt;.from(chapters.map((x) =&gt; x.toJson())),
        &quot;premiumRewardScheduleUuid&quot;: premiumRewardScheduleUuid,
        &quot;premiumVPCost&quot;: premiumVpCost,
    };
}

class Chapter {
    bool isEpilogue;
    List&lt;Level&gt; levels;
    List&lt;Reward&gt;? freeRewards;

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

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

    Map&lt;String, dynamic&gt; toJson() =&gt; {
        &quot;isEpilogue&quot;: isEpilogue,
        &quot;levels&quot;: List&lt;dynamic&gt;.from(levels.map((x) =&gt; x.toJson())),
        &quot;freeRewards&quot;: freeRewards == null ? [] : List&lt;dynamic&gt;.from(freeRewards!.map((x) =&gt; 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&lt;String, dynamic&gt; json) =&gt; Reward(
        type: typeValues.map[json[&quot;type&quot;]]!,
        uuid: json[&quot;uuid&quot;],
        amount: json[&quot;amount&quot;],
        isHighlighted: json[&quot;isHighlighted&quot;],
    );

    Map&lt;String, dynamic&gt; toJson() =&gt; {
        &quot;type&quot;: typeValues.reverse[type],
        &quot;uuid&quot;: uuid,
        &quot;amount&quot;: amount,
        &quot;isHighlighted&quot;: isHighlighted,
    };
}

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

final typeValues = EnumValues({
    &quot;Character&quot;: Type.CHARACTER,
    &quot;Currency&quot;: Type.CURRENCY,
    &quot;EquippableCharmLevel&quot;: Type.EQUIPPABLE_CHARM_LEVEL,
    &quot;EquippableSkinLevel&quot;: Type.EQUIPPABLE_SKIN_LEVEL,
    &quot;PlayerCard&quot;: Type.PLAYER_CARD,
    &quot;Spray&quot;: Type.SPRAY,
    &quot;Title&quot;: 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&lt;String, dynamic&gt; json) =&gt; Level(
        reward: Reward.fromJson(json[&quot;reward&quot;]),
        xp: json[&quot;xp&quot;],
        vpCost: json[&quot;vpCost&quot;],
        isPurchasableWithVp: json[&quot;isPurchasableWithVP&quot;],
        doughCost: json[&quot;doughCost&quot;],
        isPurchasableWithDough: json[&quot;isPurchasableWithDough&quot;],
    );

    Map&lt;String, dynamic&gt; toJson() =&gt; {
        &quot;reward&quot;: reward.toJson(),
        &quot;xp&quot;: xp,
        &quot;vpCost&quot;: vpCost,
        &quot;isPurchasableWithVP&quot;: isPurchasableWithVp,
        &quot;doughCost&quot;: doughCost,
        &quot;isPurchasableWithDough&quot;: isPurchasableWithDough,
    };
}

enum RelationType { AGENT, EVENT, SEASON }

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

class EnumValues&lt;T&gt; {
    Map&lt;String, T&gt; map;
    late Map&lt;T, String&gt; reverseMap;

    EnumValues(this.map);

    Map&lt;T, String&gt; get reverse {
        reverseMap = map.map((k, v) =&gt; MapEntry(v, k));
        return reverseMap;
    }
}

Flutter中使用包含null值的外部数据的最佳方法是什么?

答案1

得分: 1

检查fetchContracts和fetchAgents中的数据。此外,Flutter 2.0启用了空安全性,所以迁移可能是一个好主意。但是,确保在方法中添加打印语句或断点以检查空值。

英文:

Check the data in fetchContracts and fetchAgents. Also Flutter 2.0 does enable null safety so could be an idea to migrate. But yes add print statments or breakpoints in methods to check for null values.

答案2

得分: 0

以下是您要翻译的内容:

"After a lot more research of my own, I solved this by changing the models file to make sure any values that could possibly be null had a fallback value, I chose something simple &quot;NA&quot;.

I understand that this may have seemed simple, but I am completely new to Flutter's way of Null safety. I have left the updated model file bellow for anyone who has the same trouble as myself.

import &#39;dart:convert&#39;;

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

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

class Contract {
  int status;
  List&lt;Datum&gt; data;

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

  factory Contract.fromJson(Map&lt;String, dynamic&gt; json) =&gt; Contract(
        status: json[&quot;status&quot;],
        data: List&lt;Datum&gt;.from(json[&quot;data&quot;].map((x) =&gt; Datum.fromJson(x))),
      );

  Map&lt;String, dynamic&gt; toJson() =&gt; {
        &quot;status&quot;: status,
        &quot;data&quot;: List&lt;dynamic&gt;.from(data.map((x) =&gt; 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,
    required this.displayIcon,
    required this.shipIt,
    required this.freeRewardScheduleUuid,
    required this.content,
    required this.assetPath,
  });

  factory Datum.fromJson(Map&lt;String, dynamic&gt; json) =&gt; Datum(
        uuid: json[&quot;uuid&quot;],
        displayName: json[&quot;displayName&quot;],
        displayIcon: json[&quot;displayIcon&quot;] ?? &quot;NA&quot;,
        shipIt: json[&quot;shipIt&quot;],
        freeRewardScheduleUuid: json[&quot;freeRewardScheduleUuid&quot;],
        content: Content.fromJson(json[&quot;content&quot;]),
        assetPath: json[&quot;assetPath&quot;],
      );

  Map&lt;String, dynamic&gt; toJson() =&gt; {
        &quot;uuid&quot;: uuid,
        &quot;displayName&quot;: displayName,
        &quot;displayIcon&quot;: displayIcon,
        &quot;shipIt&quot;: shipIt,
        &quot;freeRewardScheduleUuid&quot;: freeRewardScheduleUuid,
        &quot;content&quot;: content.toJson(),
        &quot;assetPath&quot;: assetPath,
      };
}

class Content {
  RelationType? relationType;
  String? relationUuid;
  List&lt;Chapter&gt; chapters;
  String? premiumRewardScheduleUuid;
  int premiumVpCost;

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

  factory Content.fromJson(Map&lt;String, dynamic&gt; json) =&gt; Content(
        relationType: relationTypeValues.map[json[&quot;relationType&quot;]],
        relationUuid: json[&quot;relationUuid&quot;] ?? &quot;NA&quot;,
        chapters: List&lt;Chapter&gt;.from(json[&quot;chapters&quot;].map((x) =&gt; Chapter.fromJson(x))),
        premiumRewardScheduleUuid: json[&quot;premiumRewardScheduleUuid&quot;] ?? &quot;NA&quot;,
        premiumVpCost: json[&quot;premiumVPCost&quot;],
      );

  Map&lt;String, dynamic&gt; toJson() =&gt; {
        &quot;relationType&quot;: relationTypeValues.reverse[relationType],
        &quot;relationUuid&quot;: relationUuid,
        &quot;chapters&quot;: List&lt;dynamic&gt;.from(chapters.map((x) =&gt; x.toJson())),
        &quot;premiumRewardScheduleUuid&quot;: premiumRewardScheduleUuid,
        &quot;premiumVPCost&quot;: premiumVpCost,
      };
}

class Chapter {
  bool isEpilogue;
  List&lt;Level&gt; levels;
  List&lt;Reward&gt;? freeRewards;

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

  factory Chapter.fromJson(Map&lt;String, dynamic&gt; json) =&gt; Chapter(
        isEpilogue: json[&quot;isEpilogue&quot;],
        levels: List&lt;Level&gt;.from(json[&quot;levels&quot;].map((x) =&gt; Level.fromJson(x))),
        freeRewards: json[&quot;freeRewards&quot;] == null ? [] : List&lt;Reward&gt;.from(json[&quot;freeRewards&quot;].map((x) =&gt; Reward.fromJson(x))),
      );

  Map&lt;String, dynamic&gt; toJson() =&gt; {
        &quot;isEpilogue&quot;: isEpilogue,
        &quot;levels&quot;: List&lt;dynamic&gt;.from(levels.map((x) =&gt; x.toJson())),
        &quot;freeRewards&quot;: freeRewards == null ? [] : List&lt;dynamic&gt;.from(freeRewards!.map((x) =&gt; 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&lt;String, dynamic&gt; json) =&gt; Reward(
        type: typeValues.map[json[&quot;type&quot;]] ?? Type.CHARACTER,
        uuid: json[&quot;uuid&quot;],
        amount: json[&quot;amount&quot;],
        isHighlighted: json[&quot;isHighlighted&quot;],
      );

  Map&lt;String, dynamic&gt; toJson() =&gt; {
        &quot;type&quot;: typeValues.reverse[type],
        &quot;uuid&quot;: uuid,
        &quot;amount&quot;: amount,
        &quot;isHighlighted&quot;: isHighlighted,
      };
}

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

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

class

<details>
<summary>英文:</summary>

After a lot more research of my own, I solved this by changing the models file to make sure any values that could possibly be null had a fallback value, I chose something simple `&quot;NA&quot;`.

I understand that this may have seemed simple, but I am completely new to Flutter&#39;s way of Null safety. I have left the updated model file bellow for anyone who has the same trouble as myself.

```dart
import &#39;dart:convert&#39;;

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

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

class Contract {
  int status;
  List&lt;Datum&gt; data;

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

  factory Contract.fromJson(Map&lt;String, dynamic&gt; json) =&gt; Contract(
        status: json[&quot;status&quot;],
        data: List&lt;Datum&gt;.from(json[&quot;data&quot;].map((x) =&gt; Datum.fromJson(x))),
      );

  Map&lt;String, dynamic&gt; toJson() =&gt; {
        &quot;status&quot;: status,
        &quot;data&quot;: List&lt;dynamic&gt;.from(data.map((x) =&gt; 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,
    required this.displayIcon,
    required this.shipIt,
    required this.freeRewardScheduleUuid,
    required this.content,
    required this.assetPath,
  });

  factory Datum.fromJson(Map&lt;String, dynamic&gt; json) =&gt; Datum(
        uuid: json[&quot;uuid&quot;],
        displayName: json[&quot;displayName&quot;],
        displayIcon: json[&quot;displayIcon&quot;] ?? &quot;NA&quot;,
        shipIt: json[&quot;shipIt&quot;],
        freeRewardScheduleUuid: json[&quot;freeRewardScheduleUuid&quot;],
        content: Content.fromJson(json[&quot;content&quot;]),
        assetPath: json[&quot;assetPath&quot;],
      );

  Map&lt;String, dynamic&gt; toJson() =&gt; {
        &quot;uuid&quot;: uuid,
        &quot;displayName&quot;: displayName,
        &quot;displayIcon&quot;: displayIcon,
        &quot;shipIt&quot;: shipIt,
        &quot;freeRewardScheduleUuid&quot;: freeRewardScheduleUuid,
        &quot;content&quot;: content.toJson(),
        &quot;assetPath&quot;: assetPath,
      };
}

class Content {
  RelationType? relationType;
  String? relationUuid;
  List&lt;Chapter&gt; chapters;
  String? premiumRewardScheduleUuid;
  int premiumVpCost;

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

  factory Content.fromJson(Map&lt;String, dynamic&gt; json) =&gt; Content(
        relationType: relationTypeValues.map[json[&quot;relationType&quot;]],
        relationUuid: json[&quot;relationUuid&quot;] ?? &quot;NA&quot;,
        chapters: List&lt;Chapter&gt;.from(json[&quot;chapters&quot;].map((x) =&gt; Chapter.fromJson(x))),
        premiumRewardScheduleUuid: json[&quot;premiumRewardScheduleUuid&quot;] ?? &quot;NA&quot;,
        premiumVpCost: json[&quot;premiumVPCost&quot;],
      );

  Map&lt;String, dynamic&gt; toJson() =&gt; {
        &quot;relationType&quot;: relationTypeValues.reverse[relationType],
        &quot;relationUuid&quot;: relationUuid,
        &quot;chapters&quot;: List&lt;dynamic&gt;.from(chapters.map((x) =&gt; x.toJson())),
        &quot;premiumRewardScheduleUuid&quot;: premiumRewardScheduleUuid,
        &quot;premiumVPCost&quot;: premiumVpCost,
      };
}

class Chapter {
  bool isEpilogue;
  List&lt;Level&gt; levels;
  List&lt;Reward&gt;? freeRewards;

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

  factory Chapter.fromJson(Map&lt;String, dynamic&gt; json) =&gt; Chapter(
        isEpilogue: json[&quot;isEpilogue&quot;],
        levels: List&lt;Level&gt;.from(json[&quot;levels&quot;].map((x) =&gt; Level.fromJson(x))),
        freeRewards: json[&quot;freeRewards&quot;] == null ? [] : List&lt;Reward&gt;.from(json[&quot;freeRewards&quot;].map((x) =&gt; Reward.fromJson(x))),
      );

  Map&lt;String, dynamic&gt; toJson() =&gt; {
        &quot;isEpilogue&quot;: isEpilogue,
        &quot;levels&quot;: List&lt;dynamic&gt;.from(levels.map((x) =&gt; x.toJson())),
        &quot;freeRewards&quot;: freeRewards == null ? [] : List&lt;dynamic&gt;.from(freeRewards!.map((x) =&gt; 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&lt;String, dynamic&gt; json) =&gt; Reward(
        type: typeValues.map[json[&quot;type&quot;]] ?? Type.CHARACTER,
        uuid: json[&quot;uuid&quot;],
        amount: json[&quot;amount&quot;],
        isHighlighted: json[&quot;isHighlighted&quot;],
      );

  Map&lt;String, dynamic&gt; toJson() =&gt; {
        &quot;type&quot;: typeValues.reverse[type],
        &quot;uuid&quot;: uuid,
        &quot;amount&quot;: amount,
        &quot;isHighlighted&quot;: isHighlighted,
      };
}

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

final typeValues = EnumValues({
  &quot;Character&quot;: Type.CHARACTER,
  &quot;Currency&quot;: Type.CURRENCY,
  &quot;EquippableCharmLevel&quot;: Type.EQUIPPABLE_CHARM_LEVEL,
  &quot;EquippableSkinLevel&quot;: Type.EQUIPPABLE_SKIN_LEVEL,
  &quot;PlayerCard&quot;: Type.PLAYER_CARD,
  &quot;Spray&quot;: Type.SPRAY,
  &quot;Title&quot;: 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&lt;String, dynamic&gt; json) =&gt; Level(
        reward: Reward.fromJson(json[&quot;reward&quot;]),
        xp: json[&quot;xp&quot;],
        vpCost: json[&quot;vpCost&quot;],
        isPurchasableWithVp: json[&quot;isPurchasableWithVP&quot;],
        doughCost: json[&quot;doughCost&quot;],
        isPurchasableWithDough: json[&quot;isPurchasableWithDough&quot;],
      );

  Map&lt;String, dynamic&gt; toJson() =&gt; {
        &quot;reward&quot;: reward.toJson(),
        &quot;xp&quot;: xp,
        &quot;vpCost&quot;: vpCost,
        &quot;isPurchasableWithVP&quot;: isPurchasableWithVp,
        &quot;doughCost&quot;: doughCost,
        &quot;isPurchasableWithDough&quot;: isPurchasableWithDough,
      };
}

enum RelationType { AGENT, EVENT, SEASON }

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

class EnumValues&lt;T&gt; {
  Map&lt;String, T&gt; map;
  late Map&lt;T, String&gt; reverseMap;

  EnumValues(this.map);

  Map&lt;T, String&gt; get reverse {
    reverseMap = map.map((k, v) =&gt; MapEntry(v, k));
    return reverseMap;
  }
}

huangapple
  • 本文由 发表于 2023年5月11日 13:01:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/76224279.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定