我是 Flutter 和 GETX 的新手...
我的要求是从 API 获取选项卡名称...而不是从 API 获取选项卡数据,基于每个选项卡选择...
我在 tabview here 中发现了 getx 用法,但我不知道如何使其与 API 响应一起使用。我试图通过在我的 onInit
类中的 controller
方法中添加 api 调用来实现这一点,但没有成功....
这是我的控制器....
class MyTabController extends GetxController with SingleGetTickerProviderMixin {
var isLoading = true.obs;
var tabNames= List<TabNameModel>().obs;
List<Tab> myTabs = <Tab>[].obs;
TabController controller;
void fetchApiData() async {
isLoading(true);
try {
var response = await <HTTP API Call>;
tabNames
.assignAll(response != null ? tabNamesFromJson(response) : null);
for (TabNameModel tabname in TabNameModel.value) {
myTabs.add(Tab(text: tabname.name));
}
} finally {
isLoading(false);
}
}
@override
void onInit() {
fetchApiData()
super.onInit();
controller = TabController(vsync: this, length: myTabs.length);
}
@override
void onClose() {
controller.dispose();
super.onClose();
}
}
这是我的屏幕......
class MyTabbedWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final MyTabController _tabx = Get.put(MyTabController());
// ↑ init tab controller
return Scaffold(
appBar: AppBar(
bottom: TabBar(
controller: _tabx.controller,
tabs: _tabx.myTabs,
),
),
body: TabBarView(
controller: _tabx.controller,
children: _tabx.myTabs.map((Tab tab) {
final String label = tab.text.toLowerCase();
return Center(
child: Text(
'This is the $label tab',
style: const TextStyle(fontSize: 36),
),
);
}).toList(),
),
);
}
}
答案 0 :(得分:1)
MyTabbedWidget
试图在 myTabs
异步调用完成填充 fetchApiData
之前使用 Controller 中的 myTabs
。 myTabs
为空,直到 fetch 调用完成。
TabBarView
将尝试访问长度为零的 myTabs
,直到 API 调用完成。 Flutter TabController 长度不能为零,否则会抛出错误,我想您已经看到了。
两种解决方案:阻塞和非阻塞
一种解决方案是在应用程序启动之前进行fetchApiData
异步调用并等待它完成后再继续。在 Bindings 类中完成。这将延迟页面的加载,直到调用完成。如果没问题,这将是一个潜在的解决方案:
// make main ↓ async
void main() async {
await MyBlockingBindings().dependencies();
// ↑ make API call prior to app start, wait for results
runApp(MyApp());
}
class MyBlockingBindings extends Bindings {
List<Tab> loadedTabs = [];
@override
Future<void> dependencies() async {
// ↓ long-duration async call to load tab data
await Future.delayed(Duration(seconds: 2),
() => loadedTabs = [
Tab(text: 'BlockedLeft'),
Tab(text: 'BlockedRight')
]
);
// ↓ Register controller using fetched tab data
Get.put(MyTabController(myTabs: loadedTabs));
}
}
class MyTabController extends GetxController with SingleGetTickerProviderMixin {
List<Tab> myTabs;
// ↓ Constructor can now take myTabs argument
MyTabController({this.myTabs});
TabController controller;
@override
void onInit() {
super.onInit();
controller = TabController(vsync: this, length: myTabs.length);
}
@override
void onClose() {
controller.dispose();
super.onClose();
}
}
因为我们在 Get.put(MyTabController())
中做了 MyBlockingBindings
,所以我们可以在我们的视图小部件中使用 Get.find()
。
class MyTabbedWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final MyTabController _tabx = Get.find();
// ↑ controller already init in Bindings, just find it
return Scaffold(
appBar: AppBar(
bottom: TabBar(
controller: _tabx.controller,
tabs: _tabx.myTabs,
),
),
body: TabBarView(
controller: _tabx.controller,
children: _tabx.myTabs.map((Tab tab) {
final String label = tab.text.toLowerCase();
return Center(
child: Text(
'This is the $label tab',
style: const TextStyle(fontSize: 36),
),
);
}).toList(),
),
);
}
}
其余部分与 the example you followed 相同。
此解决方案立即加载占位符标签数据,然后在它们到达后将占位符数据与从 API 调用加载的标签交换。
(假的)2 秒。 API 调用 asyncLoadTabs()
在 MyTabController
onInit()
中完成。请注意,我们在此处未使用 await
,并且 onInit
未制作为 async
。我们不想阻止处理。异步调用将在 Flutter 的事件循环处理它时运行。
在 MyTabbedWidget
中,我们将所有内容都包装在一个 GetBuilder<MyTabController>
小部件中。当我们在 update()
中调用 MyTabController
时,我们的 GetBuilder 将使用最新数据重建自身。
TabBar onTap:
调用控制器的 switchTab(index)
,后者又调用带有所选标签索引的 asyncLoadTabs
,使用标签 # 进行另一个 API 调用。
void main() async {
runApp(MyApp());
}
class MyTabController extends GetxController with SingleGetTickerProviderMixin {
List<Tab> myTabs = <Tab>[
Tab(text: 'loading...'),
];
// ↓ Constructor can now take myTabs argument
MyTabController({myTabs}) {
this.myTabs ??= myTabs;
}
TabController controller;
@override
void onInit() {
super.onInit();
controller = TabController(vsync: this, length: myTabs.length);
asyncLoadTabs();
}
// Fake 2 sec. async call
void asyncLoadTabs({int index = 0}) async {
await Future.delayed(Duration(seconds: 2), () {
myTabs = [
Tab(text: 'LEFT $index'),
Tab(text: 'RIGHT $index'),
];
controller.dispose(); // release animation resources
// recreate TabController as length is final/cannot change ↓
controller = TabController(
vsync: this,
length: myTabs.length,
initialIndex: index // to show a particular tab on create
);
update();
// ↑ rebuilds GetBuilder widget with latest controller data
});
}
void switchTab(int index) async {
asyncLoadTabs(index: index);
}
@override
void onClose() {
controller.dispose();
super.onClose();
}
}
class MyTabbedWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// ↓ use GetBuilder & rebuild using update()
return GetBuilder<MyTabController>(
init: MyTabController(),
builder: (_tabx) => Scaffold(
appBar: AppBar(
bottom: TabBar(
controller: _tabx.controller,
tabs: _tabx.myTabs,
onTap: _tabx.switchTab, // receives tab # on tab click
),
),
body: TabBarView(
controller: _tabx.controller,
children: _tabx.myTabs.map((Tab tab) {
final String label = tab.text.toLowerCase();
return Center(
child: Text(
'This is the $label tab',
style: const TextStyle(fontSize: 36),
),
);
}).toList(),
),
),
);
}
}
答案 1 :(得分:0)
这是我的代码,从我的 API 中,我将所有类别和食物嵌套在类别中。 接口响应
{
"data": {
"getOneRestaurant": {
"error": false,
"msg": "Restaurant Get Successfully",
"data": {
"cover_img": "https://i.ibb.co/YNZ64QG/0-89399200-1551782137-fast1.jpg",
"description": "",
"address": {
"address": "21 KDA Approach Rd, Khulna 9200, Bangladesh"
},
"food_categories": [
{
"_id": "5fa122713cf61557a65d0a12",
"name": "Fast Food",
"foods": [
{
"_id": "5fcc709678070b0098203a0f",
"name": "Chicken reshmi kabab",
"description": "",
"dish_img": "https://i.ibb.co/kHGcn3v/Indian-chicken-kebab-Getty-Images-91279048-58eee4623df78cd3fcd0c6ca.jpg",
"price": 320,
"price_and_size": []
},
{
"_id": "5fcc719178070b0098203a10",
"name": "Kacchi biriyani",
"description": "",
"dish_img": "https://i.ibb.co/Zmp3yp5/47125153f54b972670697f49dac933cc.jpg",
"price": 230,
"price_and_size": []
},
{
"_id": "5fcc722578070b0098203a11",
"name": "Chicken tikka ",
"description": "",
"dish_img": "https://i.ibb.co/M2sLTqP/img-20161210-221320-largejpg.jpg",
"price": 170,
"price_and_size": []
},
{
"_id": "5fcc72f478070b0098203a12",
"name": "Chicken tandoori 1 pcs",
"description": "",
"dish_img": "https://i.ibb.co/LZw5Fp2/chicken-tandori-1526595014.jpg",
"price": 170,
"price_and_size": []
},
{
"_id": "5fce042d78070b0098203b1b",
"name": "Special thai soup for 4 person",
"description": "",
"dish_img": "https://i.ibb.co/YtmVwmm/download.jpg",
"price": 300,
"price_and_size": []
},
{
"_id": "5fce048b78070b0098203b1c",
"name": "Thai clear soup for 4 person",
"description": "",
"dish_img": "https://i.ibb.co/BjcRvNL/tomyum800-56a9498e5f9b58b7d0f9ea4f.jpg",
"price": 250,
"price_and_size": []
},
{
"_id": "5fce04d078070b0098203b1d",
"name": "Chicken vegetables soup four 4 person",
"description": "",
"dish_img": "https://i.ibb.co/ZN3Bxnk/chicken-vegetable-soup-9-1200.jpg",
"price": 180,
"price_and_size": []
},
{
"_id": "5fce050678070b0098203b1e",
"name": "Russian salad",
"description": "",
"dish_img": "https://i.ibb.co/vxh9qGZ/download.jpg",
"price": 200,
"price_and_size": []
},
{
"_id": "5fce053378070b0098203b1f",
"name": "Green salad",
"description": "",
"dish_img": "https://i.ibb.co/XpwwB8Y/green-salad-1200-1387.jpg",
"price": 100,
"price_and_size": []
},
{
"_id": "5fce056878070b0098203b20",
"name": "French fries",
"description": "",
"dish_img": "https://i.ibb.co/NCPsK6Y/Copycat-Mc-Donalds-French-Fries-500x500.jpg",
"price": 60,
"price_and_size": []
},
{
"_id": "5fce059a78070b0098203b21",
"name": "Chicken fry 4 pic",
"description": "",
"dish_img": "https://i.ibb.co/9hwPhgd/download-1.jpg",
"price": 180,
"price_and_size": []
},
{
"_id": "5fce05dc78070b0098203b22",
"name": "Chicken burger",
"description": "",
"dish_img": "https://i.ibb.co/HnJH38T/Butchies-2.jpg",
"price": 80,
"price_and_size": []
},
{
"_id": "5fce060078070b0098203b23",
"name": "Chicken pizza ",
"description": "",
"dish_img": "https://i.ibb.co/WWXzqdk/download.jpg",
"price": 120,
"price_and_size": []
},
{
"_id": "5fce062a78070b0098203b24",
"name": "Chicken naan",
"description": "",
"dish_img": "https://i.ibb.co/cgLg923/download-1.jpg",
"price": 60,
"price_and_size": []
}
]
}
]
}
}
}
}
标签
TabBar(
isScrollable: true,
labelPadding: EdgeInsets.symmetric(horizontal: width * 20),
controller: _tabController,
labelColor: Color(0xffC8102E),
unselectedLabelColor: Colors.black,
labelStyle: TextStyle(
fontWeight: FontWeight.bold
),
unselectedLabelStyle: TextStyle(
fontWeight: FontWeight.normal
),
indicatorColor: Color(0xffC8102E),
tabs: profile.foodCategories.map((e) => Tab(text: e.name)).toList(),
)
身体
TabBarView(
controller: _tabController,
children: profile.foodCategories.map((RestaurantFoodCategories e) {
return itemList(e.foods, e.id);
}).toList(),
)
项目列表
Widget itemList(List<RestaurantFoods> items,String id) {
return ListView.builder(
primary: false,
itemCount: items.length ?? 0,
padding: EdgeInsets.zero,
shrinkWrap: true,
physics: AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index){
RestaurantFoods item = items[index];
return itemCard(item, id);
},
);
}