以下是一个使用 Flutter 和 Provider 实现的书籍 CRUD(创建、读取、更新、删除)示例,数据保存在本地存储中,使用了 Hive 作为本地数据库。
项目结构
-
main.dart
:应用入口文件,初始化 Hive 和 Provider。 -
book_model.dart
:定义书籍模型。 -
book_provider.dart
:管理书籍数据的 Provider。 -
book_list_screen.dart
:显示书籍列表的界面。 -
book_form_screen.dart
:添加或编辑书籍的表单界面。
1. 添加依赖
在 pubspec.yaml
文件中添加以下依赖:
yaml
复制
dependencies:
flutter:
sdk: flutter
hive: ^2.2.3
hive_flutter: ^1.1.0
provider: ^6.0.5
path_provider: ^2.0.14
dev_dependencies:
hive_generator: ^2.0.1
build_runner: ^2.3.3
2. 初始化 Hive
在 main.dart
文件中初始化 Hive:
dart
复制
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'book_provider.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
Hive.registerAdapter(BookAdapter()); // 注册书籍模型适配器
await Hive.openBox<Book>('books'); // 打开书籍数据存储的盒子
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => BookProvider(),
child: MaterialApp(
title: 'Book CRUD Demo',
home: BookListScreen(),
),
);
}
}
3. 定义书籍模型
创建 book_model.dart
文件:
dart
复制
import 'package:hive/hive.dart';
part 'book_model.g.dart';
@HiveType(typeId: 0)
class Book extends HiveObject {
@HiveField(0)
String title;
@HiveField(1)
String author;
Book({required this.title, required this.author});
}
运行以下命令生成适配器代码:
bash
复制
flutter packages pub run build_runner build
4. 创建书籍数据 Provider
创建 book_provider.dart
文件:
dart
复制
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'book_model.dart';
class BookProvider with ChangeNotifier {
final Box<Book> _bookBox = Hive.box<Book>('books');
List<Book> get books => _bookBox.values.toList();
void addBook(Book book) {
_bookBox.add(book);
notifyListeners();
}
void updateBook(Book book) {
book.save();
notifyListeners();
}
void deleteBook(Book book) {
book.delete();
notifyListeners();
}
}
5. 创建书籍列表界面
创建 book_list_screen.dart
文件:
dart
复制
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'book_form_screen.dart';
import 'book_provider.dart';
class BookListScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Books'),
),
body: Consumer<BookProvider>(
builder: (context, provider, child) {
return ListView.builder(
itemCount: provider.books.length,
itemBuilder: (context, index) {
Book book = provider.books[index];
return ListTile(
title: Text(book.title),
subtitle: Text(book.author),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BookFormScreen(book: book),
),
);
},
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
provider.deleteBook(book);
},
),
);
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => BookFormScreen()),
);
},
child: Icon(Icons.add),
),
);
}
}
6. 创建书籍表单界面
创建 book_form_screen.dart
文件:
dart
复制
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'book_model.dart';
import 'book_provider.dart';
class BookFormScreen extends StatelessWidget {
final Book? book;
final _formKey = GlobalKey<FormState>();
final _titleController = TextEditingController();
final _authorController = TextEditingController();
BookFormScreen({this.book});
@override
void initState() {
super.initState();
if (book != null) {
_titleController.text = book!.title;
_authorController.text = book!.author;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(book != null ? 'Edit Book' : 'Add Book'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _titleController,
decoration: InputDecoration(labelText: 'Title'),
validator: (value) => value!.isEmpty ? 'Please enter a title' : null,
),
TextFormField(
controller: _authorController,
decoration: InputDecoration(labelText: 'Author'),
validator: (value) => value!.isEmpty ? 'Please enter an author' : null,
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
if (book != null) {
book!.title = _titleController.text;
book!.author = _authorController.text;
Provider.of<BookProvider>(context, listen: false).updateBook(book!);
} else {
Provider.of<BookProvider>(context, listen: false).addBook(
Book(title: _titleController.text, author: _authorController.text),
);
}
Navigator.pop(context);
}
},
child: Text(book != null ? 'Update' : 'Add'),
),
],
),
),
),
);
}
}
7. 运行应用
运行应用后,你可以通过点击 FloatingActionButton
添加书籍,点击书籍条目编辑书籍,点击删除图标删除书籍。所有数据都会保存在本地的 Hive 数据库中。
以上代码实现了一个完整的书籍 CRUD 应用,使用了 Hive 作为本地存储,通过 Provider 管理状态。
逐步解释一下上面的代码,更好地理解每个部分的作用。
1. 项目结构
-
main.dart
:应用的入口文件,负责初始化 Hive 数据库和 Provider,并设置应用的根页面。 -
book_model.dart
:定义了书籍模型(Book
类),并使用 Hive 的注解生成适配器代码。 -
book_provider.dart
:管理书籍数据的逻辑,通过 Provider 提供数据的增删改查功能。 -
book_list_screen.dart
:显示书籍列表的界面,用户可以查看书籍列表、编辑书籍或删除书籍。 -
book_form_screen.dart
:添加或编辑书籍的表单界面,用户可以输入书籍的标题和作者。
2. 代码详细解释
main.dart
dart
复制
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'book_provider.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized(); // 确保 Flutter 的初始化完成
await Hive.initFlutter(); // 初始化 Hive 数据库
Hive.registerAdapter(BookAdapter()); // 注册书籍模型的适配器
await Hive.openBox<Book>('books'); // 打开一个名为 'books' 的 Hive 数据盒
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => BookProvider(), // 创建一个 BookProvider 实例
child: MaterialApp(
title: 'Book CRUD Demo',
home: BookListScreen(), // 设置应用的根页面为书籍列表界面
),
);
}
}
-
WidgetsFlutterBinding.ensureInitialized()
:确保 Flutter 的初始化完成,这是在使用异步操作时的必要步骤。 -
Hive.initFlutter()
:初始化 Hive 数据库,使其可以在 Flutter 应用中使用。 -
Hive.registerAdapter(BookAdapter())
:注册一个适配器,让 Hive 可以存储自定义的Book
类型数据。 -
Hive.openBox<Book>('books')
:打开一个名为books
的 Hive 数据盒,用于存储书籍数据。 -
ChangeNotifierProvider
:通过 Provider 包,将BookProvider
提供给整个应用,以便在不同页面之间共享书籍数据。
book_model.dart
dart
复制
import 'package:hive/hive.dart';
part 'book_model.g.dart'; // 引入生成的适配器代码
@HiveType(typeId: 0) // 定义 Hive 类型 ID
class Book extends HiveObject {
@HiveField(0) // 定义字段 ID
String title;
@HiveField(1) // 定义字段 ID
String author;
Book({required this.title, required this.author});
}
-
@HiveType(typeId: 0)
:为Book
类指定一个唯一的类型 ID,Hive 使用这个 ID 来存储和检索数据。 -
@HiveField(0)
和@HiveField(1)
:分别为title
和author
字段指定字段 ID,Hive 使用这些 ID 来序列化和反序列化数据。 -
HiveObject
:继承自HiveObject
,这样Book
类就可以直接与 Hive 数据库交互(例如保存和删除)。
book_provider.dart
dart
复制
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'book_model.dart';
class BookProvider with ChangeNotifier {
final Box<Book> _bookBox = Hive.box<Book>('books'); // 打开书籍数据盒
List<Book> get books => _bookBox.values.toList(); // 获取所有书籍数据
void addBook(Book book) {
_bookBox.add(book); // 添加书籍到数据盒
notifyListeners(); // 通知监听者数据已更新
}
void updateBook(Book book) {
book.save(); // 更新书籍数据
notifyListeners(); // 通知监听者数据已更新
}
void deleteBook(Book book) {
book.delete(); // 删除书籍
notifyListeners(); // 通知监听者数据已更新
}
}
-
Hive.box<Book>('books')
:获取一个名为books
的 Hive 数据盒,用于存储Book
类型的数据。 -
_bookBox.values.toList()
:将数据盒中的所有书籍数据转换为一个列表。 -
addBook
:将一个新的书籍对象添加到数据盒中,并通过notifyListeners()
通知所有监听者数据已更新。 -
updateBook
:调用Book
对象的save()
方法,更新数据盒中的书籍数据。 -
deleteBook
:调用Book
对象的delete()
方法,从数据盒中删除书籍数据。
book_list_screen.dart
dart
复制
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'book_form_screen.dart';
import 'book_provider.dart';
class BookListScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Books'),
),
body: Consumer<BookProvider>(
builder: (context, provider, child) {
return ListView.builder(
itemCount: provider.books.length, // 获取书籍列表的长度
itemBuilder: (context, index) {
Book book = provider.books[index]; // 获取当前书籍
return ListTile(
title: Text(book.title), // 显示书籍标题
subtitle: Text(book.author), // 显示书籍作者
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BookFormScreen(book: book), // 编辑书籍
),
);
},
trailing: IconButton(
icon: Icon(Icons.delete), // 删除按钮
onPressed: () {
provider.deleteBook(book); // 删除书籍
},
),
);
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => BookFormScreen()), // 添加书籍
);
},
child: Icon(Icons.add),
),
);
}
}
-
Consumer<BookProvider>
:使用 Provider 包的Consumer
小部件来监听BookProvider
的变化,并根据书籍数据更新 UI。 -
ListView.builder
:构建一个书籍列表,动态显示所有书籍的标题和作者。 -
onTap
:点击书籍条目时,跳转到BookFormScreen
页面,用于编辑书籍。 -
IconButton
:点击删除按钮时,调用BookProvider
的deleteBook
方法删除书籍。 -
FloatingActionButton
:点击添加按钮时,跳转到BookFormScreen
页面,用于添加新书籍。
book_form_screen.dart
dart
复制
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'book_model.dart';
import 'book_provider.dart';
class BookFormScreen extends StatelessWidget {
final Book? book; // 当前编辑的书籍对象(如果有的话)
final _formKey = GlobalKey<FormState>(); // 表单状态管理
final _titleController = TextEditingController(); // 标题输入框控制器
final _authorController = TextEditingController(); // 作者输入框控制器
BookFormScreen({this.book});
@override
void initState() {
super.initState();
if (book != null) {
_titleController.text = book!.title; // 如果是编辑书籍,初始化标题输入框
_authorController.text = book!.author; // 初始化作者输入框
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(book != null ? 'Edit Book' : 'Add Book'), // 标题根据操作显示
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _titleController, // 绑定标题输入框控制器
decoration: InputDecoration(labelText: 'Title'), // 输入框提示
validator: (value) => value!.isEmpty ? 'Please enter a title' : null, // 验证标题是否为空
),
TextFormField(
controller: _authorController, // 绑定作者输入框控制器
decoration: InputDecoration(labelText: 'Author'), // 输入框提示
validator: (value) => value!.isEmpty ? 'Please enter an author' : null, // 验证作者是否为空
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) { // 验证表单是否通过
if (book != null) {
book!.title = _titleController.text; // 更新书籍标题
book!.author = _authorController.text; // 更新
ChangeNotifierProvider(详细教程和例子
ChangeNotifierProvider
是 Flutter 中 provider
包提供的一个工具,用于在应用中管理状态。它结合了 ChangeNotifier
和 Provider
的功能,允许开发者在多个组件之间共享和管理状态,并在状态发生变化时自动更新 UI。
ChangeNotifierProvider 的基本使用方法
ChangeNotifierProvider
的使用通常包括以下步骤:
-
创建一个状态管理类:继承自
ChangeNotifier
,并在类中定义状态和操作状态的方法。 -
在应用顶层提供状态:使用
ChangeNotifierProvider
将状态管理类的实例注入到应用中。 -
在组件中访问和使用状态:通过
Provider.of
、Consumer
或context.watch
等方式访问状态。
示例:计数器应用
以下是一个简单的计数器应用示例,展示如何使用 ChangeNotifierProvider
管理状态。
1. 添加依赖
在 pubspec.yaml
文件中添加 provider
依赖:
yaml
复制
dependencies:
flutter:
sdk: flutter
provider: ^6.0.0
然后运行 flutter pub get
。
2. 创建状态管理类
创建一个 Counter
类,继承自 ChangeNotifier
,用于管理计数器的状态:
dart
复制
import 'package:flutter/material.dart';
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // 通知监听者状态已更改
}
}
notifyListeners()
方法用于通知所有监听该状态的组件,触发它们的重建。
3. 在应用顶层提供状态
在 main.dart
文件中,使用 ChangeNotifierProvider
将 Counter
实例注入到应用中:
dart
复制
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter.dart'; // 假设 Counter 类定义在这个文件中
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => Counter(), // 创建 Counter 实例
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: CounterScreen(),
);
}
}
create
参数用于创建 ChangeNotifier
的实例。
4. 在组件中访问和使用状态
在组件中,使用 Consumer
或 context.watch
等方式访问状态:
dart
复制
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter.dart';
class CounterScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter ChangeNotifier Demo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Consumer<Counter>(
builder: (context, counter, child) => Text(
'${counter.count}',
style: Theme.of(context).textTheme.headline4,
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
final counter = Provider.of<Counter>(context, listen: false);
counter.increment();
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
-
Consumer<Counter>
:用于访问Counter
实例的状态,并在状态变化时自动更新 UI。 -
Provider.of<Counter>(context, listen: false)
:用于访问Counter
实例的方法,listen: false
表示不监听状态变化。
多个 Provider 的使用
如果需要管理多个状态,可以使用 MultiProvider
:
dart
复制
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Counter()),
ChangeNotifierProvider(create: (_) => AnotherModel()),
],
child: MyApp(),
),
);
}
总结
ChangeNotifierProvider
是一种简单且强大的状态管理方式,特别适用于需要在多个组件之间共享和管理状态的场景。通过结合 ChangeNotifier
和 Provider
,可以轻松实现状态的全局共享和动态更新。