Flutter CRUD 模型与提供者

Posted

技术标签:

【中文标题】Flutter CRUD 模型与提供者【英文标题】:Flutter CRUD model with provider 【发布时间】:2021-05-18 09:17:16 【问题描述】:

我正在使用 Provider 学习 Flutter 并尝试制作 CRUD 模型。我已经完成了创建部分、读取部分和删除部分,但是在模型的更新部分中遇到了困难。我进行了读取和显示数据的 API 调用,创建了一个创建新对象的方法和一个用于删除对象的方法,但剩下的就是修改/编辑对象。这是我到目前为止所做的代码:

调用 API 的车辆列表:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'models/vehicle.dart';
import 'screens/vehicle_details_screen.dart';
import 'services/vehicle_api.dart';
import 'models/vehicle_data_provider.dart';

class VehicleList extends StatefulWidget 
  @override
  _VehicleList createState() => _VehicleList();


class _VehicleList extends State<VehicleList> 

  _getPosts() async 
    var provider = Provider.of<HomePageProvider>(context, listen: false);

    var postsResponse = await fetchVehicles();
    if (postsResponse.isSuccessful) 
      provider.setPostsList(postsResponse.data, notify: false);
     else 
      provider.mergePostsList(postsResponse.data, notify: false);
    

    provider.setIsHomePageProcessing(false);
  

  @override
  void initState() 
    super.initState();

    _getPosts();
  

  @override
  Widget build(BuildContext context) 
    return Column(
      children: [
        Consumer<HomePageProvider>(
          builder: (context, vehicleData, child) 
            return Column(
              crossAxisAlignment: CrossAxisAlignment.center,
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                SizedBox(
                  height: 12.0,
                ),
                Container(
                  decoration: BoxDecoration(
                    color: Colors.grey[300],
                    borderRadius: BorderRadius.all(
                      Radius.circular(12.0),
                    ),
                  ),
                  child: SingleChildScrollView(
                    child: DataTable(
                      columnSpacing: 50,
                      columns: <DataColumn>[
                        DataColumn(
                          label: Text(
                            'Friendly Name',
                            style: TextStyle(fontStyle: FontStyle.italic),
                          ),
                        ),
                        DataColumn(
                          label: Text(
                            'Licence Plate',
                            style: TextStyle(fontStyle: FontStyle.italic),
                          ),
                        ),
                        DataColumn(
                          label: Text(
                            'Delete',
                            style: TextStyle(fontStyle: FontStyle.italic),
                          ),
                        ),
                      ],
                      rows: List.generate(
                        vehicleData.postsList.length,
                        (index) 
                          VehicleData post = vehicleData.getPostByIndex(index);
                          return DataRow(
                            cells: <DataCell>[
                              DataCell(
                                Text('$post.friendlyName'),
                                onTap: () 
                                  Navigator.push(
                                      context,
                                      MaterialPageRoute(
                                          builder: (context) =>
                                              VehicleDetailsScreen(
                                                color: post.color,
                                                friendlyName: post.friendlyName,
                                                licencePlate: post.licencePlate,
                                              )));
                                ,
                              ),
                              DataCell(
                                Text('$post.licencePlate'),
                              ),
                              DataCell(
                                IconButton(
                                  icon: Icon(Icons.delete),
                                  onPressed: () 
                                    vehicleData.deletePost(post);
                                  ,
                                ),
                                // onTap: vehicleData.deletePost(post),
                              ),
                            ],
                          );
                        ,
                      ),
                    ),
                  ),
                ),
              ],
            );
          ,
        ),
      ],
    );
  


车辆详情屏幕

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'models/vehicle.dart';
import 'models/vehicle_data_provider.dart';

class VehicleDetailsScreen extends StatefulWidget 
  static const String id = 'vehicle_details_screen';
  final vehicleList;
  String color, friendlyName, licencePlate;
  VehicleDetailsScreen(
      this.vehicleList, this.color, this.friendlyName, this.licencePlate);
  @override
  _VehicleDetailsScreenState createState() => _VehicleDetailsScreenState();


