Flutter本地数据存储与缓存管理插件hive_cache_manager的使用

发布于 1周前 作者 caililin 来自 Flutter

Flutter本地数据存储与缓存管理插件hive_cache_manager的使用

特性

  • 🤩️ 简单快速的编码
  • ❤️ 使用hive进行缓存
  • 🗂 使用flutter_secure_storage来存储加密密钥
  • 📸 使用ValueListenableBuilder来监听数据变化
  • 🥳 使存储自定义模型变得容易

通过使用此包,你无需为每个模型编写单独的Hive相关代码。该包提供了一个通用的缓存管理器类来处理Hive箱操作。

开始使用

在你的pubspec.yaml文件中添加此包并运行pub get

dependencies:
  hive_cache_manager: latest

引入库

import 'package:hive_cache_manager/hive_cache_manager.dart';

1. 创建自定义模型

假设我们想要存储关于书籍的信息。我们可以将 Dart 文件命名为book.dart

class Book {
   String isbn;
   String title;
   String author;
   int year;

   //构造函数
   Book({required this.title, required this.author,
     required this.year, required this.isbn});
}

2. 使用hive注解并定义类型适配器文件的名称

这些步骤对于使用hive包是必要的。

  • 使用@HiveType@HiveField注解。
  • 在模型文件顶部添加part 'model_file_name.g.dart';
  • (model_file_name 必须与你的模型文件名完全匹配)
part 'book.g.dart';

@HiveType(typeId: 1)
class Book {
   @HiveField(0)
   String isbn;
   
   @HiveField(1)
   String title;
   
   @HiveField(2)
   String author;
   
   @HiveField(3)
   int year;

   //构造函数
   Book({required this.title, required this.author,
     required this.year, required this.isbn});
}

3. 生成类型适配器文件

在你的pubspec.yaml文件中添加build_runner包作为开发依赖项:

dev_dependencies:
  #代码生成
  build_runner: latest

然后在终端中运行以下命令以生成Book类的类型适配器。这一步也是使用hive包所必需的。

flutter packages pub run build_runner build

这将在你的模型文件旁边创建model_file_name.g.dart文件。

👍 不需要在你的pubspec.yaml中添加hive_generator包。hive_cache_manager已经包含了这个包。只需运行上述命令即可。

4. 扩展你的模型从IHiveModel

  • IHiveModel是hive_cache_manager中的一个特殊抽象类。
  • 当从这个类扩展时,你的模型必须重写"mapKey"计算属性。
  • 如果你想用模型特定的键存储对象,可以使用这个属性来设置在保存模型对象到Hive箱时使用的键。
    • 例如,我们可以选择ISBN号作为Hive箱中键值对的键。
import 'package:hive_cache_manager/hive_cache_manager.dart';

@HiveType(typeId: 1)
class Book extends IHiveModel{
  @HiveField(0)
  String isbn;

  @HiveField(1)
  String title;

  @HiveField(2)
  String author;

  @HiveField(3)
  int year;

  //构造函数
  Book({required this.title, required this.author,
    required this.year, required this.isbn});

  //定义你的唯一键
  @override
  String get mapKey => isbn;
}
  • 另外,你可以使用你的属性组合或任何字符串。
@override
String get mapKey => "$isbn - $title";
  • 如果你想用自动递增的键存储你的对象,你可以返回一个空字符串(或任何字符串)。在这种情况下,这个属性将不会被使用。所以,你返回什么在这个mapKey中都不重要。
@override
String get mapKey => "";

5. 为你的模型创建一个缓存管理器类

  • ICacheManager<T extends IHiveModel>是hive_cache_manager中的一个特殊抽象类。
  • 为了处理Hive箱操作,你需要为你的模型创建一个管理器类。
  • 因为我们已经从IHiveModel扩展了我们的Book类,我们可以使用它代替T。
  • 当从ICacheManager扩展时,registerAdapters方法必须被重写。
    • 在此方法中注册你生成的类型适配器类的对象。
