Flutter报错:type 'String' is not a subtype of type 'int' of 'index' 求助
Flutter主项目加载认证数据时类型转换错误排查
在Flutter主项目中加载认证数据时抛出错误:Exception: Failed to load sertifikasi: type 'String' is not a subtype of type 'int' of 'index',但将该认证功能单独抽离到新Flutter项目中却能正常运行,已花费3小时排查仍未解决。
页面组件代码
import 'package:flutter/material.dart'; import 'package:mobile_smarcerti/app/modules/sertifikasi/views/detail_sertifikasi_body.dart'; import 'package:mobile_smarcerti/app/modules/sertifikasi/views/detail_sertifikasi_page.dart'; import 'package:mobile_smarcerti/pages/upload_sertifikasi_dosen.dart'; import 'package:mobile_smarcerti/services/api_service.dart'; class SertifikasiBody extends StatefulWidget { const SertifikasiBody({super.key}); @override __SertifikasiScreenState createState() => __SertifikasiScreenState(); } class __SertifikasiScreenState extends State<SertifikasiBody> { late Future<List<dynamic>> _sertifikasiFuture; @override void initState() { super.initState(); _sertifikasiFuture = ApiService().fetchSertifikasi(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, body: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ Row( children: [ Expanded( child: TextField( decoration: InputDecoration( hintText: 'Search', prefixIcon: const Icon(Icons.search), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), fillColor: const Color.fromARGB(145, 255, 249, 249), filled: true, ), ), ), const SizedBox(width: 10), Container( height: 54, width: 48, decoration: BoxDecoration( color: const Color.fromARGB(145, 255, 249, 249), borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.black), ), child: IconButton( icon: const Icon(Icons.filter_list), color: Colors.grey[700], onPressed: () { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text("Filter Options"), content: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( title: const Text("Filter 1"), onTap: () => Navigator.pop(context), ), ListTile( title: const Text("Filter 2"), onTap: () => Navigator.pop(context), ), ListTile( title: const Text("Filter 3"), onTap: () => Navigator.pop(context), ), ], ), ); }, ); }, ), ), ], ), const SizedBox(height: 10), Expanded( child: FutureBuilder<List<dynamic>>( future: _sertifikasiFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } else if (snapshot.hasError) { return Center(child: Text('Error: ${snapshot.error}')); } else if (!snapshot.hasData || snapshot.data!.isEmpty) { return const Center(child: Text('No data available')); } else { final sertifikasis = snapshot.data!; return ListView.builder( itemCount: sertifikasis.length, itemBuilder: (context, index) { final sertifikasi = sertifikasis[index]; return Card( color: Colors.white, margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 10), child: ListTile( leading: const Icon( Icons.library_books, size: 35.0, color: Color.fromARGB(255, 55, 94, 151), ), title: Text( sertifikasi['nama_sertifikasi'], style: const TextStyle( fontFamily: 'Poppins', fontSize: 16, color: Color.fromARGB(255, 55, 94, 151), fontWeight: FontWeight.bold, ), ), onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => DetailSertifikasi( idSertifikasi: sertifikasi['id_sertifikasi'], ), ), ); }, trailing: const Icon( Icons.arrow_forward_ios, size: 20.0, color: Color.fromARGB(255, 55, 94, 151), ), contentPadding: const EdgeInsets.all(20), ), ); }, ); } }, ), ), ], ), ), ); } }
ApiService代码
// lib/services/api_service.dart import 'package:dio/dio.dart'; import 'package:mobile_smarcerti/app/utils/constant.dart'; import 'package:mobile_smarcerti/app/utils/dio_interceptors.dart'; import 'package:shared_preferences/shared_preferences.dart'; class ApiService { late Dio _dio; ApiService() { _dio = Dio( BaseOptions( baseUrl: ApiConstants.baseUrl, connectTimeout: const Duration(seconds: 5), receiveTimeout: const Duration(seconds: 3), validateStatus: (status) { return status! < 500; }, ), ); _dio.interceptors.add(DioInterceptor()); } Future<Response> get( String path, { Map<String, dynamic>? queryParameters, Options? options, }) async { try { final prefs = await SharedPreferences.getInstance(); final token = prefs.getString('auth_token'); final response = await _dio.get( path, queryParameters: queryParameters, options: Options(headers: { 'Authorization': 'Bearer $token', 'Accept': 'application/json', if (options?.headers != null) ...options!.headers!, }), ); return response; } on DioException catch (e) { throw _handleError(e); } } Future<Response> post( String path, { dynamic data, Map<String, dynamic>? queryParameters, Options? options, }) async { try { final prefs = await SharedPreferences.getInstance(); final token = prefs.getString('auth_token'); final response = await _dio.post( path, data: data, queryParameters: queryParameters, options: Options( headers: { 'Authorization': 'Bearer $token', 'Accept': 'application/json', if (options?.headers != null) ...options!.headers!, }, ), ); return response; } on DioException catch (e) { throw _handleError(e); } } Exception _handleError(DioException e) { if (e.type == DioExceptionType.connectionTimeout) { return Exception('Connection timeout'); } if (e.type == DioExceptionType.receiveTimeout) { return Exception('Unable to connect to the server'); } if (e.type == DioExceptionType.unknown) { return Exception('Something went wrong'); } return Exception(e.message); } Future<List<dynamic>> fetchSertifikasi() async { try { final response = await _dio.get('/sertifikasis/'); if (response.data['success']) { return response.data['data']; } else { throw Exception('Failed to load sertifikasi'); } } catch (e) { throw Exception('Failed to load sertifikasi: $e'); } } }
错误信息
虚拟设备错误提示
Error: Exception: Failed to load sertifikasi: type 'String' is not a subtype of type 'int' of 'index'
调试控制台日志
D/EGL_emulation(17071): app_time_stats: avg=426.41ms min=6.15ms max=6485.81ms count=16 W/WindowOnBackDispatcher(17071): OnBackInvokedCallback is not enabled for the application. W/WindowOnBackDispatcher(17071): Set 'android:enableOnBackInvokedCallback="true"' in the application manifest. D/EGL_emulation(17071): app_time_stats: avg=28.53ms min=4.21ms max=433.69ms count=29
排查方向
- API返回数据结构差异:主项目和测试项目中,API返回的
response.data['data']结构可能不同。在主项目中,data可能是一个Map而非List,导致用整数索引访问时出错。可以在fetchSertifikasi中打印response.data['data']的类型和内容:Future<List<dynamic>> fetchSertifikasi() async { try { final response = await _dio.get('/sertifikasis/'); print('Data type: ${response.data['data'].runtimeType}'); print('Data content: ${response.data['data']}'); if (response.data['success']) { // 确保返回的是List,若为Map则处理成List final data = response.data['data']; return data is List ? data : [data]; } else { throw Exception('Failed to load sertifikasi'); } } catch (e) { throw Exception('Failed to load sertifikasi: $e'); } } - Dio拦截器影响:主项目中的
DioInterceptor可能修改了API返回的数据结构,比如对响应做了额外处理,导致data字段类型变化。可以临时移除拦截器测试,或检查拦截器代码是否有修改响应数据的逻辑。 - Token差异:主项目和测试项目使用的token不同,API根据不同用户返回的数据结构不同(比如无权限时返回错误结构,而非空列表)。检查主项目中token对应的用户权限,以及API在该权限下的返回格式。
- 环境配置差异:主项目和测试项目的
ApiConstants.baseUrl可能指向不同环境(生产/测试),两个环境的API返回结构不一致。确认两个项目的baseUrl是否相同。
内容的提问来源于stack exchange,提问作者Dhiya Rakha Ardiyona
相关产品推荐
相关产品推荐

