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

Flutter调用Laravel API删除记录时抛出异常求助

Laravel 8 + Flutter CRUD 删除操作异常排查

我用Laravel 8搭建API,Flutter做客户端开发基础CRUD应用,更新功能正常,但删除记录时抛出Exception: Error happened on delete异常,求排查原因。

Laravel 8 代码

routes/api.php

Route::apiResource('categories', \App\Http\Controllers\Api\CategoryController::class);

app/Http/Controllers/Api/CategoryController.php

public function destroy(Category $category)
{
    $category->delete();
    return response()->noContent();
}

Flutter 代码

lib/screens/categories.dart

import 'package:flutter/material.dart';
import 'package:demo_app/models/category.dart';
import 'package:demo_app/widgets/category_edit.dart';

import 'package:demo_app/providers/category_provider.dart';
import 'package:provider/provider.dart';
// import 'package:http/http.dart' as http;

class Categories extends StatefulWidget {
  const Categories({super.key});

  @override
  CategoriesState createState() => CategoriesState();
}

class CategoriesState extends State<Categories> {
  @override
  Widget build(BuildContext context) {
    final provider = Provider.of<CategoryProvider>(context);
    List<Category> categories = provider.categories;

    return Scaffold(
        appBar: AppBar(
          title: const Text('Categories'),
        ),
        body: ListView.builder(
                  itemCount: categories.length,
                  itemBuilder: (context, index) {
                    Category category = categories[index];
                    return ListTile(
                      title: Text(category.name),
                      trailing: 
                          Row(
                            mainAxisSize: MainAxisSize.min,
                            children: <Widget>[
                              IconButton(
                                icon: const Icon(Icons.edit),
                                onPressed: () {
                                  showModalBottomSheet(
                                    isScrollControlled: true,
                                    context: context,
                                    builder: (context) {
                                      return CategoryEdit(category, provider.updateCategory); 
                                      });
                                },                         
                      ),
                      IconButton(
                            icon: const Icon(Icons.delete),
                            onPressed: () => showDialog(
                              context: context,
                              builder: (context) {
                                return AlertDialog(
                                  title: const Text('confirmation'),
                                  content: const Text('Are you sure you want to delete?'),
                                  actions: [
                                    TextButton(
                                      onPressed: () => deleteCategory(provider.deleteCategory, category),
                                      child: const Text('Confirm')
                                    ),
                                    TextButton(
                                      onPressed: () => Navigator.pop(context),
                                      child: const Text('Cancel')
                                    ),
                                  ]
                                );
                              }
                              ),                               
                          ),
                            ],
                          ),
                    );
                  },
        )
    );
  }

  Future deleteCategory(Function callback, Category category) async{
    await callback(category);
    if (!mounted) return;
    Navigator.pop(context);
  }

}

lib/services/api.dart

// import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:demo_app/models/category.dart';

// import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;


class ApiService{

  ApiService();

  final String baseUrl = 'http://flutter-api.test/api/';

  Future<List<Category>> fetchCategories() async {
    http.Response response =
        await http.get(Uri.parse('${baseUrl}categories'));

    List categories = jsonDecode(response.body);

    return categories.map((category) => Category.fromJson(category)).toList();
  }

  Future <Category> updateCategory(Category category) async {

    // String uri = '${baseUrl}categories/$category.id';
    String uri = '${baseUrl}categories/${category.id}';

    http.Response response = await http.put(Uri.parse(uri),
        headers: {
          HttpHeaders.contentTypeHeader: 'application/json',
          HttpHeaders.acceptHeader: 'application/json',
        },
        body: jsonEncode({'name': category.name}));
 
        if(response.statusCode != 200){
          throw Exception('Error happened on update');
        }

    return Category.fromJson(jsonDecode(response.body));
  }

  Future<void> deleteCategory(id) async {

    String uri = '${baseUrl}categories/$id';    

    http.Response response = await http.delete(Uri.parse(uri));
 
    if(response.statusCode != 204){
      throw Exception('Error happened on delete');
    }

  }
}

