使用Flutter Dio获取数组列表存入SQLite时遇400客户端错误求助
Flutter Dio 请求返回400客户端错误排查与修复
问题描述
我是Dart和Flutter纯新手,这是我的第一个相关应用,也是首次提问。正在开发一款离线优先应用,尝试用Flutter Dio获取指定数组数据并存储到SQLite中,但出现400客户端错误。以下是接口返回示例、代码及报错信息:
接口返回数组示例
[ { "serverId": 4, "documentJobId": 15313, "serviceOn": "Jerry Brown ", "deliveryTo": "Jerry Brown ", "street1": "jcincsccncnncnc ", "street2": null, "aptSuite": null, "city": "nwcwncwcn ", "state": "MD ", "zip": "99999 ", "returnDate": "2023-12-30T00:00:00" }, { "serverId": 4, "documentJobId": 15313, "serviceOn": "David Brown ", "deliveryTo": "David Brown ", "street1": "ncjcjcncncjncn ", "street2": null, "aptSuite": null, "city": "Ncncnscn ", "state": "PA ", "zip": "99998 ", "returnDate": "2023-12-30T00:00:00" } ]
实现代码
location_model.dart
import 'dart:convert'; List<Location> FromJson(String str) => List<Location>.from(json.decode(str).map((x) => Location.fromJson(x))); String locationToJson(List<Location> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson()))); //String locationToJson(List<Location> data) => json.encode(data); class Location { int serverId; int documentJobId; String deliveryTo; String serviceOn; String street1; String? street2; String? aptSuite; String city; String state; String zip; DateTime returnDate; // double? LonLat; Location({ required this.serverId, required this.documentJobId, required this.deliveryTo, required this.serviceOn, required this.street1, this.street2, this.aptSuite, required this.city, required this.state, required this.zip, required this.returnDate, // this.LonLat }); factory Location.fromJson(Map<String, dynamic> json) => Location( serverId: json["serverId"], documentJobId: json["documentJobId"], deliveryTo: json["deliveryTo"], serviceOn: json["serviceOn"], street1: json["street1"], street2: json["street2"], aptSuite: json["aptSuite"], city: json["city"], state: json["state"], zip: json["zip"], returnDate: DateTime.parse(json["returnDate"]), // LonLat: json["LonLat"] ); Map<String, dynamic> toJson() => { "serverId": serverId, "documentJobId": documentJobId, "deliveryTo": deliveryTo, "serviceOn": serviceOn, "street1": street1, "street2": street2, "aptSuite": aptSuite, "city": city, "state": state, "zip": zip, "returnDate": returnDate, // "LonLat": LonLat }; }
main.dart
import 'package:flutter/material.dart'; import 'package:ppisnet/src/pages/home_page.dart'; import 'dart:io'; class MyHttpOverrides extends HttpOverrides{ @override HttpClient createHttpClient(SecurityContext? context){ return super.createHttpClient(context) ..badCertificateCallback = (X509Certificate cert, String host, int port)=> true; } } void main() { HttpOverrides.global = MyHttpOverrides(); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, initialRoute: 'home', routes: { 'home': (BuildContext context) => HomePage(), } ); } }
location_api_provider.dart
import 'package:ppisnet/src/models/location_model.dart'; import 'package:ppisnet/src/providers/db_provider.dart'; import 'package:dio/dio.dart'; class LocationApiProvider { Future<List<Null>> getAllLocations() async { var url ="https://############/api/ghsjkjjkjfs"; Response response = await Dio().get(url); return (response.data as List).map((location) { print('Inserting $location'); DBProvider.db.createLocation(Location.fromJson(location)); }).toList(); } }
db_provider.dart
import 'dart:io'; import 'package:ppisnet/src/models/location_model.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:sqflite/sqflite.dart'; class DBProvider { static Database? _database; static final DBProvider db = DBProvider._(); DBProvider._(); Future<Database?> get database async { // If database exists, return database if (_database != null) return _database; // If database don't exists, create one _database = await initDB(); return _database; } // Create the database and the Employee table initDB() async { Directory documentsDirectory = await getApplicationDocumentsDirectory(); final path = join(documentsDirectory.path, 'location_manager.db'); return await openDatabase(path, version: 1, onOpen: (db) {}, onCreate: (Database db, int version) async { await db.execute('CREATE TABLE Location(' 'DocumentJobId INTEGER PRIMARY KEY,' 'Street1 TEXT,' 'Street2 TEXT,' 'AptSuite TEXT,' 'City TEXT,' 'State TEXT,' 'Zip TEXT' 'ServeOn TEXT,' 'DeliveryTo TEXT,' 'ServerId INTEGER,' 'LonLat DOUBLE,' 'ReturnDate DATE' ')'); }); } // Insert employee on database createLocation(Location newLocation) async { await deleteAllLocations(); final db = await database; final res = await db?.insert('Location', newLocation.toJson()); return res; } // Delete all employees Future<int?> deleteAllLocations() async { final db = await database; final res = await db?.rawDelete('DELETE FROM Location'); return res; } Future<List<Location>> getAllLocations() async { final db = await database; final res = await db?.rawQuery("SELECT * FROM Location"); List<Location> list = res!.isNotEmpty ? res.map((c) => Location.fromJson(c)).toList() : []; return list; } }
home_page.dart
import 'package:ppisnet/src/providers/db_provider.dart'; import 'package:ppisnet/src/providers/location_api_provider.dart'; import 'package:flutter/material.dart'; class HomePage extends StatefulWidget { @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { var isLoading = false; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Api to sqlite'), centerTitle: true, actions: <Widget>[ Container( padding: EdgeInsets.only(right: 10.0), child: IconButton( icon: Icon(Icons.settings_input_antenna), onPressed: () async { await _loadFromApi(); }, ), ), Container( padding: EdgeInsets.only(right: 10.0), child: IconButton( icon: Icon(Icons.delete), onPressed: () async { await _deleteData(); }, ), ), ], ), body: isLoading ? Center( child: CircularProgressIndicator(), ) : _buildLocationListView(), ); } _loadFromApi() async { setState(() { isLoading = true; }); var apiProvider = LocationApiProvider(); await apiProvider.getAllLocations(); // wait for 2 seconds to simulate loading of data await Future.delayed(const Duration(seconds: 2)); setState(() { isLoading = false; }); } _deleteData() async { setState(() { isLoading = true; }); await DBProvider.db.deleteAllLocations(); // wait for 1 second to simulate loading of data await Future.delayed(const Duration(seconds: 1)); setState(() { isLoading = false; }); print('All locations deleted'); } _buildLocationListView() { return FutureBuilder( future: DBProvider.db.getAllLocations(), builder: (BuildContext context, AsyncSnapshot snapshot) { if (!snapshot.hasData) { return Center( child: CircularProgressIndicator(), ); } else { return ListView.separated( separatorBuilder: (context, index) => Divider( color: Colors.black12, ), itemCount: snapshot.data.length, itemBuilder: (BuildContext context, int index) { return ListTile( leading: Text( "${index + 1}", style: TextStyle(fontSize: 20.0), ), title: Text( "City: ${snapshot.data[index].city} ${snapshot.data[index].state} "), subtitle: Text('Zip: ${snapshot.data[index].zip}'), ); }, ); } }, ); } }
报错信息
E/flutter (13411): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: DioException [bad response]: This exception was thrown because the response has a status code of 400 and RequestOptions.validateStatus was configured to throw for this status code. E/flutter (13411): The status code of 400 has the following meaning: "Client error - the request contains bad syntax or cannot be fulfilled" E/flutter (13411): Read more about status codes at https://developer.mozilla.org/en-US/docs/Web/HTTP/Status E/flutter (13411): In order to resolve this exception you typically have either to verify and fix your request code or you have to fix the server code. E/flutter (13411): E/flutter (13411): #0 DioMixin.fetch (package:dio/src/dio_mixin.dart:509:7) E/flutter (13411): E/flutter (13411): #1 LocationApiProvider.getAllLocations (package:ppisnet/src/providers/location_api_provider.dart:9:25) E/flutter (13411): E/flutter (13411): #2 _HomePageState._loadFromApi (package:ppisnet/src/pages/home_page.dart:56:5) E/flutter (13411): E/flutter (13411): #3 _HomePageState.build. (package:ppisnet/src/pages/home_page.dart:27:17) E/flutter (13411):
解决方案
一、排查400错误核心原因
400是客户端请求格式或参数错误,按以下步骤定位:
- 验证请求URL有效性
确认代码中的URL是完整、正确的接口地址,无拼写错误。如果接口需要身份验证(如Token、API Key),需在请求头中添加:Dio dio = Dio(); dio.options.headers['Authorization'] = 'Bearer YOUR_TOKEN'; // 适配接口要求的验证方式 Response response = await dio.get(url); - 测试接口请求合法性
用Postman/ApiFox等工具直接调用该接口,确认请求参数、头信息是否符合接口要求,再同步调整Dio的请求配置。 - 捕获详细错误信息
修改请求代码,打印完整错误详情,方便定位问题:Future<void> getAllLocations() async { try { var url = "https://your-real-url/api/ghsjkjjkjfs"; Dio dio = Dio(); Response response = await dio.get(url); // 后续存储逻辑 } on DioException catch (e) { print('错误响应数据: ${e.response?.data}'); print('请求头: ${e.requestOptions.headers}'); print('请求地址: ${e.requestOptions.uri}'); } }
二、修复代码中的潜在问题
即使解决400错误,以下问题会导致后续存储/读取数据失败:
- 数据库创建语句语法错误
db_provider.dart中创建表的SQL语句缺少逗号,且SQLite不支持DOUBLE和DATE类型,修正后:await db.execute('CREATE TABLE Location(' 'DocumentJobId INTEGER PRIMARY KEY,' 'Street1 TEXT,' 'Street2 TEXT,' 'AptSuite TEXT,' 'City TEXT,' 'State TEXT,' 'Zip TEXT,' 'ServeOn TEXT,' 'DeliveryTo TEXT,' 'ServerId INTEGER,' 'LonLat REAL,' 'ReturnDate TEXT' ')'); - Location模型日期序列化错误
toJson方法中直接返回DateTime对象无法被SQLite存储,需转成ISO字符串:Map<String, dynamic> toJson() => { // ...其他字段 "returnDate": returnDate.toIso8601String(), }; - 批量插入逻辑错误
当前createLocation方法每次插入前都会清空数据库,导致最终只保留最后一条数据。调整为批量插入前统一清空:
同时删除// location_api_provider.dart Future<void> getAllLocations() async { try { var url = "https://your-real-url/api/ghsjkjjkjfs"; Dio dio = Dio(); Response response = await dio.get(url); // 先清空数据库 await DBProvider.db.deleteAllLocations(); // 批量插入每条数据 for (var locationJson in response.data as List) { await DBProvider.db.createLocation(Location.fromJson(locationJson)); } } on DioException catch (e) { // 错误处理 } }createLocation方法中的await deleteAllLocations();。 - 返回类型无意义问题
getAllLocations方法返回Future<List<Null>>无实际用途,改为Future<void>即可。
内容的提问来源于stack exchange,提问作者Ron Sine
相关产品推荐
相关产品推荐

