Flutter发送Post请求写入Google Sheet失败问题排查
问题描述
开发了一个包含TextField和按钮的Flutter应用,预期输入内容后点击按钮将数据写入Google Sheet,但功能无法正常运行。
错误信息
JSON编码错误
Error: Converting object to an encodable object failed: Instance of 'HashSet' at Object.throw [as throw] (localhost:58716/dart_sdk.js:5080:11) at convert._JsonStringStringifier.new.writeObject –
XMLHttpRequest错误
Error: XMLHttpRequest error.
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/_internal/js_dev_runtime/patch/core_patch.dart 963:28 get current
packages/http/src/browser_client.dart 69:22
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/async/zone.dart 1685:54 runUnary
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/async/future_impl.dart 147:18 handleValue
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/async/future_impl.dart 766:44 handleValueCallback
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/async/future_impl.dart 795:13 _propagateToListeners
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/async/future_impl.dart 557:7 [_complete]
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/async/stream_pipe.dart 61:11 _cancelAndValue
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/async/stream.dart 1530:7
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 334:14 _checkAndCall
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 339:39 dcall
C:/b/s/w/ir/cache/builder/src/out/host_debug/dart-sdk/lib/html/dart2js/html_dart2js.dart 37309:58
at Object.createErrorWithStack (http://localhost:65241/dart_sdk.js:5093:12)
at Error._throw (http://localhost:65241/dart_sdk.js:20399:18)
at Error.throwWithStackTrace (http://localhost:65241/dart_sdk.js:20396:18)
at async._AsyncCallbackEntry.new.callback (http://localhost:65241/dart_sdk.js:40921:18)
at Object._microtaskLoop (http://localhost:65241/dart_sdk.js:40778:13)
at _startMicrotaskLoop (http://localhost:65241/dart_sdk.js:40784:13)
at http://localhost:65241/dart_sdk.js:36261:9
相关代码
Flutter代码
import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'dart:convert' as convert; import 'package:http/http.dart' as http; import 'model.dart'; String tax = 'ert'; final wordi = TextEditingController(); String taxi = wordi.text; class BillSplit extends StatefulWidget { // const BillSplit({Key? key}) : super(key: key); @override State<BillSplit> createState() => _BillSplitState(); } class _BillSplitState extends State<BillSplit> { @override Widget build(BuildContext context) { return Scaffold( body: Container( margin: EdgeInsets.only(left: 20, right: 20), child: Column(children: [ Container( alignment: Alignment.centerLeft, margin: EdgeInsets.only(top: 30), child: Text( 'split', style: GoogleFonts.abel(fontSize: 20, fontWeight: FontWeight.w600), ), ), SizedBox(height: 15), Container( width: MediaQuery.of(context).size.width, height: 300, decoration: BoxDecoration(color: Colors.amber), child: Column( children: [ Text( 'total', style: GoogleFonts.abel( fontSize: 20, fontWeight: FontWeight.w600), ), Text( '${tax.toString()}', style: GoogleFonts.abel( fontSize: 20, fontWeight: FontWeight.w600), ), Container( width: MediaQuery.of(context).size.width / 2, child: TextField( keyboardType: TextInputType.number, controller: wordi, onChanged: (value) { setState(() { tax = value; }); }, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(20), ), labelText: "Tax", labelStyle: GoogleFonts.abel( fontSize: 20, fontWeight: FontWeight.w300), ), ), ), RaisedButton( onPressed: () async { // final body = json.encode(tax); final url = Uri.parse( 'https://script.google.com/macros/s/AKfycbyt3ECPqAweBMQFgi96BqTkTzE_HPpFr0hwpGf9pisQ1asTp-g2C5geYfy2XAZUvd2-/exec'); final response = await http.post( url, // headers: {'Content-Type': 'application/json'}, body: json.encode({ taxi, }), ); print('Response status: ${response.statusCode}'); print('Response body: ${response.body}'); }, // }, child: Text('Open'), ), ], ), ) ]), ), ); } } // void sendData() { // String taxi = wordi.text; // }
Google Apps Script代码
function doPost(e) { var ss = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/1z6y1I3b8xm9XLg-W-VhMOn4d8uHSa0Kv6mK_kwVZu7M/edit#gid=0"); var sh = ss.getSheetByName("Sheet1"); sheetApp(e); Logger.log(e.parameter); } function sheetApp(e) { SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1').getRange(4,1).setValue(e); }
修复方案
1. 修复JSON编码问题
原代码中{taxi}会创建HashSet而非标准键值对对象,导致JSON编码失败。同时全局变量taxi只会初始化一次,无法获取最新输入值,需修改按钮点击逻辑:
onPressed: () async { final url = Uri.parse('https://script.google.com/macros/s/AKfycbyt3ECPqAweBMQFgi96BqTkTzE_HPpFr0hwpGf9pisQ1asTp-g2C5geYfy2XAZUvd2-/exec'); // 获取最新输入值,用键值对封装 final body = json.encode({ 'tax': wordi.text, }); final response = await http.post( url, headers: {'Content-Type': 'application/json'}, body: body, ); print('Response status: ${response.statusCode}'); print('Response body: ${response.body}'); },
2. 修正Google Apps Script逻辑
原脚本直接写入整个e对象,无法解析有效数据,需解析JSON请求体并正确写入单元格:
function doPost(e) { var ss = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/1z6y1I3b8xm9XLg-W-VhMOn4d8uHSa0Kv6mK_kwVZu7M/edit#gid=0"); var sh = ss.getSheetByName("Sheet1"); // 解析JSON请求体 var data = JSON.parse(e.postData.contents); sheetApp(sh, data.tax); Logger.log(data); // 返回成功响应,避免前端请求异常 return ContentService.createTextOutput(JSON.stringify({'status': 'success'})).setMimeType(ContentService.MimeType.JSON); } function sheetApp(sheet, taxValue) { // 找到下一个空行写入,而非固定第4行 var nextRow = sheet.getLastRow() + 1; sheet.getRange(nextRow, 1).setValue(taxValue); }
3. 解决XMLHttpRequest错误
确保Google Apps Script已部署为Web应用,且部署设置:
- 执行权限选择「任何人,甚至匿名」
- 部署类型选择「Web应用」,更新版本后重新获取URL替换到Flutter代码中
内容的提问来源于stack exchange,提问作者Tomer Kravitz

