flutter_spine 0.2.6 copy "flutter_spine: ^0.2.6" to clipboard
flutter_spine: ^0.2.6 copied to clipboard

Shared infrastructure for Flutter apps — MVVM base classes, effect bus, scaffold suite, network layer, and error model.

0.2.6 #

Removed #

  • Breaking: entire pagination module deleted (lib/src/pagination/): PagedState, PagedNotifierMixin, PagedNotifierMixinNoArg, PagedController, PagedScaffold. Business implements its own list state class + plain AsyncNotifier with hand-written refresh / loadMore (see migration below).
  • Breaking: CLI paged-list command deleted (command + 4 templates). flutter_spine:new feature --variant=list no longer exists — --variant is now page | async | form.
  • Lint supertype lists (avoid_static_mutable_in_notifier, no_ui_in_viewmodel) no longer reference the removed pagination mixins.

Changed #

  • README §4 "Pattern C" rewritten as a business-side pagination reference implementation: custom TaskListState + AutoDisposeAsyncNotifier + refresh()/loadMore() + RefreshIndicator/NotificationListener UI.
  • Example tasks_vm.dart rewritten as the manual pagination reference: TaskListState (items/page/hasMore/isLoadingMore/moreError), snapshot-based optimistic update + rollback.
  • Example tasks_tab.dart updated to the new state type.

Migration #

// 之前(≤0.2.5)
class TasksVm extends AutoDisposeAsyncNotifier<PagedState<Task>>
    with PagedNotifierMixinNoArg<Task> {
  int get pageSize => 20;
  Future<List<Task>> fetchPage(int page, int size) => repo.list(page: page, size: size);
}

// 之后(0.2.6+,业务侧完全自实现,约 40 行)
@immutable
class TaskListState {
  const TaskListState({this.items = const [], this.page = 1,
    this.hasMore = true, this.isLoadingMore = false, this.moreError});
  final List<Task> items; final int page;
  final bool hasMore; final bool isLoadingMore; final Object? moreError;
  // copyWith / isEmpty ...
}

class TasksVm extends AutoDisposeAsyncNotifier<TaskListState> {
  static const _pageSize = 20;
  @override
  Future<TaskListState> build() => _fetch(1);
  Future<TaskListState> _fetch(int page) async { ... }
  Future<void> refresh() async { await future; ref.invalidateSelf(); await future; }
  Future<bool> loadMore() async { ... }
}

0.2.5 #

Removed #

  • Breaking: easy_refresh dependency removed — flutter_spine no longer provides pull-to-refresh / load-more UI machinery.
  • Breaking: PagedListView deleted (lib/src/pagination/paged_list_view.dart), including PagedScrollViewBuilder. Business builds its own list UI: PagedScaffold + RefreshIndicator / NotificationListener / own refresh framework, calling PagedController.refresh() / loadMore().
  • Breaking: AppListPageScaffold deleted. Use PagedScaffold (state machine: loading / error / empty / data) + business-side list.
  • Breaking: AppTabChildScaffold deleted. Compose EffectListener(source: ..., handleDefaults: false) + AutomaticKeepAliveClientMixin directly (the class was a 66-line thin wrapper).
  • Breaking: FilterNotifier deleted (lib/src/filter/). Write a plain Notifier<F> subclass instead (initial / set / update / reset are trivial to replicate).
  • avoid_raw_scaffold lint message updated: no longer suggests the removed scaffolds.

Changed #

  • PagedNotifierMixin doc: PagedController is now driven by business-side UI triggers.
  • PagedScaffold doc examples updated to business-side refresh pattern.
  • CLI paged-list page template rewritten: generates PagedScaffold + ListView + RefreshIndicator example with TODO markers where business adds its own load-more trigger.
  • flutter_spine.dart exports updated (4 export lines removed).

Example #

  • tasks_tab.dart rewritten as the reference implementation: RefreshIndicator + NotificationListener<ScrollNotification> load-more + AsyncValue.when first-loading/error + MoreErrorBar footer.
  • Removed demo_paged_list_page.dart / demo_app_list_page.dart demos (router + demos index updated).

Fixed #

  • test/pagination/paged_notifier_mixin_test.dart — family provider reads updated to pass an argument (pre-existing breakage on Riverpod 2.6.1: reading _fakeListProvider.future without an arg threw a null cast).

Migration #

// 之前:PagedListView 全自动(内置 easy_refresh)
PagedListView<Task>(
  provider: tasksVmProvider,
  controllerProvider: tasksVmProvider.notifier,
  itemBuilder: (ctx, task, _) => TaskTile(task),
)

// 之后:业务自己组合
RefreshIndicator(
  onRefresh: () => ref.read(tasksVmProvider.notifier).refresh(),
  child: NotificationListener<ScrollNotification>(
    onNotification: (n) {
      if (n.metrics.pixels > n.metrics.maxScrollExtent - 200) {
        ref.read(tasksVmProvider.notifier).loadMore();
      }
      return false;
    },
    child: ListView.builder(...),
  ),
)

