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

如何解决Flutter中对象转可编码对象失败的未处理异常

问题:POST请求序列化失败(Instance of 'TextEditingController')

我需要通过POST方法提交表单数据,设置了学期选填逻辑:选择KHS时显示学期下拉框,未选择则请求体传入空字符串。但点击提交按钮时触发错误:Unhandled Exception: Converting object to an encodable object failed: Instance of 'TextEditingController'。尝试修改后仍有其他错误,以下是我的Widget代码:

class _PengajuanPageState extends State<PengajuanPage> {
  String? selectedValue;
  int? selectedSmt;

  List<String> listItem = [
    'Transkrip Sementara',
    'KHS',
  ];

  List<int> khsListItem = [1, 2, 3, 4, 5, 6, 7, 8];
  final _formKey = GlobalKey<FormState>();

  final _messageController = TextEditingController();

  void submitForm() async {
    String url = Constant.baseURL;
    String token = await UtilSharedPreferences.getToken();
    final response = await http.post(
      Uri.parse(
        '$url/auth/mhs_siakad/pengajuan/pengajuan_cetak_transkrip/store',
      ),
      headers: {
        'Authorization': 'Bearer $token',
        'Content-Type': 'application/json',
      },
      body: jsonEncode(
        {
          'jenis_transkrip': selectedValue,
          'semester': selectedSmt ?? '',
          'message': _messageController,
        },
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: primaryColor,
        title: Center(
          child: Text(
            'Pengajuan Transkrip/KHS',
            style: bold5,
          ),
        ),
        actions: [
          IconButton(
            icon: const Icon(Icons.history),
            onPressed: () {
              Navigator.pushNamed(
                context,
                AppRoute.historypengajuanPage,
              );
            },
          )
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Form(
          key: _formKey,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const SizedBox(
                height: 10,
              ),
              Text(
                'Pilih Jenis Transkrip',
                style: regular5,
              ),
              const SizedBox(
                height: 8,
              ),
              Container(
                width: double.infinity,
                height: 50,
                decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.circular(8),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.grey.withOpacity(0.2),
                      spreadRadius: 1,
                      blurRadius: 9,
                      offset: const Offset(
                        1,
                        2,
                      ),
                    ),
                  ],
                ),
                child: Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: DropdownButtonHideUnderline(
                    child: DropdownButton<String>(
                      hint: const Text('Pilih Jenis Transkrip'),
                      value: selectedValue,
                      onChanged: (newValue) {
                        setState(() {
                          selectedValue = newValue!;
                          selectedSmt = null;
                        });
                      },
                      items: listItem.map((valueItem) {
                        return DropdownMenuItem<String>(
                          value: valueItem,
                          child: Text(
                            valueItem,
                          ),
                        );
                      }).toList(),
                    ),
                  ),
                ),
              ),
              const SizedBox(
                height: 10,
              ),
              Visibility(
                visible: selectedValue == 'KHS',
                child: Container(
                  width: double.infinity,
                  height: 50,
                  decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius: BorderRadius.circular(8),
                    boxShadow: [
                      BoxShadow(
                        color: Colors.grey.withOpacity(0.2),
                        spreadRadius: 1,
                        blurRadius: 9,
                        offset: const Offset(
                          1,
                          2,
                        ),
                      ),
                    ],
                  ),
                  child: Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: DropdownButtonHideUnderline(
                      child: DropdownButton<int>(
                        hint: const Text('Pilih Semester'),
                        value: selectedSmt,
                        onChanged: (newValue) {
                          setState(() {
                            selectedSmt = newValue;
                          });
                        },
                        items: khsListItem.map((value) {
                          return DropdownMenuItem<int>(
                            value: value,
                            child: Text(
                              value.toString(),
                            ),
                          );
                        }).toList(),
                      ),
                    ),
                  ),
                ),
              ),
              RichText(
                text: TextSpan(
                  text: 'Keperluan Cetak ',
                  style: GoogleFonts.poppins(
                    textStyle: const TextStyle(
                      color: Colors.black,
                      fontSize: 14,
                    ),
                  ),
                  children: <TextSpan>[
                    TextSpan(
                      text: '*',
                      style: GoogleFonts.poppins(
                        textStyle: const TextStyle(
                          color: Colors.red,
                          fontSize: 16,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
              const SizedBox(
                height: 8,
              ),
              TextFormField(
                controller: _messageController,
                maxLines: 4,
                decoration: InputDecoration(
                  enabledBorder: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(8),
                    borderSide: const BorderSide(
                      color: Colors.white,
                      width: 2.0,
                    ),
                  ),
                  fillColor: Colors.white,
                  focusedBorder: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(8),
                    borderSide: const BorderSide(
                      color: Colors.blue,
                    ),
                  ),
                  filled: true,
                  hintText: 'Deskripsikan Keperluan',
                  border: const OutlineInputBorder(
                    borderSide: BorderSide(
                      color: Colors.white,
                    ),
                  ),
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Keperluan cetak wajib di isi!';
                  }
                  return null;
                },
              ),
              const SizedBox(
                height: 16,
              ),
              Text(
                '*Silahkan isi form dan simpan untuk mengajukan cetak transkrip',
                style: regular7.copyWith(
                  color: Colors.red,
                  fontStyle: FontStyle.italic,
                ),
              ),
              const SizedBox(
                height: 16,
              ),
              Expanded(
                child: Align(
                  alignment: Alignment.bottomCenter,
                  child: Container(
                    height: 45,
                    width: double.infinity,
                    decoration: BoxDecoration(
                      color: Colors.green[100],
                      borderRadius: BorderRadius.circular(8),
                    ),
                    child: TextButton(
                      style: TextButton.styleFrom(
                        foregroundColor: Colors.green,
                      ),
                      onPressed: () {
                        if (_formKey.currentState!.validate()) {
                          submitForm();
                          ScaffoldMessenger.of(context).showSnackBar(
                            const SnackBar(content: Text('Processing Data')),
                          );
                        }
                      },
                      child: Text(
                        'Simpan',
                        style: bold5,
                      ),
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

错误原因与修复方案

1. TextEditingController直接传入导致序列化失败

你在jsonEncode中直接传入了_messageController对象,而非它的文本内容。JSON无法序列化Flutter控制器对象,需要改为获取控制器的文本值:_messageController.text。

2. 学期字段类型不统一问题

selectedSmt是int?类型,空值时你用?? ''将其转为字符串,会导致该字段类型时而为int、时而为String,后端大概率无法正常解析。建议统一类型:

  • 若后端接受字符串格式的学期,将selectedSmt转为字符串后赋值,空值传空字符串:selectedSmt?.toString() ?? ''
  • 若后端接受数字格式,空值可传null(需确认后端是否允许),或按后端要求处理。

修复后的submitForm方法

void submitForm() async {
  String url = Constant.baseURL;
  String token = await UtilSharedPreferences.getToken();
  final response = await http.post(
    Uri.parse(
      '$url/auth/mhs_siakad/pengajuan/pengajuan_cetak_transkrip/store',
    ),
    headers: {
      'Authorization': 'Bearer $token',
      'Content-Type': 'application/json',
    },
    body: jsonEncode(
      {
        'jenis_transkrip': selectedValue,
        'semester': selectedSmt?.toString() ?? '', // 统一转为字符串,空值传空串
        'message': _messageController.text, // 获取输入框文本内容
      },
    ),
  );
  // 新增响应状态处理
  if (response.statusCode == 200) {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('提交成功')),
    );
  } else {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('提交失败,请重试')),
    );
  }
}

额外优化建议

  • 在Widget销毁时释放TextEditingController,避免内存泄漏:
@override
void dispose() {
  _messageController.dispose();
  super.dispose();
}
  • 添加加载状态变量(如bool isLoading = false),请求期间禁用提交按钮,防止重复点击。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 09:08:09