我还在学习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;
}
}
1条答案
按热度按时间dhxwm5r41#
检查fetchContracts和fetchAgents中的数据。此外,Flutter 2.0确实支持null安全,因此可能是一个迁移的想法。但是,是的,在方法中添加打印语句或断点来检查空值。