easy_state_core 0.1.0 copy "easy_state_core: ^0.1.0" to clipboard
easy_state_core: ^0.1.0 copied to clipboard

A universal network-request-driven state management framework. Works with GetX, Riverpod, Bloc, or any state management solution.

easy_state_core #

A universal network-request-driven state management framework for Flutter.

一个通用的网络请求驱动状态管理框架,适用于 Flutter。


Introduction (介绍) #

Core Philosophy: Define an abstract request interface, implement it, and the controller automatically handles the request lifecycle.

核心思想:定义一个抽象的请求接口,用户实现该接口后,控制器在生命周期中自动发起请求。

Key Features (核心特性) #

Feature Description
EasyRequest<T> Single value async request engine / 单值异步请求引擎
EasyPager<T> Pagination engine with loadMore / 分页引擎,支持加载更多
EasyStore<S> Pluggable state storage interface / 可插拔状态存储接口
Zero-overhead adapters Direct state injection, no Stream subscription needed / 直接状态注入,无需 Stream 订阅

Install (安装) #

Add to your pubspec.yaml:

pubspec.yaml 中添加:

dependencies:
  easy_state_core: ^0.1.0

For adapters (适配器):

dependencies:
  easy_state_getx: ^0.1.0      # GetX adapter
  easy_state_riverpod: ^0.1.0  # Riverpod adapter

Quick Start (快速上手) #

Single Value Request (单值请求) #

import 'package:easy_state_core/easy_state_core.dart';

// 1. Create engine with fetcher
final engine = EasyRequest<User>(
  fetcher: () => api.getUser(),
);

// 2. Listen to state changes
engine.stream.listen((state) {
  print('Status: ${state.status}');
  print('Data: ${state.data}');
  print('Is refreshing: ${state.isRefreshing}');
});

// 3. Trigger fetch
await engine.fetch();

// 4. Refresh (keeps old data visible)
await engine.refresh();

// 5. Retry on error
await engine.retry();

// 6. Dispose when done
engine.dispose();

Paginated Request (分页请求) #

final pager = EasyPager<Article>(
  fetcher: (page) => api.getArticles(page: page),
  pageSize: 20,
);

// First page
await pager.fetch();

// Load next page (appends items)
await pager.loadMore();

// Refresh (resets to page 1)
await pager.refresh();

// Access state
pager.stream.listen((state) {
  print('Items: ${state.items.length}');
  print('Page: ${state.page}');
  print('Has more: ${state.hasMore}');
  print('Is loading more: ${state.isLoadingMore}');
});

pager.dispose();

State Models (状态模型) #

EasyValueState<T> #

State model for single value requests.

单值请求的状态模型。

Field Type Description
status EasyStatus Current status / 当前状态
data T? The fetched data / 获取的数据
error Object? Error if failed / 错误信息
stackTrace StackTrace? Stack trace for debugging / 调试用堆栈
isRefreshing bool True during refresh (old data visible) / 刷新中(旧数据可见)

EasyPagedState<T> #

State model for paginated requests.

分页请求的状态模型。

Field Type Description
status EasyStatus Current status / 当前状态
items List<T> Accumulated items / 累积的数据列表
page int Current page (1-based) / 当前页码(从1开始)
pageSize int Items per page / 每页数量
hasMore bool More pages available / 是否有更多数据
isRefreshing bool Refreshing first page / 正在刷新首页
isLoadingMore bool Loading next page / 正在加载下一页
error Object? Fetch/refresh error / 首次加载或刷新错误
loadMoreError Object? Load more error / 加载更多错误

State Machine (状态机) #

EasyStatus Enum #

enum EasyStatus {
  idle,     // Initial state / 初始状态
  loading,  // Fetching data / 加载中
  success,  // Data loaded / 加载成功
  empty,    // No data / 数据为空
  error,    // Failed / 加载失败
}

EasyRequest State Transitions (单值请求状态流转) #

Mermaid Diagram:

stateDiagram-v2
    [*] --> idle
    idle --> loading: fetch()
    loading --> success: data received
    loading --> empty: empty data
    loading --> error: exception

    success --> loading: fetch()
    success --> success: refresh() [isRefreshing=true]

    error --> loading: retry()
    error --> error: refresh() fails [keeps error]

    note right of success: refresh() keeps old data visible

ASCII Diagram:

