搜尋套件、儲存庫與 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 項

原始碼儲存庫

使用此套件的專案

暫無相關資料。