0.2.4 #

Changed #

  • Breaking: DioHttpConfig.authRefresh and DioHttpConfig.retry fields removed. AuthRefreshInterceptor and RetryInterceptor are no longer auto-registered by DioHttpClient.fromConfig(). Business code should explicitly add these interceptors via DioHttpConfig.interceptors (for AuthTokenInterceptor / HttpLoggingInterceptor / EnvelopeUnwrapInterceptor) or by assembling a Dio instance with DioHttpClient.fromDio() (for AuthRefreshInterceptor / RetryInterceptor which require a Dio reference).
  • Built-in interceptor classes (AuthTokenInterceptor, HttpLoggingInterceptor, EnvelopeUnwrapInterceptor, AuthRefreshInterceptor, RetryInterceptor, AuthRefreshConfig, RetryConfig) are preserved — business may still use them, just must register them explicitly.
  • DioHttpConfig.interceptors doc updated to clarify it accepts any Dio Interceptor (built-in or custom).

Added #

  • HttpClient.requestStream() — streaming request method for SSE / large file downloads / long-poll push. Returns StreamedHttpResponse with statusCode / headers / stream (Stream<List<int>>). Error normalization matches request() — non-2xx / network errors / timeouts throw AppException.
  • StreamedHttpResponse — immutable stream response wrapper with statusCode, headers, stream, isSuccess, and header() lookup.
  • HttpResponseType.stream — new enum value; maps to Dio's ResponseType.stream.
  • 3 new tests in dio_http_client_test.dart covering stream response, 401 error, and cancelToken cancellation.

Docs #

  • README §1.5 RetryInterceptor / §1.6 AuthRefreshInterceptor / §1.7 内置 Interceptor 速查 updated to show DioHttpClient.fromDio() assembly pattern.
  • README §1.8 流式响应 / SSE added.
  • AiHelper/skills/flutter-core-http-setup/SKILL.md updated: removed authRefresh/retry fields, added RetryInterceptor/AuthRefreshInterceptor assembly sections, added SSE/stream section.

Tests #

  • http_auth_refresh_interceptor_test.dart and http_retry_interceptor_test.dart refactored to use DioHttpClient.fromDio() instead of DioHttpConfig.authRefresh/.retry.

0.2.3 #

Fixed #

  • _defaultFactory URL construction — switched to pure string operations to avoid Dart SDK Uri.replace() port :0 bug. Added scheme auto-correction (http→ws, https→wss) and port :0 removal. Added step-by-step debug logging.
  • isConnectAuthError missing from WsModuleConfig — field was added to WsClientConfig but not propagated through WsModuleConfig constructor, toConfig(), and toConfigWith(), causing configuration loss when using WsModuleRegistry.
  • WsModuleConfig missing reconnect fieldsprotocols, connectTimeout, baseReconnectDelay, maxReconnectDelay, maxReconnectAttempts, reconnectJitterRatio were not in WsModuleConfig, preventing sharedWsConfig defaults from reaching the final WsClientConfig.
  • Connect auth refresh infinite loop_reconnectAttempt was reset to 0 on every connect-level auth refresh, preventing maxReconnectAttempts from ever being reached.
  • EffectListener double-dispatch — root-level EffectListener now defaults to handleDefaults: false. AppTabChildScaffold and AppBottomSheetScaffold set handleDefaultEffects: false. Only AppPageScaffold processes built-in effects, eliminating duplicate navigation/toast/dialog triggers.
  • DemosPage double-navigation (example) — parent route container now sets handleDefaultEffects: false to prevent processing effects emitted by child routes' ViewModels.

Example #

  • ws_modules.dart token extracted to _currentToken variable — onAuthExpired writes back refreshed token, queryParamsProvider reads it on each connect/reconnect.
  • demo_market_ws_page.dart refactored to use global marketGatewayProvider instead of self-built fake channel and hardcoded URL.

0.2.2 #

