Flutter离线优先功能插件brick_offline_first的使用

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

Flutter离线优先功能插件brick_offline_first的使用

概述

brick_offline_first 是一个结合了SQLite和远程数据源(如Firebase或REST)的统一存储库。它允许应用在在线或离线状态下都能正常运行。

图解

OfflineFirst#get

💡 可以通过 policy: 参数改变默认行为,例如:get<Person>(policy: OfflineFirstUpsertPolicy.localOnly)。这适用于 deletegetgetBatchedupsert 方法。

字段

@OfflineFirst(where:)

使用唯一标识符,where: 可以连接多个数据源。它声明为本地数据源(键)和远程数据源(值)之间的映射。这对于远程数据源只包含关联的唯一标识符(如 "id": 1)时特别有用。

示例:

[@OfflineFirst](/user/OfflineFirst)(where: {'id' : "data['assoc']['id']"})
final Assoc assoc;

[@OfflineFirst](/user/OfflineFirst)(where: {'id' : "data['assoc']['ids']"})
final List<Assoc> assoc;

.fromJson 和 .toJson

当存储原始数据比存储关联更优时,可以使用工厂方法 .fromJson 或方法 .toJson

import 'dart:convert';

class Weight {
  final int size;
  final String unit;

  Weight(this.size, this.unit);

  factory Weight.fromJson(Map<String, dynamic> data) {
    if (data == null || data.isEmpty) return null;

    final size = double.parse(data.keys.first.toString() ?? '0');
    return Weight(size, data.values.first);
  }

  Map<String, dynamic> toJson() => {'size': size, 'unit': unit};
}

枚举

Dart的增强枚举也可以用于自定义序列化和反序列化工作:

enum Direction {
  up,
  down;

  factory Direction.fromRest(String direction) => direction == up.name ? up : down;

  int toSqlite() => Direction.values.indexOf(this);
}

OfflineFirstSerdes

.fromJson.toJson 过于复杂时,可以使用特定于提供者的工厂或函数:

import 'dart:convert';

class Weight extends OfflineFirstSerdes<Map<int, String>, String> {
  final int size;
  final String unit;

  Weight(this.size, this.unit);

  factory Weight.fromRest(Map<String, dynamic> data) {
    if (data == null || data.isEmpty) return null;

    final size = double.parse(data.keys.first.toString() ?? '0');
    return Weight(size, data.values.first);
  }

  factory Weight.fromSqlite(String data) => Weight.fromRest(jsonDecode(data));

  Map<String, dynamic> toRest() => {size: unit};
  String toSqlite() => jsonEncode(toRest());
}

Mixins

一些常用功能不在Brick核心中,但可以通过Mixins实现:

import 'package:brick_offline_first/mixins.dart';

class MyRepository extends OfflineFirstRepository with DeleteAllMixin {}

RequestSqliteCacheManager

所有对远程数据源的请求首先通过一个队列处理,该队列跟踪未成功的请求,并在应用重新连接时重试这些请求:

final client = RestOfflineQueueClient(
  restProvider.client, // or http.Client()
  "OfflineQueue",
);

OfflineQueue逻辑流

⚠️ 队列忽略不是 DELETEPATCHPOSTPUT 的请求。

示例Demo

以下是一个完整的Flutter应用示例,展示如何使用 brick_offline_first

import 'package:flutter/material.dart';
import 'package:brick_offline_first/brick_offline_first.dart';
import 'package:brick_rest/rest.dart';
import 'package:brick_sqlite/sqlite.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Offline First Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            // TODO: Add your offline-first logic here
          ],
        ),
      ),
    );
  }
}

// Define your models and repository here
class Person {
  final String id;
  final String name;

  Person(this.id, this.name);

  factory Person.fromJson(Map<String, dynamic> json) => Person(
        json['id'],
        json['name'],
      );

  Map<String, dynamic> toJson() => {
        'id': id,
        'name': name,
      };
}

class MyRepository extends OfflineFirstRepository {
  MyRepository(RestProvider rest, SqliteProvider sqlite)
      : super(rest, sqlite);
}

