在开发任何与车辆相关的工具类应用时,我们都会面临一个基础但关键的问题:如何有效管理车辆实体。就像我在开发油耗追踪器应用时发现的那样,如果没有完善的车辆管理系统,所有后续功能(如加油记录、维护计划)都将成为无本之木。
这个模块需要同时解决三个层面的问题:
我选择使用Flutter框架结合GetX状态管理库来实现这个模块,主要基于以下考量:
我将车辆管理模块划分为四个明确层级:
code复制┌────────────────┐
│ UI层 │ <- 页面展示与用户交互
├────────────────┤
│ Controller层 │ <- 业务逻辑与状态管理
├────────────────┤
│ Service层 │ <- 数据持久化操作
├────────────────┤
│ Model层 │ <- 数据结构定义
└────────────────┘
这种分层带来的主要优势是:
数据库方案选择:
经过对比Hive、SharedPreferences和SQLite后,我最终选择sqflite作为本地存储方案,主要因为:
状态管理方案:
GetX相比Provider和Bloc的优势在于:
车辆模型采用最小字段集原则,只包含必要属性:
dart复制class Vehicle {
final String id; // 唯一标识
final String name; // 车辆名称
final String plateNo; // 车牌号
final int createdAtMs; // 创建时间戳
// 标准构造函数、create工厂方法、序列化方法...
}
实际项目中,你可以根据需求扩展字段(如车型、排量等),但建议初期保持精简。
对应的SQLite表结构设计如下:
sql复制CREATE TABLE vehicles (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
plate_no TEXT,
created_at_ms INTEGER NOT NULL
)
几个设计要点:
数据库操作封装在FillUpDb类中,关键方法包括:
dart复制class FillUpDb {
// 初始化数据库
Future<Database> _open() async {...}
// 查询所有车辆(按创建时间倒序)
Future<List<Vehicle>> listVehicles() async {
final db = await _open();
final rows = await db.query('vehicles',
orderBy: 'created_at_ms DESC');
return rows.map(Vehicle.fromMap).toList();
}
// 新增或更新车辆
Future<void> upsertVehicle(Vehicle vehicle) async {
final db = await _open();
await db.insert('vehicles', vehicle.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace);
}
// 删除车辆及关联数据
Future<void> deleteVehicle(String vehicleId) async {
final db = await _open();
await db.delete('vehicles',
where: 'id = ?', whereArgs: [vehicleId]);
await db.delete('fillups',
where: 'vehicle_id = ?', whereArgs: [vehicleId]);
}
}
VehiclesController负责管理车辆相关状态:
dart复制class VehiclesController extends GetxController {
final vehicles = <Vehicle>[].obs; // 车辆列表
final activeVehicleId = Rxn<String>(); // 当前选中车辆ID
// 刷新数据
Future<void> refreshAll() async {
final list = await FillUpDb.instance.listVehicles();
vehicles.value = list;
if (activeVehicleId.value == null && list.isNotEmpty) {
activeVehicleId.value = list.first.id; // 默认选中第一辆
}
}
// 获取当前选中车辆
Vehicle? get activeVehicle {
final id = activeVehicleId.value;
return id == null ? null : vehicles.firstWhereOrNull((v) => v.id == id);
}
// 删除车辆
Future<void> remove(String vehicleId) async {
await FillUpDb.instance.deleteVehicle(vehicleId);
if (activeVehicleId.value == vehicleId) {
activeVehicleId.value = null; // 清除当前选中状态
}
await refreshAll(); // 刷新列表
}
}
在应用启动时通过Bindings初始化Controller:
dart复制class FillUpBindings extends Bindings {
@override
void dependencies() {
Get.put(VehiclesController()); // 全局单例
// 其他Controller...
}
}
这种设计确保整个应用共享同一个车辆状态实例。
使用GetX的路由系统定义车辆相关页面:
dart复制getPages: [
GetPage(name: '/vehicles', page: () => VehiclesPage()),
GetPage(name: '/vehicle/add', page: () => VehicleEditorPage()),
GetPage(name: '/vehicle/detail', page: () => VehicleDetailPage()),
// 其他页面...
]
路由命名遵循以下规范:
核心交互逻辑:
dart复制Obx(() {
if (ctl.vehicles.isEmpty) {
return _EmptyState(...); // 空状态提示
}
return ListView.separated(
itemCount: ctl.vehicles.length,
itemBuilder: (context, index) {
final vehicle = ctl.vehicles[index];
return InkWell(
onTap: () => Get.toNamed('/vehicle/detail', arguments: vehicle),
child: ListTile(
title: Text(vehicle.name),
subtitle: Text(vehicle.plateNo),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => _confirmDelete(vehicle.id),
),
),
);
},
);
})
表单处理关键代码:
dart复制final nameController = TextEditingController();
final plateController = TextEditingController();
// 保存逻辑
void _saveVehicle() {
final name = nameController.text.trim();
if (name.isEmpty) {
Get.snackbar('错误', '请输入车辆名称');
return;
}
final vehicle = Vehicle.create(
name: name,
plateNo: plateController.text.trim(),
);
ctl.addOrUpdate(vehicle);
Get.back(); // 自动返回
}
@override
void dispose() {
nameController.dispose();
plateController.dispose();
super.dispose();
}
问题现象:
删除车辆后,关联的加油记录可能成为孤儿数据
解决方案:
在数据库层实现级联删除:
dart复制Future<void> deleteVehicle(String vehicleId) async {
final db = await _open();
await db.delete('vehicles', where: 'id = ?', whereArgs: [vehicleId]);
await db.delete('fillups', where: 'vehicle_id = ?', whereArgs: [vehicleId]);
}
问题现象:
在多页面场景下,车辆选择状态不同步
解决方案:
使用全局状态管理:
dart复制// 在任何页面获取当前车辆
final currentVehicle = Get.find<VehiclesController>().activeVehicle;
// 切换车辆时自动通知所有监听者
void selectVehicle(String id) {
activeVehicleId.value = id;
}
列表渲染优化:
使用ListView.separated而非ListView.builder,避免不必要的重建
数据库操作优化:
我在实际开发中遇到的一个典型问题是车辆删除后的状态同步。最初的设计是在页面层处理状态更新,这导致某些边缘场景下状态不同步。后来调整为在Controller层统一管理,问题得到彻底解决。
另一个值得分享的经验是关于表单设计。最初版本要求填写过多字段(如车型、购买日期等),导致用户流失率增高。简化到只保留必要字段后,使用体验明显改善。