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

Flutter中initState调用双API致UI卡顿,求数据优化与本地存储方案

解决Flutter initState中API调用导致的UI卡顿与数据复用问题

问题描述

在Flutter的initState中调用两个获取后端数据的API时,UI出现卡顿,如何解决该卡顿问题?同时希望缩短接口数据获取耗时,且需在多页面复用这些数据,如何实现本地存储以避免重复调用API?

代码示例

@override
void onInit() {
  getCustomerList();
  getProductList();
  super.onInit();
}

Future<void> getCustomerList() async {
  List<CustomerModel> newItems = (await BaseClient.safeApiCall(
    ApiConstants.GET_CUSTOMER_LIST,
    RequestType.get,
    headers: await BaseClient.generateHeaders(),
    queryParameters: {
      '\$count': true,
    },
    onSuccess: (json) {
      List<CustomerModel>? customers;
      if (json.data['value'] != null) {
        customers = [];
        json.data['value'].forEach((v) {
          customers?.add(CustomerModel.fromJson(v));
        });
      }
      return customers;
    },
    onError: (e) {},
  ));

  for (CustomerModel customer in newItems) {
    if (customer.companyName != null) {
      customerFilters.add(customer.customerName!);
    }
  }
}

Future<void> getProductList() async {
  List<ProductModel> newItems = (await BaseClient.safeApiCall(
    ApiConstants.GET_PRODUCT_LIST,
    RequestType.get,
    headers: await BaseClient.generateHeaders(),
    queryParameters: {
      '\$count': true,
    },
    onSuccess: (json) {
      List<ProductModel>? products;
      if (json.data['value'] != null) {
        products = [];
        json.data['value'].forEach((v) {
          products?.add(ProductModel.fromJson(v));
        });
      }
      return products;
    },
    onError: (e) {},
  ));

  for (ProductModel products in newItems) {
    if (products.productName != null) {
      productFilters.add(products.productName!);
    }
  }
}

一、解决UI卡顿问题

1. 并行发起API请求,减少总等待时间

显式用Future.wait管理并行任务,确保两个API同时发起,避免串行等待:

@override
void onInit() {
  super.onInit();
  // 并行执行两个API请求
  Future.wait([
    getCustomerList(),
    getProductList(),
  ]);
}

2. 把数据解析、过滤移到后台线程

数据量较大时,fromJson转换和循环过滤会阻塞UI线程,用compute函数将这些计算任务移到后台isolate:

第一步:抽离计算逻辑为独立函数

// 客户列表过滤逻辑
List<String> filterCustomerNames(List<CustomerModel> customers) {
  return customers
      .where((customer) => customer.companyName != null)
      .map((customer) => customer.customerName!)
      .toList();
}

// 客户列表JSON解析逻辑
List<CustomerModel> parseCustomerList(dynamic jsonData) {
  if (jsonData['value'] == null) return [];
  return (jsonData['value'] as List)
      .map((item) => CustomerModel.fromJson(item))
      .toList();
}

// 产品列表同理
List<String> filterProductNames(List<ProductModel> products) {
  return products
      .where((product) => product.productName != null)
      .map((product) => product.productName!)
      .toList();
}

List<ProductModel> parseProductList(dynamic jsonData) {
  if (jsonData['value'] == null) return [];
  return (jsonData['value'] as List)
      .map((item) => ProductModel.fromJson(item))
      .toList();
}

第二步:在API请求中使用compute执行后台任务

Future<void> getCustomerList() async {
  List<CustomerModel> newItems = (await BaseClient.safeApiCall(
    ApiConstants.GET_CUSTOMER_LIST,
    RequestType.get,
    headers: await BaseClient.generateHeaders(),
    queryParameters: {
      '\$count': true,
    },
    onSuccess: (json) async {
      // JSON解析移到后台线程
      return await compute(parseCustomerList, json.data);
    },
    onError: (e) {},
  ));

  // 过滤操作移到后台线程
  final filteredNames = await compute(filterCustomerNames, newItems);
  // 回到主线程更新UI数据
  customerFilters.addAll(filteredNames);
}

// getProductList做同样修改
Future<void> getProductList() async {
  List<ProductModel> newItems = (await BaseClient.safeApiCall(
    ApiConstants.GET_PRODUCT_LIST,
    RequestType.get,
    headers: await BaseClient.generateHeaders(),
    queryParameters: {
      '\$count': true,
    },
    onSuccess: (json) async {
      return await compute(parseProductList, json.data);
    },
    onError: (e) {},
  ));

  final filteredNames = await compute(filterProductNames, newItems);
  productFilters.addAll(filteredNames);
}

二、实现本地存储复用数据,避免重复调用API

用Hive做本地存储(性能优于SharedPreferences,支持直接存储对象),结合状态管理实现多页面数据复用:

1. 配置Hive依赖

在pubspec.yaml中添加:

dependencies:
  hive: ^2.2.3
  hive_flutter: ^1.1.0

dev_dependencies:
  build_runner: ^2.4.4
  hive_generator: ^1.1.5

2. 为模型类生成Hive适配器

给模型类添加注解,生成存储适配器:

import 'package:hive/hive.dart';

part 'customer_model.g.dart';

@HiveType(typeId: 0)
class CustomerModel {
  @HiveField(0)
  final String? customerName;
  @HiveField(1)
  final String? companyName;

  CustomerModel({this.customerName, this.companyName});

  factory CustomerModel.fromJson(Map<String, dynamic> json) {
    return CustomerModel(
      customerName: json['customerName'],
      companyName: json['companyName'],
    );
  }
}

// ProductModel同理,typeId设为1

运行命令生成适配器:

flutter pub run build_runner build

3. 初始化Hive

在main函数中初始化并注册适配器:

import 'package:hive_flutter/hive_flutter.dart';

void main() async {
  await Hive.initFlutter();
  Hive.registerAdapter(CustomerModelAdapter());
  Hive.registerAdapter(ProductModelAdapter());
  runApp(MyApp());
}

4. 封装缓存+网络请求逻辑

优先读取本地缓存,缓存过期或无缓存时再发起网络请求,同时更新缓存:

Future<void> getCustomerList() async {
  final customerBox = await Hive.openBox<CustomerModel>('customers');
  final cacheTimeBox = await Hive.openBox('cache_timestamps');
  
  // 缓存有效期24小时
  const cacheExpiry = 24 * 60 * 60 * 1000;
  final lastFetchTime = cacheTimeBox.get('customer_list') ?? 0;
  final isCacheValid = DateTime.now().millisecondsSinceEpoch - lastFetchTime < cacheExpiry;

  // 先使用有效缓存更新UI
  if (customerBox.isNotEmpty && isCacheValid) {
    final cachedCustomers = customerBox.values.toList();
    final filteredNames = await compute(filterCustomerNames, cachedCustomers);
    customerFilters.addAll(filteredNames);
  }

  // 发起网络请求获取最新数据
  try {
    List<CustomerModel> newItems = (await BaseClient.safeApiCall(
      ApiConstants.GET_CUSTOMER_LIST,
      RequestType.get,
      headers: await BaseClient.generateHeaders(),
      queryParameters: {
        '\$count': true,
      },
      onSuccess: (json) async {
        return await compute(parseCustomerList, json.data);
      },
      onError: (e) {},
    ));

    // 更新缓存和缓存时间
    await customerBox.clear();
    await customerBox.addAll(newItems);
    await cacheTimeBox.put('customer_list', DateTime.now().millisecondsSinceEpoch);

    // 用最新数据更新UI
    customerFilters.clear();
    final filteredNames = await compute(filterCustomerNames, newItems);
    customerFilters.addAll(filteredNames);
  } catch (e) {
    // 无缓存且请求失败时处理错误(比如显示空状态)
    if (customerBox.isEmpty) {
      // 错误处理逻辑
    }
  }
}

// getProductList做同样修改

5. 多页面数据复用

用Riverpod/Provider封装数据逻辑,多个页面直接监听状态即可:
以Riverpod为例:

final customerListProvider = FutureProvider<List<CustomerModel>>((ref) async {
  final customerBox = await Hive.openBox<CustomerModel>('customers');
  final cacheTimeBox = await Hive.openBox('cache_timestamps');
  const cacheExpiry = 24 * 60 * 60 * 1000;
  final lastFetchTime = cacheTimeBox.get('customer_list') ?? 0;
  final isCacheValid = DateTime.now().millisecondsSinceEpoch - lastFetchTime < cacheExpiry;

  if (customerBox.isNotEmpty && isCacheValid) {
    return customerBox.values.toList();
  }

  final newItems = await BaseClient.safeApiCall(
    ApiConstants.GET_CUSTOMER_LIST,
    RequestType.get,
    headers: await BaseClient.generateHeaders(),
    queryParameters: {
      '\$count': true,
    },
    onSuccess: (json) async {
      return await compute(parseCustomerList, json.data);
    },
    onError: (e) {},
  );

  await customerBox.clear();
  await customerBox.addAll(newItems);
  await cacheTimeBox.put('customer_list', DateTime.now().millisecondsSinceEpoch);
  return newItems;
});

页面中使用:

final customerListAsync = ref.watch(customerListProvider);
customerListAsync.when(
  loading: () => const CircularProgressIndicator(),
  error: (e, _) => const Text('加载失败'),
  data: (customers) {
    final filteredNames = customers.where((c) => c.companyName != null).map((c) => c.customerName!).toList();
    customerFilters.addAll(filteredNames);
    return // 你的UI组件;
  },
);

内容的提问来源于stack exchange,提问作者Ahmed Raza

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 21:55:57