┌─────────────────────────────────────────────────────────────┐
│                    EasyRequest State Machine                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌──────┐  fetch()   ┌─────────┐  data    ┌─────────┐     │
│   │ idle │ ─────────► │ loading │ ───────► │ success │     │
│   └──────┘            └─────────┘          └────┬────┘     │
│                            │                    │          │
│                            │ empty         refresh()       │
│                            ▼              (isRefreshing)   │
│                       ┌─────────┐              │           │
│                       │  empty  │              │           │
│                       └─────────┘              ▼           │
│                            │            ┌───────────┐      │
│                            │ error      │ success + │      │
│                            ▼            │ refreshing│      │
│                       ┌─────────┐       └───────────┘      │
│                       │  error  │ ◄─── refresh fails       │
│                       └────┬────┘      (keeps old data)    │
│                            │                               │
│                       retry()                              │
│                            │                               │
│                            ▼                               │
│                       ┌─────────┐                          │
│                       │ loading │                          │
│                       └─────────┘                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

EasyPager State Transitions (分页请求状态流转) #

Mermaid Diagram:

stateDiagram-v2
    [*] --> idle
    idle --> loading: fetch()/refresh()
    loading --> success: items received
    loading --> empty: no items
    loading --> error: exception

    success --> success: loadMore() [isLoadingMore=true]
    success --> success: refresh() [isRefreshing=true]
    success --> success: loadMore fails [loadMoreError set]

    error --> loading: fetch()/refresh()

ASCII Diagram:

┌──────────────────────────────────────────────────────────────────┐
│                     EasyPager State Machine                      │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│   ┌──────┐  fetch()   ┌─────────┐  items   ┌─────────┐          │
│   │ idle │ ─────────► │ loading │ ───────► │ success │          │
│   └──────┘            └─────────┘          └────┬────┘          │
│                            │                    │               │
│                       empty/error          loadMore()           │
│                            │              (isLoadingMore)       │
│                            ▼                    │               │
│                    ┌──────────────┐             ▼               │
│                    │ empty/error  │      ┌────────────┐         │
│                    └──────────────┘      │  success + │         │
│                                          │ loadingMore│         │
│                                          └─────┬──────┘         │
│                                                │                │
│                                           success/fail          │
│                                                │                │
│                                                ▼                │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │ On loadMore fail: items preserved, loadMoreError set    │   │
│   │ On refresh fail with items: items preserved, error set  │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

Overlay Flags (覆盖层标志) #

Flag When Set Behavior
isRefreshing During refresh() Old data remains visible / 旧数据保持可见
isLoadingMore During loadMore() Existing items preserved / 现有数据保留

Adapters (适配器) #

Architecture (架构) #

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Fetcher   │────►│   Engine    │────►│  EasyStore  │
│  (your API) │     │ EasyRequest │     │ (interface) │
└─────────────┘     │  EasyPager  │     └──────┬──────┘
                    └─────────────┘            │
                                               ▼
                    ┌──────────────────────────────────────┐
                    │         Adapter Implementations       │
                    ├──────────────┬───────────────────────┤
                    │   RxStore    │  RiverpodStore (TBD)  │
                    │   (GetX)     │  BlocStore (TBD)      │
                    └──────────────┴───────────────────────┘

GetX Adapter (GetX 适配器) #

// 1. Single value controller
class UserController extends EasyGetxController<User> {
  @override
  Future<User> onFetch() => api.getUser();

  // Optional: customize behavior
  @override
  bool get autoFetch => true;  // Auto-fetch on ready

  @override
  Duration get fetchDelay => Duration.zero;  // Delay before auto-fetch

  @override
  void onError(Object error, StackTrace stack) {
    // Handle errors
  }
}

// 2. In your widget
class UserPage extends StatelessWidget {
  final controller = Get.put(UserController());

  @override
  Widget build(BuildContext context) {
    return controller.obx(
      (user) => Text(user!.name),
      onLoading: CircularProgressIndicator(),
      onError: (error) => Text('Error: $error'),
      onEmpty: Text('No user'),
    );
  }
}
// 3. Paginated controller
class ArticleListController extends EasyGetxPagedController<Article> {
  @override
  Future<List<Article>> onFetch({required int page}) {
    return api.getArticles(page: page, pageSize: pageSize);
  }

  @override
  int get pageSize => 20;
}

// 4. In your widget with infinite scroll
class ArticleListPage extends StatelessWidget {
  final controller = Get.put(ArticleListController());