import 'package:hive_cache_manager/hive_cache_manager.dart';
import '../model/book.dart';
import 'package:hive/hive.dart';

class BookCacheManager extends ICacheManager<Book>{
  BookCacheManager(super.boxName);
  
  @override
  void registerAdapters() {
    if(!Hive.isAdapterRegistered(1)){ //1是Book类的type id
      Hive.registerAdapter(BookAdapter()); // 如果未注册,这里注册生成的BookAdapter。
    }
  }
}
  • 🎊 现在,BookCacheManager类可以处理所有如放入或删除Book对象等箱操作。
  • 🎉 BookCacheManager还提供了一个ValueListenableBuilder,它连接到你放入Book对象的Hive箱。这样,你可以监听箱的变化而无需使用setState((){})。

6. 初始化Hive后使用

  • 这一步也是使用hive包所必需的。
  • 你可以在main中初始化它,然后在runApp之前调用。
import 'package:hive_flutter/hive_flutter.dart';

void main() async{
   await Hive.initFlutter("hive_db");
   
   runApp(const MyApp());
}

7. 创建一个管理器对象并显式调用其init方法

  • init方法运行BookCacheManager的registerAdapters()方法,然后打开一个可以存储Book对象的Hive箱。
  • 如果isEncrypted参数为true,则它会创建一个加密的箱,而无需你编写任何加密或flutter_secure_storage相关的代码。
  • 如果你不希望加密数据,请使用isEncrypted: false
BookCacheManager bookCacheManager = BookCacheManager("books_hive_box");

// 如果你想要一个加密的箱,传入true
await bookCacheManager.init(isEncrypted: true);

8. 创建一些模型对象

Book firstBook = Book(
   title: 'Lord of The Rings - The Fellowship of the Ring',
   author: 'J. R. R. Tolkien',
   year: 1954,
   isbn: '9780618260515',
);

Book secondBook = Book(
   title: 'Lord of The Rings - The Two Towers',
   author: 'J. R. R. Tolkien',
   year: 1954,
   isbn: '9780261102361',
);

Book thirdBook = Book(
   title: 'Lord of The Rings - The Return of the King',
   author: 'J. R. R. Tolkien',
   year: 1955,
   isbn: '9780007136575',
);
  • 🙋🏼‍♀️ 有任何其他《魔戒》粉丝吗?

9. 通过缓存管理器在Hive箱上执行任何操作

  • addItemaddItems方法使用自动递增的键添加值。
  • putItemputItems方法使用模型特定的键(使用mapKey属性)添加值。

放入示例:

await bookCacheManager.putItem(item: firstBook);

await bookCacheManager.putItems(items: [firstBook, secondBook, thirdBook]);

// 使用mapKey属性访问一个项目
await bookCacheManager.removeItem(key: secondBook.mapKey);

await bookCacheManager.removeItems(keys: [firstBook.mapKey, thirdBook.mapKey]);

await bookCacheManager.clearAll();

await bookCacheManager.closeBox();

print(bookCacheManager.getItem(key: thirdBook.mapKey)?.year);

print(bookCacheManager.getValues().length);

print(bookCacheManager.hiveBoxLength);

print(bookCacheManager.isHiveBoxOpen);

print(bookCacheManager.isHiveBoxEmpty);

print(bookCacheManager.isHiveBoxNotEmpty);

添加示例:

List<Book> books = [firstBook, secondBook, thirdBook];
await bookCacheManager.addItems(items: books);
  
print("${bookCacheManager.getValues().first.isbn} 等于 9780618260515");

// 从Hive箱中删除名为 "thirdBook" 的对象
// 由于对象是用自动递增的键添加的,"thirdBook" 的键应该是2(第一个项目的键是0)
await bookCacheManager.removeItem(key: 2);

// 读取从0到1(包括1)的键的值
Iterable<Book> otherBooks = bookCacheManager.getValuesBetween(startKey: 0, endKey: 1);
  
