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

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

排查方向

  1. 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');
      }
    }
    
  2. Dio拦截器影响:主项目中的DioInterceptor可能修改了API返回的数据结构,比如对响应做了额外处理,导致data字段类型变化。可以临时移除拦截器测试,或检查拦截器代码是否有修改响应数据的逻辑。
  3. Token差异:主项目和测试项目使用的token不同,API根据不同用户返回的数据结构不同(比如无权限时返回错误结构,而非空列表)。检查主项目中token对应的用户权限,以及API在该权限下的返回格式。
  4. 环境配置差异:主项目和测试项目的ApiConstants.baseUrl可能指向不同环境(生产/测试),两个环境的API返回结构不一致。确认两个项目的baseUrl是否相同。

内容的提问来源于stack exchange,提问作者Dhiya Rakha Ardiyona

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 04:19:52