  @override
  Widget build(BuildContext context) {
    return controller.obx(
      (articles) => ListView.builder(
        itemCount: articles!.length + 1,
        itemBuilder: (context, index) {
          if (index == articles.length) {
            return controller.hasMore
                ? LoadMoreButton(onTap: controller.loadMore)
                : SizedBox.shrink();
          }
          return ArticleCard(articles[index]);
        },
      ),
    );
  }
}

Riverpod Adapter (Riverpod 适配器) #

// 1. Create a Notifier that wraps EasyRequest
class UserNotifier extends Notifier<EasyValueState<User>> {
  late final EasyRequest<User> _engine;

  @override
  EasyValueState<User> build() {
    _engine = EasyRequest<User>(
      fetcher: () => ref.read(apiProvider).getUser(),
    );
    _engine.stream.listen((s) => state = s);
    _engine.fetch();
    ref.onDispose(_engine.dispose);
    return EasyValueState<User>.initial();
  }

  Future<void> refresh() => _engine.refresh();
  Future<void> retry() => _engine.retry();
}

final userProvider = NotifierProvider<UserNotifier, EasyValueState<User>>(
  UserNotifier.new,
);

Custom Adapter Guide (自定义适配器指南) #

To create your own adapter, implement the EasyStore<S> interface:

要创建自定义适配器,实现 EasyStore<S> 接口:

/// The store interface - just 3 members!
abstract interface class EasyStore<S> {
  S get value;
  set value(S value);
  void dispose();
}

Example: ValueNotifier adapter (示例:ValueNotifier 适配器)

class ValueNotifierStore<S> implements EasyStore<S> {
  ValueNotifierStore(this._notifier);

  final ValueNotifier<S> _notifier;

  @override
  S get value => _notifier.value;

  @override
  set value(S v) => _notifier.value = v;

  @override
  void dispose() {} // ValueNotifier managed externally
}

Usage (使用方式):

final notifier = ValueNotifier(EasyValueState<User>.initial());
final engine = EasyRequest<User>(
  fetcher: () => api.getUser(),
  store: ValueNotifierStore(notifier),
);

// Engine writes directly to notifier - no stream needed!
await engine.fetch();
print(notifier.value.data); // User data

Advanced (进阶) #

Concurrency Protection (并发保护) #

Engines use in-flight Future guards to prevent duplicate requests:

引擎使用 in-flight Future 保护机制防止重复请求:

// These calls return the SAME Future (no duplicate requests)
final f1 = engine.fetch();
final f2 = engine.fetch();
assert(identical(f1, f2)); // true

Empty Detection (空值检测) #

Built-in isValueEmpty() handles common types:

内置 isValueEmpty() 处理常见类型:

isValueEmpty(null);        // true
isValueEmpty([]);          // true
isValueEmpty({});          // true
isValueEmpty('');          // true
isValueEmpty('hello');     // false
isValueEmpty([1, 2, 3]);   // false

Error Handling (错误处理) #

Stale data on error: When refresh() fails but old data exists, the data is preserved.

错误时保留旧数据:当 refresh() 失败但存在旧数据时,数据会被保留。

// state.data still contains old user
// state.status == EasyStatus.error
// state.error contains the exception

Separate loadMore errors: Pagination separates refresh errors from loadMore errors.

分离加载更多错误:分页引擎将刷新错误与加载更多错误分开处理。

// state.error - refresh/fetch error
// state.loadMoreError - loadMore error (items preserved)

FAQ (常见问题) #

Mermaid diagrams not rendering? (Mermaid 图表不显示?) #

Mermaid diagrams render on GitHub but not on pub.dev. ASCII diagrams are provided as fallback.

Mermaid 图表在 GitHub 上可渲染,但在 pub.dev 上不行。已提供 ASCII 图表作为备选。

When to use EasyRequest vs EasyPager? (何时使用 EasyRequest vs EasyPager?) #

Use Case Engine
Single object (user profile, settings) / 单个对象(用户资料、设置) EasyRequest<T>
List with pagination / 带分页的列表 EasyPager<T>
List without pagination / 不带分页的列表 EasyRequest<List<T>>

How is hasMore calculated? (hasMore 如何计算?) #

hasMore = fetchedItems.length >= pageSize

If your API returns fewer items than pageSize, hasMore becomes false.

如果 API 返回的数据少于 pageSize,hasMore 变为 false。


License #

MIT

0
likes
150
points
0
downloads

Publisher

unverified uploader

Weekly Downloads

A universal network-request-driven state management framework. Works with GetX, Riverpod, Bloc, or any state management solution.

Homepage

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

flutter

More

Packages that depend on easy_state_core