// 将剩余的模型对象放入可迭代对象
Iterable<Book> remainingBooks = [firstBook, secondBook];

// "otherBooks" 和 "remainingBooks" 应该相等
// 覆盖Book类中的toString()方法以便打印漂亮的可迭代对象。
print("$otherBooks 等于 $remainingBooks");

10. 在UI上显示数据

UI屏幕录制

以下是Example项目的屏幕录制。 App Bar上的 “+” 按钮向Hive箱添加3个Book对象。 借助ValueListenableBuilder,我们可以监听箱的变化。 Scaffold的body中使用三种不同的方式展示了箱中的数据。

  • 第一个列表显示了箱中的所有数据。
  • 第二个列表按键过滤(显示具有 “9780618260515” 和 “9780007136575” 键的书籍)。
  • 第三个列表按模型类的属性过滤(仅显示年份信息为 “1955” 的书籍)。

✔️ 从一个列表中删除一个项目会被立即识别,并且更改会在所有列表中反映出来。

如何使用ValueListenableBuilder

通过缓存管理器的getValueListenableBuilder方法访问ValueListenableBuilder。

bookCacheManager.getValueListenableBuilder(
   buildLayout: buildBooksLayout,
   keys: filterKeys,
)

此方法需要两个参数。第一个(buildLayout)是一个使用正在监听的数据的函数。以下是buildLayout参数的类型:

Widget Function(List<T> data) buildLayout

在示例项目中,它实现如下:

Widget buildBooksLayout(List<Book> books) {
    // 在此处消费数据
    // 在UI上显示数据并定义你的业务逻辑
    return books.isEmpty
        ? const Center(child: Text("没有数据显示"))
        : buildListView(books);
}

Widget buildListView(List<Book> books){
    return ListView.builder(
      itemCount: books.length,
      itemBuilder: (context, index) {
        final item = books.elementAt(index);
        return  _BookCard(book: item); 
      },
    );
}

_BookCard 是一个定义Book对象UI布局的状态小部件。它使用Card和ListTile小部件。你可以创建任何你喜欢的设计/布局。查看GitHub上的 _BookCard 小部件以查看代码。

第二个参数控制按键过滤。这是一个可选参数。如果给定,箱中的数据将按给定的键过滤。只有过滤后的数据才会传递给buildLayout函数。

显示箱中的所有数据

不提供任何键以显示箱中的所有数据。

Container(
  child: bookCacheManager.getValueListenableBuilder(
      buildLayout: buildBooksLayout,
  ),
)

过滤特定书籍并显示它们

通过提供keys参数按键过滤。

Container(
  child: bookCacheManager.getValueListenableBuilder(
      buildLayout: buildBooksLayout, 
      keys: ["9780618260515", "9780007136575"], //《魔戒》第一部《护戒使者》和第三部《王者归来》的ISBN
  ),
)

或者,在自定义的ValueListenableBuilder小部件中创建自己的过滤器

使用缓存管理器的getListenable方法获取ValueListenable。在构建方法中过滤或排序你的数据。

Container(
  child: ValueListenableBuilder(
      valueListenable: bookCacheManager.getListenable(),
      builder: (context, box, widget) {
          List<Book> data = [];

          for (Book book in bookCacheManager.getValues()) {
          // 过滤年份信息为1995的书籍
          // 根据需要更改此过滤器
              if (book.year == 1955) {
                  data.add(book);
              }
          }
          // 你也可以在这里对数据进行排序

          return buildLayout(data);
      },
  ),
)

