Flutter中API数据填充Dropdown列表为空问题求助
问题分析与解决方案
核心问题
- 异步请求未触发UI更新:
_getProductList是异步函数,initState里调用后直接print(allProducts),此时请求还未完成,打印结果必然为空;且数据获取完成后未调用setState通知Flutter更新UI,导致Dropdown无法渲染数据。 - 构造函数写法不规范:自定义State构造函数传递参数的方式不符合Flutter最佳实践,应通过
widget访问StatefulWidget的参数。 - 冗余API调用:Dropdown的
onChanged里重复调用_getProductList完全没必要,反而会重复发起无效请求。 - 初始值可能不匹配:如果
portProduct和API返回的productId.toString()格式不一致,Dropdown会因找不到对应值报错。
修正后的完整代码
import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:gateway_device/flutter_flow/flutter_flow_theme.dart'; import 'package:gateway_device/models/products_list_model.dart'; import 'package:gateway_device/routes/route_helper.dart'; import 'package:gateway_device/utils/app_constants.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; class EditDevicePortWidget extends StatefulWidget { final String deviceId; final String portId; final String publicId; final String label; final String portProduct; const EditDevicePortWidget({ Key? key, required this.deviceId, required this.portId, required this.publicId, required this.label, required this.portProduct, }) : super(key: key); @override State<EditDevicePortWidget> createState() => _EditDevicePortWidgetState(); } class _EditDevicePortWidgetState extends State<EditDevicePortWidget> { late String _selectedProduct; List<TankProduct> allProducts = []; bool _isLoading = true; // 增加加载状态优化体验 @override void initState() { super.initState(); _selectedProduct = widget.portProduct; _fetchProductList(); } // 异步获取产品列表并更新UI Future<void> _fetchProductList() async { try { final allProductsUrl = Uri.parse(AppConstants.BASE_URL + AppConstants.TANK_PRODUCTS_LIST_URI); final response = await http.get(allProductsUrl, headers: { 'Authorization': 'Bearer ' + AppConstants.TOKEN, 'X-API-KEY': AppConstants.API_KEY, }); if (response.statusCode == 200) { Map<String, dynamic> body = json.decode(response.body); List<TankProduct> fetchedProducts = []; (body["tank_products"]).forEach((product) { fetchedProducts.add(TankProduct.fromJson(product)); }); setState(() { allProducts = fetchedProducts; _isLoading = false; // 校验初始值是否在列表中,避免Dropdown报错 if (!allProducts.any((item) => item.productId.toString() == _selectedProduct)) { _selectedProduct = allProducts.isNotEmpty ? allProducts.first.productId.toString() : ''; } }); } else { setState(() => _isLoading = false); Get.snackbar('Error', 'Failed to load products'); } } catch (e) { setState(() => _isLoading = false); Get.snackbar('Error', e.toString()); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar( title: Text( '${widget.publicId} - ${widget.label}', style: FlutterFlowTheme.of(context).bodyText1.override( fontFamily: 'Heebo', fontSize: 18, fontWeight: FontWeight.w500, ), ), centerTitle: true, elevation: 0, backgroundColor: Colors.white, automaticallyImplyLeading: false, actions: [ IconButton( hoverColor: Colors.transparent, iconSize: 40, icon: const Icon( Icons.close, color: Colors.black, size: 30, ), onPressed: () { Get.offNamed(RouteHelper.getPortProfile( widget.deviceId, widget.portId, widget.publicId, widget.label, )); }, ), ], iconTheme: const IconThemeData(color: Colors.black), ), body: SafeArea( child: Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height.h * 1, decoration: const BoxDecoration(color: Colors.white), child: Column( children: [ const Divider( height: 10, thickness: 2, color: Colors.black, ), Column( children: [ Row( mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.center, children: [ Padding( padding: const EdgeInsetsDirectional.fromSTEB(10, 10, 0, 0), child: Text( 'Product:', style: FlutterFlowTheme.of(context).bodyText1.override( fontFamily: 'Heebo', fontSize: 18, fontWeight: FontWeight.w500, ), ), ), ], ), // 根据加载状态切换显示内容 _isLoading ? const Center(child: CircularProgressIndicator()) : Column( mainAxisSize: MainAxisSize.max, children: <Widget>[ SizedBox( child: ButtonTheme( alignedDropdown: true, child: DropdownButton<String>( value: allProducts.isNotEmpty ? _selectedProduct : null, iconSize: 30, icon: null, style: TextStyle(color: Colors.black, fontSize: 16.sp), hint: const Text('Select Product'), onChanged: allProducts.isNotEmpty ? (newValue) { setState(() { _selectedProduct = newValue!; }); print(_selectedProduct); } : null, // 无数据时禁用选择 items: allProducts.map((item) { return DropdownMenuItem( child: Text(item.productName), value: item.productId.toString(), ); }).toList(), ), ), ) ], ), ], ) ], ), ), ), ); } }
关键修改点说明
- 规范构造函数:移除State的自定义构造函数,通过
widget.xxx访问父组件参数,符合Flutter官方规范。 - 异步请求与UI更新:在
_fetchProductList中获取数据后调用setState,更新产品列表和加载状态,触发UI重建。 - 增加加载状态:添加
_isLoading变量,请求过程中显示加载指示器,避免页面空白。 - 校验初始值:确保初始选中值存在于返回列表中,避免Dropdown出现"Value not in items"错误。
- 移除冗余调用:删除
onChanged里的API请求,选择产品时仅更新选中值即可。 - 错误处理:添加try-catch和状态码判断,通过GetX显示错误提示,提升容错性。
内容的提问来源于stack exchange,提问作者Mackina
相关产品推荐
相关产品推荐

