在Flutter中,有多种方式可以进行数据持久化存储。以下是一些常见的方式:
以上的这些我们都不使用,这里要使用的是Hive库,地址是 Hive,感兴趣的可以自行了解,本文运行效果图。
Hive是一个轻量级、快速的本地数据库解决方案,适用于在移动应用程序中进行数据持久化存储。Hive采用高效的自定义序列化算法,能够在移动设备上快速读写数据,适用于处理结构化数据。并且Hive是用纯Dart编写的,这使得它比不支持Flutter网络的SQLite更有优势。
首先我们创建一个名为study_hive
的项目。
创建项目之后,我们配置一下依赖库,在项目的pubspec.yaml文件中,添加如下所示代码:
dependencies:
get:
hive:
hive_flutter:
dev_dependencies:
hive_generator:
build_runner:
在dependencies
中我添加了get和hive的库,在dev_dependencies中添加了一个构建对象的依赖库。冒号后面没有写版本号就是获取该库最新的版本。添加位置如下图所示:
然后点击Pub get获取对应的依赖库即可,到这里为止我们的配置工作就完成了。
在使用Hive库时我们需要想一下,用这个库去做什么?先设想一个应用场景,而不是写到哪里就是哪里,乱枪打鸟不可取。我们就写这样一个场景,对于人员信息的操作,可以增加、查询、修改、删除、删除所有。基于这个场景我们就可以去设计UI了,我们尽量在一个页面去解决,更直观一些(PS:我也是偷一个懒)。
首先我们在lib目录下新建一个page包,page包下新建一个hive_page.dart
,里面的代码如下:
import 'package:flutter/material.dart';
class HivePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Hive Demo"),
),
body: Container(
color: Colors.blue,
),
);
}
}
当前页面很简单,就是一个标题和蓝色背景,当然你现在还看不到的,我们需要修改一下main.dart中的代码:
import 'package:flutter/material.dart';
import 'package:study_hive/page/hive_page.dart';
void main() async {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Hive Demo',
theme: ThemeData(
primaryColor: Colors.blueAccent,
appBarTheme: const AppBarTheme(elevation: 0),
),
home: HivePage(),
);
}
}
这里的修改就是去掉了原来默认代码,并且加载我们刚写好的HivePage
,下面我们可以运行一下,虚拟器或者真机都可以。
在HivePage的build()中增加如下代码:
///通用输入框
Widget baseEdit(String hintText, TextInputType type,
TextEditingController textController) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: Colors.black87,
width: 1.0,
),
),
margin: const EdgeInsets.only(top: 6),
padding: const EdgeInsets.all(0),
height: 44,
child: TextField(
textInputAction: TextInputAction.none,
keyboardType: type,
cursorColor: Colors.black87,
cursorWidth: 1,
controller: textController,
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(left: 10),
filled: true,
fillColor: Colors.white,
hintText: hintText,
hintStyle: const TextStyle(
textBaseline: TextBaseline.alphabetic,
color: Colors.grey,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
),
),
),
);
}
这里的代码就是构建一个输入框的组件,将里面的提示文本、键盘类型和输入框控制器抽离了出来。控制器我们就放到GetX中使用,在page包下新建一个hive_controller.dart
,代码如下所示:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class HiveController extends GetxController {
late TextEditingController nameEditController,ageEditController;
@override
void onInit() {
super.onInit();
nameEditController = TextEditingController();
ageEditController = TextEditingController();
}
}
这里主要就是对于输入框控制器的初始化。回到HivePage的build中再写两个组件,代码如下:
var size4 = const SizedBox(
height: 4,
width: 4,
);
///保存按钮
var saveBtn = TextButton(
onPressed: () {
print('Save');
},
child: const Text(
'Save',
style: TextStyle(color: Colors.blue),
));
一个是间隔,一个是保存按钮,然后我们可以再写一个组件用来包含刚才所写的内容。这里面就需要用到baseEdit去构建两个输入框,因此我们加上GetX,在page包下新建一个hive_controller.dart
,代码如下所示:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class HiveController extends GetxController {
late TextEditingController nameEditController,ageEditController;
@override
void onInit() {
super.onInit();
nameEditController = TextEditingController();
ageEditController = TextEditingController();
}
}
回到HivePage中,在build中增加一个组件,代码如下:
///保存组件
var saveWidget = Container(
width: MediaQuery.of(context).size.width,
margin: const EdgeInsets.all(8),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.0),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
baseEdit('Name', TextInputType.name, controller.nameEditController),
size4,
baseEdit('Age', TextInputType.number, controller.ageEditController),
saveBtn
],
),
);
最后我们再修改一下返回的Scaffold
中的代码,在这里我们加载刚才写好的保存组件,如下所示:
return Scaffold(
appBar: AppBar(
title: const Text("Hive Demo"),
),
body: Container(
color: Colors.blue,
child: Column(
children: [saveWidget],
),
),
);
这里你需要注意的就是代码的顺序了,当前这个组件在最下边,通过一张图来说明。
运行一下:
这样增加的UI就写好了,下面我们构建显示和删除的。
在build中添加如下代码:
///列表组件
var listWidget = Expanded(
child: Container(
width: MediaQuery.of(context).size.width,
// 允许高度自适应
margin: const EdgeInsets.only(left: 8, right: 8, bottom: 8),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.0),
),
));
var deleteAllBtn = ElevatedButton(
onPressed: () {
print('DeleteAll');
},
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.delete, color: Colors.red),
SizedBox(width: 4),
Text(
'DeleteAll',
style: TextStyle(color: Colors.red),
)
],
));
再修改一下返回的Scaffold,将列表和按钮组件添加进去,代码如下所示:
return Scaffold(
appBar: AppBar(
title: const Text("Hive Demo"),
),
body: Container(
color: Colors.blue,
child: Column(
children: [saveWidget, listWidget, deleteAllBtn],
),
),
);
再保存一下,热重载,效果如图所示:
下面我们就可以开始使用Hive了,之前我们已经添加过依赖了,下面我们首先进行初始化。
在Flutter中使用Hive,我们需要在main()
函数中进行初始化,注意导包语句:
import 'package:hive_flutter/hive_flutter.dart';
main()
函数代码如下所示:
void main() async {
//初始化Hive
await Hive.initFlutter();
runApp(const MyApp());
}
初始化之后我们就可以去使用了,在此之前我们需要明确使用的方式,因为我们操作的是对象,包含常规的数据类型,因此我们就需要自定义对象。
在lib
下创建一个models
目录,该目录下创建person.dart文件,代码如下:
class Person {
String name;
int age;
Person({required this.name,required this.age});
}
这是标准的对象代码,然后我们可以使用Hive注释这个类和类里面的变量,然后快速生成一个TypeAdapter类代码,下面我们修改一下Person的代码如下:
import 'package:hive/hive.dart';
part 'person.g.dart';
@HiveType(typeId: 1)
class Person {
@HiveField(0)
String name;
@HiveField(1)
int age;
Person({required this.name, required this.age});
}
首先注意导包的语句,这里的part 'person.g.dart';
语句会标红,这是因为目前还没有这个文件,这个文件就是我们需要快捷生成的。HiveType 和 HiveField 是 Hive 数据库中用来定义对象映射和序列化的注解。
HiveType
是一个标记注解,用于标识 Hive 中的自定义对象类。它告诉 Hive 数据库,被注解的类是一个 Hive 对象,需要进行序列化和反序列化。@HiveType()
注解来标记这个类,以便 Hive 可以识别并处理这个类。HiveField
是用来标记类中的字段(成员变量)的注解,用于指定字段在 Hive 数据库中的位置和顺序。@HiveField()
注解来标记类中的字段,以便 Hive 可以按照指定的顺序进行序列化和反序列化。 下面我们通过在Terminal
中输入一行代码,生成对应的TypeAdapter对象类,代码如下所示:
flutter packages pub run build_runner build
输入后回车,如下图所示:
你会看到对应的person.g.dart
文件就已经生成在models
文件夹中,里面的代码如下所示:
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'person.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class PersonAdapter extends TypeAdapter<Person> {
@override
final int typeId = 1;
@override
Person read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return Person(
name: fields[0] as String,
age: fields[1] as int,
);
}
@override
void write(BinaryWriter writer, Person obj) {
writer
..writeByte(2)
..writeByte(0)
..write(obj.name)
..writeByte(1)
..write(obj.age);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is PersonAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
下面我们注册TypeAdapter对象
依然是修改main()
函数,注意一点,在打开使用Hive的盒子之前,需要先注册TypeAdapter,代码如下所示:
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:study_hive/models/person.dart';
import 'package:study_hive/page/hive_page.dart';
void main() async {
//初始化Hive
await Hive.initFlutter();
//注册TypeAdapter
Hive.registerAdapter(PersonAdapter());
//打开盒子
await Hive.openBox<Person>('personBox');
runApp(const MyApp());
}
注意导包语句,现在我们的盒子就打开了,盒子名称是personBox,这个可以自己去定义的,下面我们就可以正式去使用这个盒子来进行CURD了。
在进行CURD时,我们将代码写在GetxController中,提供相关的函数进行操作,下面我们修改一下HiveController
中的代码:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:study_hive/models/person.dart';
class HiveController extends GetxController {
late TextEditingController nameEditController, ageEditController;
final personBox = Hive.box<Person>('personBox');
@override
void onInit() {
super.onInit();
nameEditController = TextEditingController();
ageEditController = TextEditingController();
}
void save() {
var person = Person(
name: nameEditController.text, age: int.parse(ageEditController.text));
personBox.add(person);
nameEditController.clear();
ageEditController.clear();
}
void modify(int index, Person person) {
personBox.putAt(index, person);
}
void delete(int index) {
personBox.deleteAt(index);
}
void deleteAll() {
personBox.clear();
}
@override
void onClose() {
nameEditController.dispose();
ageEditController.dispose();
super.onClose();
}
}
上面的代码解释一下,首先我们获取personBox盒子对象,final personBox = Hive.box<Person>('personBox');
,然后就是save()
函数中获取输入框的值进行保存,保存之后再清空输入框,这里就没有对输入框的内容判空处理,需要注意一下。modify()
函数中通过下标和person对象就可以完成,删除和删除所有就是可以直接处理的,就没有什么好说的。你会发现没有查询,这是因为Hive提供了一个名为ValueListenableBuilder
的小部件,它只在数据库内的任何数值被修改时才会刷新。下面我们就可以在HivePage中去使用刚才所写的函数。
首先我们修改一下listWidget
组件的代码:
var listWidget = Expanded(
child: Container(
width: MediaQuery.of(context).size.width,
margin: const EdgeInsets.only(left: 8, right: 8, bottom: 8),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.0),
),
child: ValueListenableBuilder(
valueListenable: controller.personBox.listenable(),
builder: (context, box, widget) {
if (box.isEmpty) {
return const Center(
child: Text('Empty'),
);
} else {
return ListView.builder(
itemCount: box.length,
itemBuilder: (context, index) {
var personData = box.getAt(index)!;
return ListTile(
title: Text(personData.name),
subtitle: Text(personData.age.toString()),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: const Icon(Icons.edit),
onPressed: () {
showModifyDialog(index, personData);
},
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
controller.delete(index);
},
),
],
),
);
});
}
})));
这里的核心代码就是ValueListenableBuilder
的使用,这里我们判断了box是否为空,空就显示文字提示一下,不为空就构建一个ListView显示Item数据。如下图所示:
在列表的Item中我们除了显示用户的名称和年龄之外还有两个功能按钮,分别用于修改和删除,如下图所示:
针对于删除很简单之后调用控制器里面写好的函数就可以了,删除之后列表会自动刷新的。而修改的话屏幕上没有空间了,因此我就写一个弹窗去显示需要修改的内容,代码如下所示:
void showModifyDialog(int index, Person personData) => showDialog(
context: context,
builder: (BuildContext context) {
TextEditingController nameController =
TextEditingController(text: personData.name);
TextEditingController ageController =
TextEditingController(text: personData.age.toString());
return AlertDialog(
title: const Text('Modify Data'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(controller: nameController),
TextField(controller: ageController)
],
),
actions: [
ElevatedButton(
child: const Text('Modify'),
onPressed: () {
var person = Person(
name: nameController.text,
age: int.parse(ageController.text));
controller.modify(index, person);
Navigator.of(context).pop(); // 关闭对话框
})
],
);
});
弹窗修改之后就关闭弹窗。最后我们再修改一下保存按钮和删除所有按钮组件的代码,如下所示:
var saveBtn = TextButton(
onPressed: () {
controller.save();
},
child: const Text(
'Save',
style: TextStyle(color: Colors.blue),
));
var deleteAllBtn = ElevatedButton(
onPressed: () {
controller.deleteAll();
},
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.delete, color: Colors.red),
SizedBox(width: 4),
Text(
'DeleteAll',
style: TextStyle(color: Colors.red),
)
],
));
那么基本上代码就写完了,下面我们整体看一下运行效果。
效果符合我的预期,文章到这里就结束了,元宵节快乐呀!
源码地址:study_hive