Flutter 如何通过调用API,使用flatter和GetX构建动态Tabbar和Tabview

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

我是一个新的颤栗和GETX

我的要求是从API获取选项卡名称。。。然后根据每个选项卡选择从API获取选项卡数据

我在tabview中发现了getx的用法,但我不知道如何使其与API响应一起工作。我试图通过在我的
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(),
        ),
      ),
    );
  }
}