class _VehicleDetailsScreenState extends State<VehicleDetailsScreen> 
  final GlobalKey<FormState> _formKey = new GlobalKey<FormState>();
  String color, friendlyName, licencePlate;
  final TextEditingController _controllerfriendlyName = TextEditingController();
  final TextEditingController _controllerlicencePlate = TextEditingController();
  final TextEditingController _controllerColor = TextEditingController();

  @override
  void initState() 
    super.initState();
    setState(() 
      _controllerColor.text = widget.color;
      _controllerfriendlyName.text = widget.friendlyName;
      _controllerlicencePlate.text = widget.licencePlate;
    );
  

  @override
  void dispose() 
    _controllerColor.dispose();
    _controllerfriendlyName.dispose();
    _controllerlicencePlate.dispose();
    super.dispose();
  

  @override
  Widget build(BuildContext context) 
    final myProvider = Provider.of<HomePageProvider>(context);
    return Scaffold(
      body: Container(
        width: MediaQuery.of(context).size.width,
        height: MediaQuery.of(context).size.height,
        color: Colors.white,
        child: Container(
          padding: EdgeInsets.all(20.0),
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.only(
              topLeft: Radius.circular(20.0),
              topRight: Radius.circular(20.0),
            ),
          ),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'Vehicle Details',
                textAlign: TextAlign.center,
                style: TextStyle(
                  fontSize: 30.0,
                  color: Colors.grey,
                ),
              ),
              Container(
                width: MediaQuery.of(context).size.width,
                height: MediaQuery.of(context).size.height - 400,
                child: Form(
                  key: _formKey,
                  child: new ListView(
                    shrinkWrap: true,
                    padding: const EdgeInsets.symmetric(horizontal: 16.0),
                    children: <Widget>[
                      TextFormField(
                        validator: (value) 
                          if (value.isEmpty) 
                            return 'Please enter friendly name';
                          
                          return null;
                        ,
                        controller: _controllerColor,
                        onSaved: (value) 
                          color = value;
                        ,
                        decoration: const InputDecoration(
                          icon: Icon(Icons.directions_car),
                          hintText: 'Enter friendly car name',
                          labelText: 'Name',
                        ),
                        keyboardType: TextInputType.datetime,
                      ),
                      TextFormField(
                        onSaved: (value) 
                          friendlyName = value;
                        ,
                        controller: _controllerfriendlyName,
                        validator: (value) 
                          if (value.isEmpty) 
                            return 'Please enter vehicle model';
                          
                          return null;
                        ,
                        decoration: const InputDecoration(
                          icon: Icon(Icons.directions_car_outlined),
                          hintText: 'Enter vehicle model',
                          labelText: 'Model',
                        ),
                        keyboardType: TextInputType.phone,
                      ),
                      TextFormField(
                        validator: (value) 
                          if (value.isEmpty) 
                            return 'Please enter some text';
                          
                          return null;
                        ,
                        controller: _controllerlicencePlate,
                        onSaved: (value) 
                          licencePlate = value;
                        ,
                        decoration: const InputDecoration(
                          icon: Icon(Icons.calendar_today),
                          hintText: 'Enter manufactured year',
                          labelText: 'Year',
                        ),
                        keyboardType: TextInputType.emailAddress,
                      ),
                    ],
                  ),
                ),
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  FlatButton(
                    child: Text(
                      'Cancel',
                      style: TextStyle(
                        color: Colors.white,
                      ),
                    ),
                    color: Colors.grey,
                    onPressed: () 
                      Navigator.pop(context);
                    ,
                  ),
                  SizedBox(
                    width: 10,
                  ),
                  FlatButton(
                    child: Text(
                      'Edit',
                      style: TextStyle(
                        color: Colors.white,
                      ),
                    ),
                    color: Colors.grey,
                    onPressed: () async 
                      if (_formKey.currentState.validate()) 
                        _formKey.currentState.save();
////The method bellow updateList ()needs to be changed in order to modify the existing list, this method just creates a new one.
                        await myProvider.updateList(VehicleData(  
                          friendlyName: friendlyName.toString(),
                          color: color.toString(),
                          licencePlate: licencePlate.toString(),
                        ));
                        Navigator.of(context).pop();
                      
                    ,
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  


这里是 HomePageProvider 模型:

import 'package:flutter/foundation.dart';
import 'models/vehicle.dart';

class HomePageProvider extends ChangeNotifier 
  bool _isHomePageProcessing = true;
  int _currentPage = 1;
  String friendlyName;
  List<VehicleData> _postsList = [];
  bool _shouldRefresh = true;

  bool get shouldRefresh => _shouldRefresh;

  setShouldRefresh(bool value) => _shouldRefresh = value;

  int get currentPage => _currentPage;

  setCurrentPage(int page) 
    _currentPage = page;
  

  bool get isHomePageProcessing => _isHomePageProcessing;

  setIsHomePageProcessing(bool value) 
    _isHomePageProcessing = value;
    notifyListeners();
  

  List<VehicleData> get postsList => _postsList;

  setPostsList(List<VehicleData> list, bool notify = true) 
    _postsList = list;
    if (notify) notifyListeners();
  
// This method needs to be changed
  updateList(VehicleData list) 
    _postsList.add(list);
    notifyListeners();
  

  mergePostsList(List<VehicleData> list, bool notify = true) 
    _postsList.addAll(list);
    if (notify) notifyListeners();
  

  deletePost(VehicleData list) 
    _postsList.remove(list);

    notifyListeners();
  

  addPost(VehicleData post, bool notify = true) 
    _postsList.add(post);
    if (notify) notifyListeners();
  

  VehicleData getPostByIndex(int index) => _postsList[index];

  int get postsListLength => _postsList.length;


这就是车辆数据模型:

class VehicleData 
  final String id,
      licencePlate,
      countryRegistered,
      type,
      color,
      vin,
      owner,
      garage,
      friendlyName,
      model,
      make,
      year,
      status,
      insuredBy,
      insurancePolicyNumber,
      policyExpirationDate,
      mobile,
      cellPhoneProvider,
      notes;
  final int capacity, co2perKm, luggageCapacity;

  VehicleData(
      this.id,
      this.licencePlate,
      this.countryRegistered,
      this.type,
      this.color,
      this.vin,
      this.co2perKm,
      this.owner,
      this.garage,
      this.friendlyName,
      this.model,
      this.make,
      this.year,
      this.capacity,
      this.luggageCapacity,
      this.status,
      this.insuredBy,
      this.insurancePolicyNumber,
      this.policyExpirationDate,
      this.mobile,
      this.cellPhoneProvider,
      this.notes);

  factory VehicleData.fromJson(Map<String, dynamic> json) 
    return VehicleData(
      id: json['id'],
      licencePlate: json['licencePlate'],
      countryRegistered: json['countryRegistered'],
      type: json['type'],
      color: json['color'],
      vin: json['vin'],
      co2perKm: json['co2perKm'],
      owner: json['owner'],
      garage: json['garage'],
      friendlyName: json['friendlyName'],
      model: json['model'],
      make: json['make'],
      year: json['year'],
      capacity: json['capacity'],
      luggageCapacity: json['luggageCapacity'],
      status: json['status'],
      insuredBy: json['insuredBy'],
      insurancePolicyNumber: json['insurancePolicyNumber '],
      policyExpirationDate: json['policyExpirationDate '],
      mobile: json['mobile'],
      cellPhoneProvider: json['cellPhoneProvider '],
      notes: json['notes '],
    );
  

API 调用:

import 'dart:convert';
import 'package:http/http.dart';;
import 'models/vehicle.dart';

Future<HTTPResponse<List<VehicleData>>> fetchVehicles() async 
  final response =
      await get('https://run.mocky.io/v3/d7a00528-176c-402f-850e-76567de3c68d');
  if (response.statusCode == 200) 
    var body = jsonDecode(response.body)['data'];
    List<VehicleData> posts = [];
    body.forEach((e) 
      VehicleData post = VehicleData.fromJson(e);
      posts.add(post);
    );
    return HTTPResponse<List<VehicleData>>(
      true,
      posts,
      message: 'Request Successful',
      statusCode: response.statusCode,
    );
   else 
    return HTTPResponse<List<VehicleData>>(
      false,
      null,
      message:
          'Invalid data received from the server! Please try again in a moment.',
      statusCode: response.statusCode,
    );
  

class HTTPResponse<T> 
  bool isSuccessful;
  T data;
  int statusCode;
  String message;
  HTTPResponse(this.isSuccessful, this.data, this.message, this.statusCode);

主要:

import 'package:flutter/material.dart';
import 'components/vehicle_list.dart';
import 'screens/settings_screen.dart';
import 'package:provider/provider.dart';
import 'models/vehicle_data_provider.dart';

void main()  
 
  runApp(MyApp());


class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MultiProvider(
      providers: [
        ChangeNotifierProvider<HomePageProvider>(
            create: (_) => HomePageProvider()),
        ChangeNotifierProvider<AppLocale>(create: (_) => AppLocale()),
      ],
      child: Consumer<AppLocale>(builder: (context, locale, child) 
        return MaterialApp(
          home: VehicleList(),
        );
      ),
    );
  

就像我说的,我只需要更新(编辑)现有数据,这些数据已经从一个屏幕传递到另一个屏幕。有人可以向我解释在更新数据时我做错了什么。我知道updateList() 方法与 addNew 方法完全相同,但我似乎找不到解决方案。

【问题讨论】:

你能分享你注册HomePageProvider的代码吗?这将帮助我理解问题。 @PreetShah 已添加到帖子中。 【参考方案1】:

好的。所以,我想我明白了。问题可能是因为您有两个单独的数据源。一个是 API,另一个是 HomePageProvider 类。现在,您正在做的是从 API 收集数据并将其放入 HomePageProvider 类中。但是每次调用 VehicleList() 小部件时,都会从 API 刷新数据。因此,如果您对 HomePageProvider 类中的数据进行任何更改,它将被来自 API 的数据覆盖。

我建议您收集 HomePageProvider 类本身中的所有数据,然后当且仅当您的 _postsList 变量没有数据时调用 API。或者确定一些其他措施来了解您是否应该调用 API。如果您的数据仅包含唯一值,我建议使用Set 而不是List。它将帮助您避免一些不必要的代码。

【讨论】:

我会尝试改变这一点。您提到的想法是否有任何资源或完整示例?困扰我的是我已经完成了其他 3 个,但是在更新/编辑时,我不太明白出了什么问题.. 如果我的想法可行,请告诉我。看,问题是你需要一个分离视图、模型、存储库和控制器的架构。是否有任何功能可以让您直接将数据添加到 API 数据库? 我虽然只用一个模型做了这个,当我调用API时,数据写入提供者(HomePageProvider),我做了一个添加和删除方法,但我有没有修改/编辑方法。有没有一种方法可以在提供程序中创建一个方法,或者我真的需要一种方法来将修改后的数据发送回 API 以及如果需要如何? 看,问题是您直接从 API 检索数据并将其放入 Provider 中。您需要做的是检索 Provider 中的数据。因为每次加载 Widget VehicleList 时,都会调用函数 fetchVehicles()。因此,新数据每次都会在您的 Provider 中被覆盖。因此,请确保您在 HomePageProvider 中调用过一次。 我明白你的意思,但我不明白我如何能够添加、删除它们而不是修改它们?我是不是没有在initState 中设置小部件的状态,所以它不会再次被调用,所以我可以通过 Provider 更改数据?很抱歉打扰您,但我做不到,我已经完成了所有其他工作,但没有修改/编辑。

以上是关于Flutter CRUD 模型与提供者的主要内容,如果未能解决你的问题,请参考以下文章

在 Flutter 中使用单例与提供者?

补丁/更新后不与提供者更新状态,颤振

带有 Provider 的单个模型(不是列表)的 CRUD 方法

Flutter Bloc 与“InProgress”的提供者状态管理

与flutter中更改通知提供程序相关的问题

Spring Data MongoDB 一:入门篇(环境搭建简单的CRUD操作)