以上示例展示了如何设置一个基本的Flutter应用,并使用 brick_offline_first 实现离线优先的功能。根据实际需求,您可以进一步扩展和定制此示例。


更多关于Flutter离线优先功能插件brick_offline_first的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter离线优先功能插件brick_offline_first的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何使用Flutter插件brick_offline_first来实现离线优先功能的代码示例。这个插件旨在帮助开发者轻松实现离线数据缓存和同步功能。

首先,确保你已经在pubspec.yaml文件中添加了brick_offline_first依赖:

dependencies:
  flutter:
    sdk: flutter
  brick_offline_first: ^最新版本号 # 请替换为实际最新版本号

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

接下来是一个简单的示例,展示如何使用brick_offline_first插件来缓存和同步数据。

1. 初始化离线存储

首先,你需要初始化离线存储。在Flutter应用的入口文件(通常是main.dart)中,进行如下设置:

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

void main() {
  // 初始化离线存储
  OfflineFirst.initialize(
    storageDirectory: 'offline_data', // 指定离线数据存储目录
  );

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Offline First Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

2. 创建数据模型

创建一个简单的数据模型,例如一个Todo类:

import 'package:json_annotation/json_annotation.dart';

part 'todo.g.dart';

@JsonSerializable()
class Todo {
  final String id;
  final String title;
  final String description;
  final bool completed;

  Todo({required this.id, required this.title, required this.description, required this.completed});

  factory Todo.fromJson(Map<String, dynamic> json) => _$TodoFromJson(json);
  Map<String, dynamic> toJson() => _$TodoToJson(this);
}

生成todo.g.dart文件:

flutter pub run build_runner build

3. 使用离线存储

在你的主页面或其他组件中,使用OfflineFirst的API来缓存和同步数据。例如,假设你有一个获取待办事项列表的方法:

import 'package:flutter/material.dart';
import 'package:brick_offline_first/brick_offline_first.dart';
import 'todo.dart';

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<Todo> todos = [];

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

  Future<void> _fetchTodos() async {
    // 尝试从网络获取数据(这里模拟一个网络请求)
    List<Todo> networkTodos = await _fetchTodosFromNetwork();

    // 将数据保存到离线存储
    await OfflineFirst.save('todos', networkTodos);

    // 从离线存储读取数据(确保即使在离线时也能获取数据)
    List<Todo> cachedTodos = await OfflineFirst.load<List<Todo>>('todos');

    // 更新UI
    setState(() {
      todos = cachedTodos ?? [];
    });
  }

  Future<List<Todo>> _fetchTodosFromNetwork() async {
    // 这里模拟一个网络请求,实际中应替换为真实的API调用
    return Future.delayed(Duration(seconds: 2), () {
      return [
        Todo(id: '1', title: 'Todo 1', description: 'Description 1', completed: false),
        Todo(id: '2', title: 'Todo 2', description: 'Description 2', completed: true),
      ];
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Offline First Demo'),
      ),
      body: ListView.builder(
        itemCount: todos.length,
        itemBuilder: (context, index) {
          Todo todo = todos[index];
          return ListTile(
            title: Text(todo.title),
            subtitle: Text(todo.description),
            trailing: Checkbox(
              value: todo.completed,
              onChanged: (value) {
                // 更新单个Todo项(这里简单处理,不保存到网络或离线存储)
                setState(() {
                  todos[index] = todo.copyWith(completed: value!);
                });
              },
            ),
          );
        },
      ),
    );
  }
}

在这个示例中,我们模拟了一个从网络获取待办事项列表的过程,并将数据保存到离线存储中。然后,我们从离线存储中读取数据以更新UI。这样,即使在没有网络连接的情况下,用户也能看到之前缓存的数据。

请注意,这只是一个基本示例。在实际应用中,你可能需要处理更多的边缘情况,例如冲突解决、数据同步策略等。brick_offline_first插件提供了丰富的API来处理这些复杂场景,你可以参考其官方文档以获取更多信息和高级用法。

回到顶部