You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter首页多模块API数据加载异常问题咨询

Flutter首页API加载与UI渲染问题

我的Flutter首页包含三个模块:

  • 分类列表视图(categories list view)
  • 热门美食列表视图(popular food list view)
  • 新品美食列表视图(new food list view)

我希望首页加载时一次性获取所有API数据,已经为分类和热门美食模块写了数据获取方法,但现在出现问题:在onInit中调用两个接口方法后,只有一个模块能加载数据;修改代码保存热重载后,另一个模块才会显示数据。请问该如何正确调用API并让UI正常渲染?


现有代码

Home Controller

import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter_admin/consts/consts.dart';
import '../api_service/api_endpoints.dart';
import '../model/category_model.dart';
import '../model/popular_recipe_model.dart';

class HomeController extends GetxController {


  var currentNavIndex = 0.obs;
  var isLoading = false.obs;

  //category list variable
  List categoryList = [].obs;

  //popular recipe list variable
  List popularRecipeList = [].obs;

  @override
  void onInit() {
    fetchCategory();
    fetchPopularRecipe();
    super.onInit();
  }

  //change navigation tab index
  changeIndex(index) {
    currentNavIndex.value = index;
  }

// fetch category
  Future<List?> fetchCategory() async {
    var client = http.Client();
    isLoading.value = true;
    try {
      var response = await client.get(Uri.parse(
          ApiEndPoints.baseUrl + ApiEndPoints.authEndPoints.fetchCat));
      if (response.statusCode == 200) {
        var jsonString = jsonDecode(response.body);
        var data = jsonString['data'];
        categoryList =
            List.from(data).map((e) => Categories.fromJson(e)).toList();
        isLoading.value = false;
        return categoryList;
      } else {
        Get.snackbar("Error", "data not found");
        return null;
      }
    } finally {
      client.close();
    }
  }
 

//  fetch popular recipe
  Future<List?> fetchPopularRecipe() async {
    var client = http.Client();
    isLoading.value = true;
    try {
      var response = await client.get(Uri.parse(
          ApiEndPoints.baseUrl + ApiEndPoints.authEndPoints.popularRecipe));
      if (response.statusCode == 200) {
        var jsonString = jsonDecode(response.body);
        var data = jsonString['data'];
        popularRecipeList =
            List.from(data).map((e) => PopularRecipe.fromJson(e)).toList();
        isLoading.value = false;
        return popularRecipeList;
      } else {
        Get.snackbar("Error", "data not found");
        return null;
      }
    } finally {
      client.close();
    }
  }
}

Home Screen(热门美食模块片段)

Obx(()=>SizedBox(
    height: Dimensions.height300,
    child: controller.isLoading.value ? const Center(child: CircularProgressIndicator()) : ListView.builder(
        physics: const BouncingScrollPhysics(),
        scrollDirection: Axis.horizontal,
        itemCount: controller.popularRecipeList.length,
        itemBuilder: (context, index) {
          return popularRecipe(
              title: "${controller.popularRecipeList[index].name}",
              time: "${controller.popularRecipeList[index].recipeTime}",
              icon: const Icon(Icons.bookmark_outline_sharp),
              img: "${controller.popularRecipeList[index].imageUrl}");
        }),
  ),
),

问题原因与修复方案

核心问题

  1. 加载状态冲突:两个API请求共用同一个isLoading变量,先完成的请求会直接把isLoading设为false,导致未完成请求的模块提前渲染空数据。
  2. 响应式失效:直接用=赋值覆盖Observable列表,破坏了GetX的响应式绑定,导致数据更新后UI不刷新。

修复后的代码

Home Controller

import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter_admin/consts/consts.dart';
import '../api_service/api_endpoints.dart';
import '../model/category_model.dart';
import '../model/popular_recipe_model.dart';

class HomeController extends GetxController {
  var currentNavIndex = 0.obs;
  // 为每个模块单独设置加载状态
  var isCategoryLoading = false.obs;
  var isPopularLoading = false.obs;

  // 明确泛型类型,保留Observable列表引用
  final categoryList = <Categories>[].obs;
  final popularRecipeList = <PopularRecipe>[].obs;

  @override
  void onInit() {
    // 同时发起两个请求,提升加载效率
    Future.wait([
      fetchCategory(),
      fetchPopularRecipe(),
    ]);
    super.onInit();
  }

  void changeIndex(index) {
    currentNavIndex.value = index;
  }

  Future<List<Categories>?> fetchCategory() async {
    var client = http.Client();
    isCategoryLoading.value = true;
    try {
      var response = await client.get(Uri.parse(
          ApiEndPoints.baseUrl + ApiEndPoints.authEndPoints.fetchCat));
      if (response.statusCode == 200) {
        var jsonString = jsonDecode(response.body);
        var data = jsonString['data'];
        // 使用assignAll更新列表,保留响应式
        categoryList.assignAll(
          List.from(data).map((e) => Categories.fromJson(e)).toList(),
        );
        isCategoryLoading.value = false;
        return categoryList;
      } else {
        Get.snackbar("Error", "分类数据获取失败");
        isCategoryLoading.value = false;
        return null;
      }
    } catch (e) {
      Get.snackbar("Error", "请求出错: $e");
      isCategoryLoading.value = false;
      return null;
    } finally {
      client.close();
    }
  }

  Future<List<PopularRecipe>?> fetchPopularRecipe() async {
    var client = http.Client();
    isPopularLoading.value = true;
    try {
      var response = await client.get(Uri.parse(
          ApiEndPoints.baseUrl + ApiEndPoints.authEndPoints.popularRecipe));
      if (response.statusCode == 200) {
        var jsonString = jsonDecode(response.body);
        var data = jsonString['data'];
        popularRecipeList.assignAll(
          List.from(data).map((e) => PopularRecipe.fromJson(e)).toList(),
        );
        isPopularLoading.value = false;
        return popularRecipeList;
      } else {
        Get.snackbar("Error", "热门美食数据获取失败");
        isPopularLoading.value = false;
        return null;
      }
    } catch (e) {
      Get.snackbar("Error", "请求出错: $e");
      isPopularLoading.value = false;
      return null;
    } finally {
      client.close();
    }
  }
}

Home Screen(热门美食模块)

Obx(()=>SizedBox(
    height: Dimensions.height300,
    child: controller.isPopularLoading.value 
        ? const Center(child: CircularProgressIndicator()) 
        : ListView.builder(
            physics: const BouncingScrollPhysics(),
            scrollDirection: Axis.horizontal,
            itemCount: controller.popularRecipeList.length,
            itemBuilder: (context, index) {
              return popularRecipe(
                  title: "${controller.popularRecipeList[index].name}",
                  time: "${controller.popularRecipeList[index].recipeTime}",
                  icon: const Icon(Icons.bookmark_outline_sharp),
                  img: "${controller.popularRecipeList[index].imageUrl}");
            }),
  ),
),

额外优化点

  • 分类模块需同步修改为使用isCategoryLoading控制加载状态
  • 添加异常捕获,避免请求出错时加载状态无法结束
  • 明确列表泛型类型,提升代码可读性与类型安全性

内容的提问来源于stack exchange,提问作者MS Droid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 22:25:30