Flutter异步方法数据获取问题:如何在.then外部使用返回值
问题解决:Flutter异步数据无法在外部使用的问题
问题根源
你这问题核心是异步操作的执行顺序:getAllPlayerData()是异步方法,调用后不会阻塞代码流程,initState里调用它之后,直接就进入build方法了——这时候theStats还没被赋值,所以bad print先输出null。等SharedPreferences取ID、数据库查询这些异步操作全部完成后,才会执行.then里的代码给theStats赋值,这时候good print才会输出。
解决方案1:用FutureBuilder(最推荐,符合Flutter生命周期)
这是Flutter处理异步数据渲染的标准方式,不用手动管理状态,自动处理加载、完成、错误三种状态:
import 'package:cleanmaybe/Controllers/players_controller.dart'; import 'package:flutter/material.dart'; class PlayerStatView extends StatefulWidget { const PlayerStatView({Key? key}) : super(key: key); @override State<PlayerStatView> createState() => _PlayerStatViewState(); } class _PlayerStatViewState extends State<PlayerStatView> { // 提前初始化异步请求的Future,避免每次build重复发起请求 late Future<List<Map<String, dynamic>>> _statsFuture; @override void initState() { super.initState(); _statsFuture = getAllPlayerData(); } @override Widget build(BuildContext context) { return FutureBuilder( future: _statsFuture, builder: (context, snapshot) { // 加载中:显示加载指示器 if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } // 加载出错:显示错误信息 else if (snapshot.hasError) { return Center(child: Text('加载失败:${snapshot.error}')); } // 加载完成:使用数据渲染UI else { final theStats = snapshot.data!; print("数据已加载:$theStats"); // 替换成你实际需要的UI组件,比如列表展示数据 return Container( padding: const EdgeInsets.all(16), child: Text('玩家数据:$theStats'), ); } }, ); } }
解决方案2:手动管理状态(async/await + setState)
如果不想用FutureBuilder,可以单独写异步方法加载数据,拿到数据后调用setState触发UI更新:
import 'package:cleanmaybe/Controllers/players_controller.dart'; import 'package:flutter/material.dart'; class PlayerStatView extends StatefulWidget { const PlayerStatView({Key? key}) : super(key: key); @override State<PlayerStatView> createState() => _PlayerStatViewState(); } class _PlayerStatViewState extends State<PlayerStatView> { List<Map<String, dynamic>>? theStats; bool _isLoading = true; // 标记是否处于加载状态 @override void initState() { super.initState(); _loadPlayerData(); } // 单独定义异步方法,因为initState不能直接加async修饰 Future<void> _loadPlayerData() async { try { final data = await getAllPlayerData(); setState(() { theStats = data; _isLoading = false; }); print("Good print $theStats"); } catch (e) { print("加载出错:$e"); setState(() => _isLoading = false); } } @override Widget build(BuildContext context) { if (_isLoading) { return const Center(child: CircularProgressIndicator()); } print("当前数据:$theStats"); return Container( child: Text('玩家数据:$theStats'), ); } }
额外优化:给异步方法指定返回类型(避免dynamic)
原代码中方法返回dynamic容易引发类型问题,建议明确指定返回类型:
修改getPlayerData方法:
// rawQuery返回的是List<Map<String, dynamic>>,直接指定该类型 Future<List<Map<String, dynamic>>> getPlayerData(int? idPlayer) async { var dbCoach = await db; // 用占位符?代替字符串拼接,避免SQL注入风险 var dataPlayer = await dbCoach!.rawQuery( "SELECT * FROM players WHERE idPlayer = ?", [idPlayer], ); return dataPlayer; }
修改getAllPlayerData方法:
Future<List<Map<String, dynamic>>> getAllPlayerData() async { SharedPreferences sp = await _pref; int? id = sp.getInt('idPlayer'); final statAccess = DatabaseModel(); return await statAccess.getPlayerData(id); }
重要提醒
- 不要在
build方法里直接调用异步方法,否则每次UI刷新都会重复发起请求 - 使用FutureBuilder时,务必把
Future放在initState里初始化,不能直接写在future参数中,避免重复请求 - 数据库查询禁止直接拼接字符串,用占位符
?防止SQL注入风险
内容的提问来源于stack exchange,提问作者zsupraz
相关产品推荐
相关产品推荐

