搜索插件、代码仓与 APP 分类

插件详情v2.0.0

flutter_data

在 Flutter 中处理持久化数据模型的无缝方式。受 Ember Data 和 ActiveRecord 的启发。

AndroidiOSmacOSWindowsLinux
近 30 天下载
1020
喜欢数
373
Pub 分数
130

安装插件

flutter pub add flutter_data

插件介绍

基于插件公开信息、社区数据与项目使用情况整理的选型参考。

Flutter Data 提供了一种简化的方法来管理 Flutter 应用程序中的持久化数据模型,其灵感来自 Ember Data 和 ActiveRecord。它通过为本地和远程数据源提供统一的 API,简化了 CRUD 操作、状态管理和跨设备的数据同步。该包与 Flutter 生态系统无缝集成,支持类型安全的模型、自动序列化和内置缓存。开发者可以轻松定义数据模型和关系,从而在无需样板代码的情况下实现高效的数据处理。

适合场景

  • 管理离线优先的数据工作流
  • 在本地存储和API之间同步数据
  • 构建可扩展的 CRUD 应用程序
  • 处理复杂的数据关系
  • 实现响应式数据更新

主要特点

  • 自动模型序列化
  • 内置的 SQLite 持久化存储
  • 响应式状态管理
  • 对 REST 和 GraphQL API 的支持
  • 类型安全的数据模型和查询

适合对象

  • 需要离线数据访问的应用程序
  • 使用 REST 或 GraphQL 后端的项目
  • 寻求减少样板代码的开发者
  • 采用类似MVC数据架构的团队
  • 具有复杂数据关系的应用程序

使用建议

  • 新用户的上手难度
  • 与成熟的替代方案相比,文档有限
  • 可能需要为自定义端点进行额外设置
  • 对于数据需求极少的简单应用来说并不理想
  • 依赖外部依赖项,例如 json_serializable

原始 README

以下为关联源码仓库的 README 原文快照,最新内容请访问源码仓库。

Flutter Data

tests codecov pub.dev license

Persistent reactive models in Flutter with zero boilerplate

Flutter Data is a local-first data framework with a customizable REST client and powerful model relationships, built on Riverpod.

Inspired by Ember Data and ActiveRecord.

Features

  • Adapters for all models 🚀
    • Default CRUD and custom remote endpoints
    • StateNotifier watcher APIs
  • Built for offline-first 🔌
    • SQLite3-based local storage at its core, with adapters for many other engines: Objectbox, Isar, etc (coming soon)
  • Intuitive APIs, effortless setup 💙
    • Truly configurable and composable via Dart mixins and codegen
    • Built-in Riverpod providers for all models
  • Exceptional relationship support ⚡️
    • Automatically synchronized, fully traversable relationship graph
    • Reactive relationships

👩🏾‍💻 Quick introduction

In Flutter Data, every model gets its default adapter. These adapters can be extended by mixing in custom adapters.

Annotate a model with @DataAdapter and pass a custom adapter:

@JsonSerializable()
@DataAdapter([MyJSONServerAdapter])
class User extends DataModel<User> {
  @override
  final int? id; // ID can be of any type
  final String name;
  User({this.id, required this.name});
  // `User.fromJson` and `toJson` optional
}

mixin MyJSONServerAdapter on RemoteAdapter<User> {
  @override
  String get baseUrl => "https://my-json-server.typicode.com/flutterdata/demo/";
}

After code-gen, Flutter Data will generate the resulting Adapter<User> which is accessible via Riverpod's ref.users or container.users.

@override
Widget build(BuildContext context, WidgetRef ref) {
  final state = ref.users.watchOne(1);
  if (state.isLoading) {
    return Center(child: const CircularProgressIndicator());
  }
  final user = state.model;
  return Text(user.name);
}

To update the user:

TextButton(
  onPressed: () => ref.users.save(User(id: 1, name: 'Updated')),
  child: Text('Update'),
),

