如何通过调用 API 使用 Flutter 和 GetX 构建动态 Tabbar 和 Tabview 阻塞解决方案非阻塞解决方案选项卡开关上的 API 调用

问题描述

我是 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();
  }
}

有时我得到空标签,有时我得到这样的错误......

error screen

这是我的屏幕......

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(),);
  }
}

解决方法

问题

MyTabbedWidget 试图在 myTabs 异步调用完成填充 fetchApiData 之前使用 Controller 中的 myTabsmyTabs 为空,直到 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 将使用最新数据重建自身。

选项卡开关上的 API 调用

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,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',);
          }).toList(),);
  }
}
,

这是我的代码,从我的 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","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","dish_img": "https://i.ibb.co/Zmp3yp5/47125153f54b972670697f49dac933cc.jpg","price": 230,{
                "_id": "5fcc722578070b0098203a11","name": "Chicken tikka ","dish_img": "https://i.ibb.co/M2sLTqP/img-20161210-221320-largejpg.jpg","price": 170,{
                "_id": "5fcc72f478070b0098203a12","name": "Chicken tandoori 1 pcs","dish_img": "https://i.ibb.co/LZw5Fp2/chicken-tandori-1526595014.jpg",{
                "_id": "5fce042d78070b0098203b1b","name": "Special thai soup for 4 person","dish_img": "https://i.ibb.co/YtmVwmm/download.jpg","price": 300,{
                "_id": "5fce048b78070b0098203b1c","name": "Thai clear soup for 4 person","dish_img": "https://i.ibb.co/BjcRvNL/tomyum800-56a9498e5f9b58b7d0f9ea4f.jpg","price": 250,{
                "_id": "5fce04d078070b0098203b1d","name": "Chicken vegetables soup four 4 person","dish_img": "https://i.ibb.co/ZN3Bxnk/chicken-vegetable-soup-9-1200.jpg","price": 180,{
                "_id": "5fce050678070b0098203b1e","name": "Russian salad","dish_img": "https://i.ibb.co/vxh9qGZ/download.jpg","price": 200,{
                "_id": "5fce053378070b0098203b1f","name": "Green salad","dish_img": "https://i.ibb.co/XpwwB8Y/green-salad-1200-1387.jpg","price": 100,{
                "_id": "5fce056878070b0098203b20","name": "French fries","dish_img": "https://i.ibb.co/NCPsK6Y/Copycat-Mc-Donalds-French-Fries-500x500.jpg","price": 60,{
                "_id": "5fce059a78070b0098203b21","name": "Chicken fry  4 pic","dish_img": "https://i.ibb.co/9hwPhgd/download-1.jpg",{
                "_id": "5fce05dc78070b0098203b22","name": "Chicken burger","dish_img": "https://i.ibb.co/HnJH38T/Butchies-2.jpg","price": 80,{
                "_id": "5fce060078070b0098203b23","name": "Chicken pizza ","dish_img": "https://i.ibb.co/WWXzqdk/download.jpg","price": 120,{
                "_id": "5fce062a78070b0098203b24","name": "Chicken naan","dish_img": "https://i.ibb.co/cgLg923/download-1.jpg","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);
      },);
  }

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...