额外信息

  • 此包依赖于flutter_secure_storage包以存储加密密钥。确保为所有平台(例如Web和Linux)进行所有必要的配置。
  • 目前,flutter_secure_storage强制要求minSdkVersion大于等于18。
  • 在使用flutter_secure_storage时,可能会遇到compileSdkVersion不兼容问题(它要求minSdkVersion >= 33)。
    • 在你的gradle文件(/android/app/build.gradle)中相应地更改这些属性。对于Flutter 2.8或更高版本,请执行以下步骤:

      1. 在你的local.properties文件(/android/local.properties)中添加以下行:
      flutter.compileSdkVersion=33
      flutter.minSdkVersion=18
      
      1. 在你的gradle文件中引用新添加的版本号:
      compileSdkVersion localProperties.getProperty('flutter.compileSdkVersion').toInteger()
        
      minSdkVersion localProperties.getProperty('flutter.minSdkVersion').toInteger()
      

更多关于Flutter本地数据存储与缓存管理插件hive_cache_manager的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter本地数据存储与缓存管理插件hive_cache_manager的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter应用中使用hive_cache_manager插件进行本地数据存储与缓存管理的代码示例。hive_cache_manager结合了Hive的高性能本地存储和缓存管理功能,非常适合需要高效本地存储和缓存策略的应用。

首先,确保在pubspec.yaml文件中添加依赖:

dependencies:
  flutter:
    sdk: flutter
  hive: ^2.0.4  # 请检查最新版本
  hive_flutter: ^1.0.0  # 如果你需要在Flutter中使用Hive的Widget
  hive_cache_manager: ^0.3.0  # 请检查最新版本

然后,运行flutter pub get来安装这些依赖。

初始化Hive和CacheManager

在你的应用入口文件(通常是main.dart)中,初始化Hive和CacheManager:

import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:hive_cache_manager/hive_cache_manager.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // 初始化Hive
  await Hive.initFlutter();

  // 打开一个box用于存储数据
  var box = await Hive.openBox('myBox');

  // 初始化CacheManager,配置Hive存储
  CacheManager cacheManager = HiveCacheManager(
    config: CacheConfig(
      store: box,
      maxAge: const Duration(days: 7), // 缓存数据的最大存活时间
      keyBuilder: (url) => url.hashCode.toString(), // 用于生成缓存键的策略
    ),
  );

  runApp(MyApp(cacheManager: cacheManager));
}

class MyApp extends StatelessWidget {
  final CacheManager cacheManager;

  MyApp({required this.cacheManager});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('HiveCacheManager Demo')),
        body: Center(
          child: MyHomePage(cacheManager: cacheManager),
        ),
      ),
    );
  }
}

使用CacheManager获取和缓存数据

在你的主页组件中,你可以使用CacheManager来获取和缓存网络数据:

import 'package:flutter/material.dart';
import 'package:hive_cache_manager/hive_cache_manager.dart';

class MyHomePage extends StatefulWidget {
  final CacheManager cacheManager;

  MyHomePage({required this.cacheManager});

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Future<String?>? _futureData;

  @override
  void initState() {
    super.initState();
    _fetchData();
  }

  Future<void> _fetchData() async {
    String url = 'https://jsonplaceholder.typicode.com/posts/1'; // 示例API
    try {
      // 使用CacheManager获取数据,如果缓存中有数据则直接返回缓存
      var response = await widget.cacheManager.getFile(url);
      String data = await response.readAsString();
      setState(() {
        _futureData = Future.value(data);
      });
    } catch (error) {
      setState(() {
        _futureData = Future.error(error);
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<String?>(
      future: _futureData,
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.done) {
          if (snapshot.hasError) {
            return Text('Error: ${snapshot.error}');
          } else if (snapshot.hasData) {
            return Text('Data: ${snapshot.data!}');
          }
        }
        return CircularProgressIndicator();
      },
    );
  }
}

总结

上面的代码展示了如何在Flutter应用中使用hive_cache_manager进行本地数据存储与缓存管理。首先,我们初始化了Hive和CacheManager,然后在组件中使用CacheManager来获取和缓存网络数据。这样,应用可以高效地管理本地缓存,提高用户体验。

请确保在实际应用中根据需求调整缓存配置,如缓存的过期时间、缓存键的生成策略等。

回到顶部