我在尝试在我的主应用程序中实现 futurebuilder 时遇到了一些困难(我已经在其他地方成功地使用了它,但我正在努力弄清楚我正在做的不同事情)。
我已经从一个更大更复杂的应用程序中重新创建了下面的问题,以尝试确定我遇到的问题...如果那里仍然有一些多余的东西,我深表歉意,但因为我不确定是哪一点导致了我我留下的问题。
总之,我正在加载一些 xml 数据,我需要在初始加载中显示某些内容。
如果我将数据加载到 tmp 值中,然后在 setState 中设置状态属性,它可以工作,但我必须为状态属性设置很多任意值,这似乎是错误的。
这是有效的代码版本(好吧,它至少可以运行......事实上,它正在拾取真实事物中的硬编码值,这并不是什么问题,因为我可以将它们设置为我想要的值,并且后续操作可以使一切正确)。
import 'package:flutter/material.dart';
import 'load_xml.dart';
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final String xmlAssetPath = 'data.xml';
DataTree dataTree = DataTree(categories: []);
Category selectedCategory = Category(name: 'not_set', groupNames: []);
Group selectedGroup = Group(name: "not_set", items: []);
String groupName ="Citrus";
Item selectedItem = Item(name: "Orange");
int selectedItemIndex = 0;
void _loadData() async {
DataTree tmpDataTree = await loadXml(xmlAssetPath);
debugPrint ("Loading data tree...");
for (var category in tmpDataTree.categories) {
debugPrint ("Loading category ${category.name}...");
if ((category.isDefault ?? false) && (category.defaultGroupName != null)) {
debugPrint ("Setting groupName to ${category.defaultGroupName}...");
groupName = category.defaultGroupName!;
}
}
debugPrint ("Loading selected group $groupName...");
Group tmpSelectedGroup = await loadGroup(xmlAssetPath, groupName);
for (var item in tmpSelectedGroup.items) {
debugPrint ("Loading item ${item.name}...");
if (item.name == tmpSelectedGroup.defaultItemName) {
selectedItem = item;
selectedItemIndex = tmpSelectedGroup.items.indexOf(selectedItem);
}
}
setState(() {
dataTree = tmpDataTree;
selectedGroup = tmpSelectedGroup;
});
}
@override
void initState() {
super.initState();
_loadData();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: selectedCategory.name,
home: ListView.builder(
itemBuilder: (context, index) => Card(
key: ValueKey(selectedGroup.items[index].name),
child: ListTile(
title: Text(selectedGroup.items[index].name),
subtitle: Text((selectedGroup.items[index].name==selectedItem.name) ? "Selected" : "Not selected"),
),
),
itemCount: selectedGroup.items.length,
),
);
}
}
但理想情况下,我想避免硬编码值,所以我尝试像这样使用 FutureBuilder:
import 'package:flutter/material.dart';
import 'load_xml.dart';
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final String xmlAssetPath = 'data.xml';
Future<DataTree> dataTree;
Category selectedCategory = Category(name: 'not_set', groupNames: []);
Future<Group> selectedGroup;
// Group selectedGroup = Group(name: "not_set", items: []);
String groupName ="Citrus";
Item selectedItem = Item(name: "Orange");
int selectedItemIndex = 0;
void _loadData() async {
dataTree = await loadXml(xmlAssetPath);
debugPrint ("Loading data tree...");
for (var category in dataTree.categories) {
debugPrint ("Loading category ${category.name}...");
if ((category.isDefault ?? false) && (category.defaultGroupName != null)) {
debugPrint ("Setting groupName to ${category.defaultGroupName}...");
groupName = category.defaultGroupName!;
}
}
debugPrint ("Loading selected group $groupName...");
selectedGroup = await loadGroup(xmlAssetPath, groupName);
for (var item in selectedGroup.items) {
debugPrint ("Loading item ${item.name}...");
if (item.name == selectedGroup.defaultItemName) {
selectedItem = item;
selectedItemIndex = selectedGroup.items.indexOf(selectedItem);
}
}
}
@override
void initState() {
super.initState();
_loadData();
}
@override
Widget build(BuildContext context) {
return FutureBuilder (
future: Future.wait([dataTree,selectedGroup]),
builder: (BuildContext context, AsyncSnapshot<List<dynamic>> snapshot) {
if (snapshot.hasData) {
return MaterialApp (
title: snapshot.data![1].name,
home: ListView.builder(
itemBuilder: (context, index) => Card(
key: ValueKey(snapshot.data![1].items[index].name),
child: ListTile(
title: Text(snapshot.data![1].items[index].name),
subtitle: Text((snapshot.data![1].items[index].name==selectedItem.name) ? "Selected" : "Not selected"),
),
),
itemCount: snapshot.data![1].items.length,
)
);
} else {
return Container();
}
}
);
}
}
但我当然明白
lib/my_app_await_not_working.dart:23:16: Error: A value of type 'DataTree' can't be assigned to a variable of type 'Future<DataTree>'.
- 'DataTree' is from 'package:test_init/load_xml.dart' ('lib/load_xml.dart').
- 'Future' is from 'dart:async'.
dataTree = await loadXml(xmlAssetPath);
...
我从here发现是因为我正在使用await,它阻止了它成为未来。
但是如果我这样做..
import 'package:flutter/material.dart';
import 'load_xml.dart';
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final String xmlAssetPath = 'data.xml';
Future<DataTree> dataTree;
Category selectedCategory = Category(name: 'not_set', groupNames: []);
Future<Group> selectedGroup;
// Group selectedGroup = Group(name: "not_set", items: []);
String groupName ="Citrus";
Item selectedItem = Item(name: "Orange");
int selectedItemIndex = 0;
void _loadData() {
dataTree = loadXml(xmlAssetPath);
debugPrint ("Loading data tree...");
for (var category in dataTree.categories) {
debugPrint ("Loading category ${category.name}...");
if ((category.isDefault ?? false) && (category.defaultGroupName != null)) {
debugPrint ("Setting groupName to ${category.defaultGroupName}...");
groupName = category.defaultGroupName!;
}
}
debugPrint ("Loading selected group $groupName...");
selectedGroup = loadGroup(xmlAssetPath, groupName);
for (var item in selectedGroup.items) {
debugPrint ("Loading item ${item.name}...");
if (item.name == selectedGroup.defaultItemName) {
selectedItem = item;
selectedItemIndex = selectedGroup.items.indexOf(selectedItem);
}
}
}
@override
void initState() {
super.initState();
_loadData();
}
@override
Widget build(BuildContext context) {
return FutureBuilder (
future: Future.wait([dataTree,selectedGroup]),
builder: (BuildContext context, AsyncSnapshot<List<dynamic>> snapshot) {
if (snapshot.hasData) {
return MaterialApp (
title: snapshot.data![1].name,
home: ListView.builder(
itemBuilder: (context, index) => Card(
key: ValueKey(snapshot.data![1].items[index].name),
child: ListTile(
title: Text(snapshot.data![1].items[index].name),
subtitle: Text((snapshot.data![1].items[index].name==selectedItem.name) ? "Selected" : "Not selected"),
),
),
itemCount: snapshot.data![1].items.length,
)
);
} else {
return Container();
}
}
);
}
}
然后我得到类似的东西
ib/my_app_fb_not_working.dart:14:20: Error: Field 'dataTree' should be initialized because its type 'Future<DataTree>' doesn't allow null.
- 'Future' is from 'dart:async'.
- 'DataTree' is from 'package:test_init/load_xml.dart' ('lib/load_xml.dart').
Future<DataTree> dataTree;
^^^^^^^^
在这两种情况下我也会遇到类似的错误。
lib/my_app_fb_not_working.dart:25:35: Error: The getter 'categories' isn't defined for the class 'Future<DataTree>'.
- 'Future' is from 'dart:async'.
- 'DataTree' is from 'package:test_init/load_xml.dart' ('lib/load_xml.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'categories'.
for (var category in dataTree.categories) {
^^^^^^^^^^
我尝试了很多不同的方法来声明它们,但不知道如何通过 future 获取数据。
作为参考,如果它有助于复制,这里是我正在测试的其他模块(不要太担心低效/错误的 xml 加载,我可以稍后解决。我认为从测试的角度来看,它工作得足够好这个)....
main.dart
import 'package:flutter/material.dart';
import 'my_app.dart';
void main() {
runApp(const MyApp());
}
load_xml.dart
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
import 'package:xml/xml.dart' as xml;
import 'dart:async';
class DataTree {
List<Category> categories = [];
DataTree({required this.categories});
}
class Category {
final List<String> groupNames;
final String name;
final bool? isDefault;
final String? defaultGroupName;
const Category({required this.groupNames, required this.name, this.isDefault, this.defaultGroupName});
}
class Group {
final List<Item> items;
final String name;
final bool? isDefault;
final String? defaultItemName;
const Group({required this.items, required this.name, this.isDefault, this.defaultItemName});
}
class Item {
final String name;
final bool? isDefault;
const Item({required this.name, this.isDefault});
}
Future<Group> loadGroup(String xmlAssetPath, String groupName) async {
debugPrint ("Inside load group for $groupName");
Group group;
List<Item> itemList = [];
String xmlString = await rootBundle.loadString(xmlAssetPath);
final document = xml.XmlDocument.parse(xmlString);
final groups = document.findAllElements('group')
.where((group) => group.getAttribute('name') == groupName)
.toList();
// do we need these?
String name='not set';
bool isGroupDefault = false;
bool isItemDefault = false;
String defaultItemName='not set';
for (var element in groups) {
String? name = element.getAttribute('name');
debugPrint("Checking group $name");
if (name == groupName) {
debugPrint("Matches $name");
name = element.getAttribute('name').toString();
isGroupDefault = (element.getAttribute('default') == 'true') ? true : false;
final items = element.findElements('item');
for (var item in items) {
String name = item.getAttribute('name').toString();
debugPrint("Parsing item $name");
isItemDefault = (item.getAttribute('default') == 'true') ? true : false;
if (isItemDefault == true) {
defaultItemName = name;
debugPrint ("Got default item $defaultItemName");
} // change this to id at some point
itemList.add ( Item (
name: name,
));
isItemDefault = false;
}
break;
}
}
group = Group (
items: itemList,
name: groupName,
isDefault: isGroupDefault,
defaultItemName: defaultItemName,
);
return group;
}
Future<DataTree> loadXml(String xmlAssetPath) async {
DataTree dataTree;
List<Category> categoryList = [];
String xmlString = await rootBundle.loadString(xmlAssetPath);
final document = xml.XmlDocument.parse(xmlString);
final categories = document.findAllElements('category');
for (var element in categories) {
List<String> groupNames = [];
String categoryName = element.getAttribute('name').toString();
final groups = element.findElements('group');
for (var element in groups) {
String groupName = element.getAttribute('name').toString();
groupNames.add(groupName);
}
categoryList.add(Category(
name: categoryName,
groupNames: groupNames,
));
}
dataTree = DataTree(categories: categoryList);
return dataTree;
}
资产/data.xml
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<data>
<category name="Fruit" default="true">
<group name="Citrus">
<item name="Orange"/>
<item name="Lemon" default="true"/>
<item name="Lime"/>
</group>
<group name="Berry" default="true">
<item name="Strawberry"/>
<item name="Raspberry" default="true"/>
<item name="Gooseberry"/>
</group>
<group name="Exotic">
<item name="Pineapple"/>
<item name="Papaya"/>
<item name="Mango"/>
</group>
</category>
<category name="Vegetable">
<group name="Root">
<item name="Carrot"/>
<item name="Potato"/>
<item name="Parsnip"/>
</group>
<group name="Brassica">
<item name="Cabbage"/>
<item name="Broccoli"/>
<item name="Kale"/>
</group>
</category>
</data>
是时候停止使用 FutureBuilder 并开始使用 Riverpod 了。 Riverpod 可以很好地管理来自异步操作的缓存数据,并将这些数据聚合以获得总体结果。 当我对 Riverpod 提供商感到满意后,我就停止使用 FutureBuilder。