lib/providers/category_provider.dart

// import 'dart:io';

import 'package:demo_app/models/category.dart';
import 'package:flutter/material.dart';
import 'package:demo_app/services/api.dart';



class CategoryEdit extends StatefulWidget {
  final Category category;
  final Function categoryCallback;

  const CategoryEdit(this.category, this.categoryCallback, {Key? key}) : super(key: key);

  @override
  CategoryEditState createState() => CategoryEditState();

}


class CategoryEditState extends State<CategoryEdit> {
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  final categoryNameController = TextEditingController();
  ApiService apiService = ApiService();
  String errorMessage = '';


  @override 
  void initState(){
    categoryNameController.text = widget.category.name;
    super.initState();
  }

  @override
  Widget build(BuildContext context){
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Form(
          key: _formKey,
          child: Column(children: <Widget>[
            TextFormField(
              onChanged: (text) => setState(() => errorMessage = ''),
              controller: categoryNameController,
              validator: (String? value) {
                if (value!.isEmpty) {
                  return 'Enter category name';
                }
                return null;
              },
              decoration: const InputDecoration(
                border: OutlineInputBorder(),
                labelText: 'Category Name',
              ),
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: <Widget>[
                ElevatedButton(
                  child: const Text('Save'),
                  onPressed: () => saveCategory(context),
                ),
                ElevatedButton(
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.red
                  ),
                  child: const Text('Cancel'),
                  onPressed: () => Navigator.pop(context),
                ),
              ],
            ),
            Text(errorMessage, style: const TextStyle(color: Colors.red),)
          ]
          )
          )
         );
   }
  
  Future saveCategory(BuildContext context) async {
    final form = _formKey.currentState;

    if(!form!.validate())
    {
      return;
    }

    widget.category.name = categoryNameController.text;

    await widget.categoryCallback(widget.category);
    if (!mounted) return;
    Navigator.pop(context);

 }  


}

异常信息

flutter: Exception: Error happened on delete

排查步骤

Laravel 端检查点

  • 路由模型绑定验证:确认Category模型的主键是id,若主键为其他字段,需在模型中定义protected $primaryKey = '你的主键名';,否则Laravel无法匹配记录,返回404。
  • 权限与中间件:检查API路由是否需要认证中间件(如auth:sanctum),若需要,Flutter端删除请求未携带令牌会返回401/403状态码。
  • 查看服务器日志:打开storage/logs/laravel.log,里面会记录具体错误,比如记录不存在、数据库约束冲突等。
  • 手动测试API:用Postman或curl发送DELETE请求到http://flutter-api.test/api/categories/[有效ID],观察状态码:
    • 404:ID不存在或路由绑定失败
    • 401/403:认证未通过
    • 500:服务器内部错误(如关联数据无法删除)

Flutter 端检查点

  • 参数传递匹配:确认CategoryProvider的deleteCategory方法正确提取category.id传递给ApiService。若直接传整个对象,会导致URL拼接错误:
    // CategoryProvider中正确的deleteCategory示例
    Future<void> deleteCategory(Category category) async {
      await apiService.deleteCategory(category.id);
      categories.removeWhere((cat) => cat.id == category.id);
      notifyListeners();
    }
    
  • 补充请求头:对比update请求,delete请求缺少Accept头,部分Laravel API可能要求该头,修改ApiService:
    Future<void> deleteCategory(id) async {
      String uri = '${baseUrl}categories/$id';    
      http.Response response = await http.delete(Uri.parse(uri),
        headers: {
          HttpHeaders.acceptHeader: 'application/json',
        });
      if(response.statusCode != 204){
        throw Exception('Error happened on delete: ${response.statusCode} - ${response.body}');
      }
    }
    
  • 优化异常信息:在异常中加入响应状态码和内容,方便定位具体错误。
  • ID类型检查:确认Flutter中Category的id类型与Laravel模型一致,若Laravel用整数ID,Flutter中传递字符串会导致匹配失败。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 11:40:25