Flutter 如何通过调用API,使用flatter和GetX构建动态Tabbar和Tabview
我是一个新的颤栗和GETX 我的要求是从API获取选项卡名称。。。然后根据每个选项卡选择从API获取选项卡数据 我在tabview中发现了getx的用法,但我不知道如何使其与API响应一起工作。我试图通过在我的Flutter 如何通过调用API,使用flatter和GetX构建动态Tabbar和Tabview,flutter,dart,getx,Flutter,Dart,Getx,我是一个新的颤栗和GETX 我的要求是从API获取选项卡名称。。。然后根据每个选项卡选择从API获取选项卡数据 我在tabview中发现了getx的用法,但我不知道如何使其与API响应一起工作。我试图通过在我的controller类中的onInit方法中添加api调用来实现这一点,但没有成功 这是我的控制器…… class MyTabController extends GetxController with SingleGetTickerProviderMixin { var isLoad
controller
类中的onInit
方法中添加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(),
),
);
}
}
这是我的代码,从我的API中,我将所有类别和食物嵌套在类别中。 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": []
}
]
}
]
}
}
}
}
标签
身体
项目列表
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);
},
);
}
Widget itemList(列表项,字符串id){
返回ListView.builder(
主要:错误,
itemCount:items.length±0,
填充:EdgeInsets.zero,
收缩膜:对,
物理:AlwaysScrollableScrollPhysics(),
itemBuilder:(上下文,索引){
RestaurantFoods项目=项目[索引];
退货项目卡(项目、身份证);
},
);
}
问题
MyTabbedWidget
在fetchApiData
异步调用完成填充myTabs
之前,正在尝试从控制器使用myTabs
<代码>myTabs在提取调用完成之前为空
TabBarView
将尝试访问myTabs
,在API调用完成之前,该选项卡的长度为零。颤振控制器长度不能为零,否则它将抛出一个错误,我想你们已经看到了
解决
两种解决方案:阻塞和非阻塞
封闭溶液
一种解决方案是在应用程序启动之前进行fetchApiData
异步调用。在绑定类中完成。这将延迟页面加载,直到调用完成。如果可以,这将是一个潜在的解决方案:
// 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();
}
}
其余部分与之前相同
非封闭溶液
此解决方案立即加载占位符选项卡数据,然后在占位符数据到达后,将占位符数据与从API调用加载的选项卡交换
(假)2秒。API调用asyncLoadTabs()
在MyTabController
onInit()
中完成。请注意,我们在这里没有使用wait
,并且onInit
没有设置为async
。我们不想阻止处理。当flatter的事件循环到达异步调用时,异步调用将运行
在MyTabbedWidget
中,我们将所有内容包装在GetBuilder
小部件中。当我们在MyTabController
中调用update()
时,我们的GetBuilder将使用最新数据重建自身
选项卡开关上的API调用
TabBar onTap:
调用控制器的switchTab(index)
,后者依次调用asynchloadtabs
,并选择选项卡的索引,使用选项卡#进行另一个API调用
void main()异步{
runApp(MyApp());
}
类MyTabController使用SingleGetTickerProviderMixin扩展GetxController{
列表myTabs=[
选项卡(文本:“正在加载…”),
];
// ↓ 构造函数现在可以接受myTabs参数
MyTabController({myTabs}){
this.myTabs???=myTabs;
}
tab控制器;
@凌驾
void onInit(){
super.onInit();
controller=TabController(vsync:this,length:myTabs.length);
asyncLoadTabs();
}
//假2秒异步调用
void asyncLoadTabs({int index=0})async{
等待未来。延迟(持续时间(秒:2),(){
myTabs=[
选项卡(文本:“左$index”),
选项卡(文本:“右$index”),
];
controller.dispose();//释放动画资源
//重新创建TabController,因为长度为最终长度/无法更改↓
控制器=选项卡控制器(
vsync:这个,,
长度:myTabs.length,
initialIndex:index//显示创建时的特定选项卡
);
更新();
// ↑ 使用最新控制器数据重建GetBuilder小部件
});
}
void switchTab(int索引)异步{
asyncLoadTabs(索引:index);
}
@凌驾
void onClose(){
controller.dispose();
super.onClose();
}
}
类MyTabbedWidget扩展了无状态Widget{
@凌驾
小部件构建(构建上下文){
// ↓ 使用GetBuilder&使用update()重建
返回GetBuilder(
init:MyTabController(),
建筑商:(\u tabx)=>脚手架(
appBar:appBar(
底部:选项卡栏(
控制器:\ tabx.controller,
选项卡:\ u tabx.myTabs,
onTap:_tabx.switchTab,//接收选项卡#在选项卡上单击
),
),
正文:选项卡视图(
控制器:\ tabx.controller,
子项:_tabx.myTabs.map((选项卡){
最终字符串标签=tab.text.toLowerCase();
返回中心(
子:文本(
'这是$label选项卡',
样式:常量文本样式(字体大小:36),
),
);
}).toList(),
),
),
);
}
}
控制器是如何定义的??当API调用完成并且我有数据时,我初始化控制器_tabController=tabController(长度:profile.foodCategories.length,vsync:this)如何从每个选项卡选择上的API调用为TabBarView提供数据???使用TabBar onTap处理程序进行更新,以对选项卡选择进行API调用以更新选项卡。感谢您的帮助。。。成功了!!!只是好奇。。。。如何使用Obx…,而不是使用GetBuilder。。。?
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);
},
);
}
// 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();
}
}
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(),
),
);
}
}
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(),
),
),
);
}
}