Added #

  • BaseWsGateway — abstract WebSocket gateway for business modules. Delegates connection lifecycle to WsClient; subclasses (Market / Asset / Swap) define type-safe subscription APIs and topic encoding.
  • WsClientConfig.headersProvider — dynamic headers callback. Called on every connect / reconnect to fetch the latest token, eliminating the need to rebuild config after auth refresh.
  • WsClientConfig.queryParamsProvider — dynamic query string callback. Same pattern as headersProvider, for backends that pass auth tokens via URL query params (?token=xxx) instead of HTTP headers. Manual string concatenation avoids Dart SDK Uri.replace() port :0 bug.
  • WsClientConfig.onAuthExpired + isAuthCloseCode — token expiry auto-refresh. When the server closes with an auth close code (e.g. 4001), DefaultWsClient calls onAuthExpired with single-flight guarantee, then reconnects with the new token. Unsuccessful refresh transitions to WsFailed.
  • WsClientConfig.isConnectAuthError — connection-level auth error detection. Handles HTTP 401/403 rejection during WebSocket upgrade handshake (complements isAuthCloseCode which handles post-connect close frames). Defined externally via predicate so backend-specific error formats are not hardcoded.
  • Close code handling in DefaultWsClient — normal close codes (1000, 1001) now transition to WsDisconnected without triggering auto-reconnect. All other close codes continue to trigger standard reconnect.
  • Unified IOWebSocketChannel in _defaultFactory — always uses IOWebSocketChannel.connect(), no longer implicitly switches between WebSocketChannel.connect() and IOWebSocketChannel based on parameter presence. Behavior is now consistent regardless of whether headers/queryParams are configured.
  • WsTopicRouter.simple() — factory constructor for standard pub/sub protocols where channel name equals topic name. Auto-generates topicExtractor, subscribeFrameBuilder, and unsubscribeFrameBuilder from a single channelKey parameter.
  • WsModuleRegistry + WsModuleConfig — registration pattern for WebSocket modules. Each business module defines a WsModuleConfig instance; WsModuleRegistry.build() maps URIs to configs, replacing hand-written if-else chains in wsConfigBuilderProvider overrides.
  • CLI ws-gateway commandflutter_spine:new ws-gateway <name> generates topic / topic_router / ws_gateway / providers four-file scaffold.

Changed #

  • Breaking: WsClientConfig.headers replaced by headersProvider (Map<String, dynamic> Function()?). Existing code must change from headers: {'key': 'val'} to headersProvider: () => {'key': 'val'}.
  • WsClientConfig constructor now accepts headersProvider, queryParamsProvider, onAuthExpired, and isAuthCloseCode (all optional).
  • _defaultFactory no longer branches on parameter presence — always constructs IOWebSocketChannel for consistent behavior.

Tests #

  • 9 new test cases for close code handling, auth refresh, and connect-level auth detection (40 total in test/network/ws/).

Example #

  • demo_market_ws/ — full MarketWsGateway implementation: topic encoding/decode (MarketTopic), protocol adapter (marketTopicRouter), Riverpod StreamProvider.autoDispose.family for automatic subscription lifecycle, and interactive lifecycle demo page.
  • demo_asset_ws/ & demo_swap_ws/ — showcase multi-module Gateway pattern. All three modules share the same auth/heartbeat/reconnect config via a _sharedWsConfig factory in main.dart, each overriding only its own topicRouter.
  • main.dart now demonstrates FlutterSpineConfig.extraOverrides with WsModuleRegistry.build() replacing the if-else chain.

0.1.2 #

Added #

  • PagedListView.scrollViewBuilder — embed the list in a CustomScrollView with extra slivers.
  • PagedListView.enableLoadMore — disable load-more footer, keep only pull-to-refresh.
  • AppListPageScaffold.scrollViewBuilder / enableLoadMore — forwarded to PagedListView.
  • PagedScrollViewBuilder typedef.
  • Example demo pages: /demos/paged-list, /demos/app-list.

Fixed #

  • DioExceptionType.transformTimeout not found with dio 5.9.x — replaced with default / _ fallback for forward compatibility.

0.1.1 #

Added #

  • Scaffold CLI (flutter_spine:new).
  • generator_templates.dart — riverpod_generator support.
  • FeatureCommandflutter_spine:new feature one-key whole feature generation.
  • BootstrapCommandflutter_spine:new bootstrap app skeleton.
  • FlutterCoreDiagnosticsBanner.
  • MaterialDefaultEffectHandler ctor overrides.
  • mixin-based API: ViewModelMixin, AsyncViewModelMixin, family variants.

Changed #

  • CLI templates now use {{Name}} / {{name}} / {{name_snake}} / {{name-kebab}} / {{Title}} naming conventions.
  • Renamed FlutterCore to FlutterSpine, FlutterCoreConfig to FlutterSpineConfig.

0.1.0 #

  • Initial release.
  • error/: sealed AppException hierarchy + safeApiCall normalization.
  • network/: ChannelClient MethodChannel wrapper.
  • pagination/: PagedState + PagedNotifierMixin (family + noArg).
  • filter/: FilterNotifier base class.
  • presentation/: AsyncBuilder + AsyncValue extensions.
  • logging/: AppLogger interface + PrettyAppLogger implementation.
  • observers/: ErrorObserver (toast callback injection) + LogObserver.
  • storage/: KeyValueStorage abstraction + HiveStorage + keyValueStorageProvider.
  • theme/: AppThemeExtension + ThemeModeNotifier.
  • utils/: num_ext, string_ext, date_ext, iterable_ext, context_ext.
1
likes
140
points
352
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

Shared infrastructure for Flutter apps — MVVM base classes, effect bus, scaffold suite, network layer, and error model.

Homepage
Repository (GitHub)
View/report issues
Contributing

License

MIT (license)

Dependencies

analyzer, args, custom_lint_builder, dio, flutter, flutter_riverpod, go_router, hive, http_parser, logger, meta, path, riverpod_annotation, web_socket_channel

More

Packages that depend on flutter_spine