【问题标题】:How to build a Dynamic Tabbar and Tabview using Flutter and GetX by calling API如何通过调用 API 使用 Flutter 和 GetX 构建动态 Tabbar 和 Tabview
【发布时间】:2021-03-31 18:12:02
【问题描述】:

我是 Flutter 和 GETX 的新手...

我的要求是从 API 获取选项卡名称...而不是根据每个选项卡选择从 API 获取选项卡数据...

我在 tabview here 中找到了 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(),
  ),
);
  }
}

【问题讨论】:

    标签: flutter dart flutter-getx


    【解决方案1】:

    问题

    MyTabbedWidget 正在尝试在 fetchApiData 异步调用完成填充 myTabs 之前从 Controller 使用 myTabs。在 fetch 调用完成之前,myTabs 为空。

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

    由于我们在MyBlockingBindings 中使用了Get.put(MyTabController()),因此我们可以在视图小部件中使用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&lt;MyTabController&gt; 小部件中。当我们在MyTabController 中调用update() 时,我们的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,
                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(),
            ),
          ),
        );
      }
    }
    

    【讨论】:

    • 如何在每个选项卡选择上通过 API 调用提供 TabBarView ???
    • 更新了 TabBar onTap 处理程序,以对选项卡选择进行 API 调用以更新选项卡。
    • 只是好奇..而不是使用GetBuilder..,我如何使用Obx...实现它?
    【解决方案2】:

    这是我的代码,在我的 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);
          },
        );
      }
    

    【讨论】:

    • 控制器是如何定义的??
    • 当 API 调用完成并且我有了初始化控制器的数据时。 _tabController = TabController(length: profile.foodCategories.length, vsync: this)
    猜你喜欢
    • 2022-10-25
    • 2021-11-11
    • 2021-08-29
    • 2021-11-07
    • 2021-10-25
    • 2021-10-12
    • 2022-08-05
    • 2021-06-28
    • 2021-06-17
    相关资源
    最近更新 更多