1

我是 Flutter 和 GETX 的新手……

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

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

2 回答 2

1

问题

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

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

其余部分与您遵循的示例相同

非阻塞解决方案

此解决方案立即加载占位符选项卡数据,然后在它们到达后将占位符数据与从 API 调用加载的选项卡交换。

(假的)2秒。API 调用asyncLoadTabs()MyTabController onInit(). 请注意,我们不是await在此处使用,onInit也不是在制作async。我们不想阻止处理。当 Flutter 的事件循环处理它时,异步调用将运行。

MyTabbedWidget我们将所有内容包装在一个GetBuilder<MyTabController>小部件中。当我们调用GetBuilder 时,将使用最新数据重建自身update()MyTabController

选项卡开关上的 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(),
        ),
      ),
    );
  }
}
于 2020-12-22T23:22:19.917 回答
-1

这是我的代码,从我的 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": []
              }
            ]
          }
        ]
      }
    }
  }
}

选项卡

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);
      },
    );
  }
于 2020-12-22T06:28:30.067 回答