ref.users.watchOne(1) will make a background HTTP request (to https://my-json-server.typicode.com/flutterdata/demo/users/1 in this case), deserialize data and listen for any further local changes to the user.

state is of type DataState which has loading, error and data substates.

In addition to the reactivity, models have ActiveRecord-style extension methods so the above becomes:

GestureDetector(
  onTap: () => User(id: 1, name: 'Updated').save(),
  child: Text('Update')
),

Compatibility

Fully compatible with the tools we know and love:

Flutter ✅ Or plain Dart. It does not require Flutter.
json_serializable ✅ Fully supported (but not required)
Riverpod ✅ Supported & automatically wired up
Classic JSON REST API ✅ Built-in support!
JSON:API ✅ Supported via external adapter
Firebase, Supabase, GraphQL ✅ Can be fully supported by writing custom adapters
Freezed ✅ Supported!
Flutter Web - TBD

📲 Apps using Flutter Data in production

logos

Integrations

  • Appwrite Offline. A Flutter Data adapter for Appwrite that provides offline support, real-time updates, and seamless integration with Flutter Data's powerful features.

🚨 BREAKING CHANGES IN 2.0

  • All methods are now directly on Adapter, there is no Repository, RemoteAdapter or LocalAdapter. Any method you are looking for is probably on Adapter, for example, findAll from LocalAdapter is now called findAllLocal
  • For initialization we no longer call the configure... method on the Riverpod overrides, we just do localStorageProvider.overrideWithValue and pass a LocalStorage instance; the actual initialization is done via initializeFlutterData which needs an adapter map. An adapterProvidersMap is conveniently code-generated and available on main.data.dart
  • Offline operations are still supported, but retries are not, it is up to clients to implement them

📚 Public API

Initialization

First you need to supply a local storage provider, via Riverpod configuration. A popular option for the base directory is using the path_provider package.

ProviderScope(
  overrides: [
    localStorageProvider.overrideWithValue(
      LocalStorage(
        baseDirFn: () async {
          return (await getApplicationSupportDirectory()).path;
        },
        busyTimeout: 5000,
        clear: LocalStorageClearStrategy.never,
      ),
    )
  ],
  // ...
),

And initialize like so:

return Scaffold(
  body: ref.watch(initializeFlutterData(adapterProvidersMap)).when(
    data: (_) => child,
    error: (e, _) => const Text('Error'),
    loading: () => const Center(child: CircularProgressIndicator()),
  ),
Adapters

Method names should be self explanatory. All of these methods have a reasonable default implementation.

Local storage

All models are identified by keys, that might be associated to an id (either self-assigned or fetched from a remote source).

Keys have the format model#5.

List<T> findAllLocal();

List<T> findManyLocal(Iterable<String> keys);

T? findOneLocal(String? key);

T? findOneLocalById(Object id);

bool exists(String key);

T saveLocal(T model, {bool notify = true});

Future<List<String>?> saveManyLocal(Iterable<DataModelMixin> models,
      {bool notify = true, bool async = true});

void deleteLocal(T model, {bool notify = true});

void deleteLocalById(Object id, {bool notify = true});

void deleteLocalByKeys(Iterable<String> keys, {bool notify = true});

Future<void> clearLocal({bool notify = true});

int get countLocal;

Set<String> get keys;
Remote & watchers
Future<List<T>> findAll({
    bool remote = true,
    bool background = false,
    Map<String, dynamic>? params,
    Map<String, String>? headers,
    bool syncLocal = false,
    OnSuccessAll<T>? onSuccess,
    OnErrorAll<T>? onError,
    DataRequestLabel? label,
  });

Future<T?> findOne(
    Object id, {
    bool remote = true,
    bool background = false,
    Map<String, dynamic>? params,
    Map<String, String>? headers,
    OnSuccessOne<T>? onSuccess,
    OnErrorOne<T>? onError,
    DataRequestLabel? label,
  });

Future<T> save(
    T model, {
    bool remote = true,
    Map<String, dynamic>? params,
    Map<String, String>? headers,
    OnSuccessOne<T>? onSuccess,
    OnErrorOne<T>? onError,
    DataRequestLabel? label,
  });

Future<T?> delete(
    Object model, {
    bool remote = true,
    Map<String, dynamic>? params,
    Map<String, String>? headers,
    OnSuccessOne<T>? onSuccess,
    OnErrorOne<T>? onError,
    DataRequestLabel? label,
  });

Set<OfflineOperation<T>> get offlineOperations;

DataState<List<T>> watchAll({
    bool remote = false,
    Map<String, dynamic>? params,
    Map<String, String>? headers,
    bool syncLocal = false,
    String? finder,
    DataRequestLabel? label,
  });

DataState<T?> watchOne(
    Object model, {
    bool remote = false,
    Map<String, dynamic>? params,
    Map<String, String>? headers,
    AlsoWatch<T>? alsoWatch,
    String? finder,
    DataRequestLabel? label,
  });

DataStateNotifier<List<T>> watchAllNotifier(
      {bool remote = false,
      Map<String, dynamic>? params,
      Map<String, String>? headers,
      bool syncLocal = false,
      String? finder,
      DataRequestLabel? label});

DataStateNotifier<T?> watchOneNotifier(Object model,
      {bool remote = false,
      Map<String, dynamic>? params,
      Map<String, String>? headers,
      AlsoWatch<T>? alsoWatch,
      String? finder,
      DataRequestLabel? label});

final coreNotifierThrottleDurationProvider;
Serialization
Future<Map<String, dynamic>> serialize(T model,
      {bool withRelationships = true});

Future<DeserializedData<T>> deserialize(Object? data,
      {String? key, bool async = true});

Future<DeserializedData<T>> deserializeAndSave(Object? data,
      {String? key, bool notify = true, bool ignoreReturn = false});

➕ Questions and collaborating

Please use Github to ask questions, open issues and send PRs. Thanks!

Tests can be run with: dart test

📝 License

See LICENSE.

所属分类

依赖

依赖清单 17 项

源码仓库

使用此插件的项目

暂无相关数据。