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 模型与提供者的主要内容,如果未能解决你的问题,请参考以下文章
带有 Provider 的单个模型(不是列表)的 CRUD 方法