搜尋套件、儲存庫與 APP 分類

套件詳情v1.0.0

lifecycle

一個 Flutter 套件,讓您輕鬆為組件生命週期新增鉤子。

AndroidiOSWebmacOSWindowsLinux
近 30 天下載
4120
喜歡數
81
Pub 分數
160

安裝套件

flutter pub add lifecycle

套件介紹

根據套件公開資料產生的選型評估,僅供參考。

生命週期套件為 Flutter 提供了一種簡單的方式,用於為組件生命週期事件加入鉤子,讓開發者能在初始化、釋放和重繪等特定階段執行程式碼。它透過提供乾淨的宣告式生命週期回呼來增強狀態管理,無需複雜的樣板程式碼。非常適合管理與 UI 樹中組件存在相關的副作用、訂閱或清除任務。該套件支援所有主要平台,包括 Android、iOS、Web、macOS、Windows 和 Linux。

適用情境

  • 管理小部件生命週期中的訂閱
  • 在小部件建立和銷毀時執行程式碼
  • 處理有狀態小部件中的副作用
  • 簡化清理邏輯
  • 與響應式狀態管理整合
  • 避免記憶體泄漏

主要特色

  • 宣告式生命週期鈎子
  • 最小化樣板程式碼
  • 跨平台支援
  • 與現有組件的輕鬆整合
  • 支援 init、dispose 與 rebuild 事件
  • 輕量且無依賴

適合對象

  • 使用有狀態小部件的開發人員
  • 採用函數式 Flutter 模式的團隊
  • 需要細粒度生命週期控制的專案
  • 尋找 StatefulWidget 的替代方案的人
  • 頻繁建立/銷毀小工具的應用程式
  • 專注於撰寫乾淨、可維護的程式碼的開發者

使用建議

  • 對於簡單小部件來說並非必要
  • 可能會在高頻率小部件更新中引入開銷
  • 需要理解 Flutter 的 widget 生命周期
  • 僅限生命週期特定用例
  • 不取代完整的狀態管理解決方案
  • 最佳搭配其他狀態處理方式使用

原始 README

以下為關聯原始碼儲存庫的 README 原文快照,最新內容請造訪原始碼儲存庫。

Pub Version pub points likes

English | 简体中文

lifecycle

Composable lifecycle state for Flutter widgets.

lifecycle combines app state, Navigator routes, paged containers, and scroll viewports into one immutable LifecycleSnapshot. A descendant can only be active when every containing scope is active:

App → Navigator route → Page/Tab → Viewport item → Widget

There is no global observer and no event bus. Each Navigator owns an explicit controller, nested scopes compose automatically, and callbacks receive both the resulting snapshot and the cause of the transition.

Lifecycle model

The current state is represented by:

LifecycleSnapshot.active(
  visibleFraction: 1,
  appState: AppLifecycleState.resumed,
)

Phases are detached, hidden, visible, active, and disposed. A single phase is the single source of truth; attached, visible, and active are derived getters. A single state change produces events in deterministic order:

created → appeared → activated → deactivated → disappeared → disposed

Only events that apply to a transition are emitted. For example, hiding an active widget emits deactivated and then disappeared.

LifecycleTransition.cause identifies whether the change came from the app, a route, a back gesture, page selection, a viewport, the widget tree, or a custom boundary.

Installation

dependencies:
  lifecycle: ^1.0.0
import 'package:lifecycle/lifecycle.dart';

App and Navigator setup

Create one NavigatorLifecycleController per Navigator. The controller owns its observer; dispose the controller where you create it.

class AppState extends State<App> {
  final navigation = NavigatorLifecycleController();

  @override
  Widget build(BuildContext context) {
    return LifecycleApp(
      child: MaterialApp(
        navigatorObservers: [navigation.observer],
        builder: (context, child) => NavigatorLifecycleScope(
          controller: navigation,
          child: child!,
        ),
        home: const HomeScreen(),
      ),
    );
  }

  @override
  void dispose() {
    navigation.dispose();
    super.dispose();
  }
}

Use a separate controller, observer, and scope for each nested Navigator. Both imperative routes and the Navigator pages API are supported. Opaque routes hide the route below; non-opaque routes such as dialogs keep it visible but inactive. Interactive back gestures expose the previous route as visible.

Routes can also be inspected or removed through the controller:

final route = navigation.routeNamed('/checkout');
final routes = navigation.routes;

if (route != null) {
  final lifecycle = navigation.lifecycleFor(route);
  debugPrint('checkout: ${lifecycle?.phase}');
  navigation.removeRoute(route);
}

Listen from a widget

For a stateless subtree, use LifecycleListener:

LifecycleListener(
  onTransition: (transition) {
    debugPrint('${transition.previous.phase} → ${transition.current.phase}');
  },
  onEvent: (event, transition) {
    debugPrint('${event.name} (${transition.cause.name})');
  },
  child: const Content(),
)

For a State, use LifecycleStateMixin:

class ArticleState extends State<Article> with LifecycleStateMixin<Article> {
  @override
  void onLifecycleEvent(
    LifecycleEvent event,
    LifecycleTransition transition,
  ) {
    if (event == LifecycleEvent.activated) {
      refreshArticle();
    }
  }

  @override
  Widget build(BuildContext context) => const ArticleView();
}

LifecycleBuilder rebuilds when any snapshot field changes, including visibleFraction:

LifecycleBuilder(
  builder: (context, lifecycle) {
    return Text(lifecycle.phase.name);
  },
)

Custom boundaries

Use LifecycleBoundary to compose domain state with the surrounding scope. It can restrict a descendant, but it cannot make a descendant more active than its parent.

LifecycleBoundary(
  constraint: !panelIsOpen || animation.value <= 0
      ? const LifecycleConstraint.hidden()
      : panelHasFocus
          ? LifecycleConstraint.active(visibleFraction: animation.value)
          : LifecycleConstraint.visible(visibleFraction: animation.value),
  child: const Panel(),
)

PageView

LifecyclePageView owns page scopes and forwards the usual PageView options. The selected page becomes active only after scrolling settles. During a drag, visible pages expose a fractional visibility and remain inactive.

final controller = PageController(initialPage: 1);

LifecyclePageView(
  controller: controller,
  onPageTransition: (index, transition) {
    debugPrint('page $index: ${transition.events}');
  },
  children: const [
    FeedPage(key: ValueKey('feed')),
    SearchPage(key: ValueKey('search')),
  ],
)

The builder constructor supports large and reorderable data sets. Supply a stable ID and its reverse lookup when order can change:

LifecyclePageView.builder(
  controller: controller,
  itemCount: items.length,
  pageIdBuilder: (index) => items[index].id,
  findPageIndex: (id) => items.indexWhere((item) => item.id == id),
  itemBuilder: (context, index) => ItemPage(
    key: ValueKey(items[index].id),
    item: items[index],
  ),
)

The widget deliberately does not change Flutter's keep-alive policy. A lazy page that is disposed emits disposed; wrap page content with AutomaticKeepAliveClientMixin when its State must survive off-screen.

TabBarView

LifecycleTabBarView(
  controller: tabController,
  onTabTransition: (index, transition) {
    debugPrint('tab $index: ${transition.events}');
  },
  children: const [OverviewTab(), ActivityTab()],
)

LifecycleTabBarView follows both animated taps and horizontal swipes. A tab is active only when the TabController is settled.

ListView, GridView, and CustomScrollView

Place ViewportLifecycleItem below the nearest Scrollable. Visibility is calculated from the item's two-dimensional area inside that viewport, so vertical lists, horizontal lists, grids, and slivers share the same behavior.

ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) => ViewportLifecycleItem(
    visibleThreshold: 0.25,
    activeThreshold: 0.8,
    activationPolicy:
        ViewportLifecycleActivationPolicy.whenScrollSettles,
    onTransition: (transition) {
      debugPrint('item $index: ${transition.current.visibleFraction}');
    },
    child: ItemTile(item: items[index]),
  ),
)

An item is visible when its fraction reaches visibleThreshold. By default it can become active only after scrolling settles and its fraction reaches activeThreshold; use ViewportLifecycleActivationPolicy.immediate to retain activation while scrolling. If activeThreshold is omitted, it uses the visible threshold. Reported fractions are stabilized to one-percent increments by default; set visibleFractionGranularity to zero for exact measurements. The containing app, route, and page states still apply.

Direct controller use

Most apps only need widgets, but LifecycleController is public for custom containers. Child state is composed with its parent, listeners are safe to add/remove during delivery, and reentrant updates are queued deterministically.

final parent = LifecycleController()..attach();
final child = LifecycleController(
  constraint: const LifecycleConstraint.hidden(),
);

void onChildChanged() {
  final transition = child.lastTransition!;
  debugPrint('${transition.previous.phase} -> ${transition.current.phase}');
}

child
  ..addListener(onChildChanged)
  ..attach(parent: parent);

child.updateLocal(
  constraint: const LifecycleConstraint.active(),
  cause: LifecycleCause.custom,
);

final subtree = LifecycleScope(
  controller: child,
  child: const CustomContainerContent(),
);

LifecycleController uses the standard ChangeNotifier channel for both snapshot and transition changes. During a listener callback, value is the latest snapshot and lastTransition describes the atomic change that produced it. Remember to call removeListener when the listener can outlive the controller.

See the example application and the tests for complete runnable compositions.

1.0 migration

Version 1.0 is a deliberate API redesign. The global defaultLifecycleObserver, legacy wrapper classes, and dispatch/subscribe mixins were removed. Replace them with explicit LifecycleApp and NavigatorLifecycleController setup, then use LifecycleListener, LifecycleStateMixin, LifecyclePageView, LifecycleTabBarView, and ViewportLifecycleItem.

所屬分類

原始碼儲存庫

使用此套件的專案

暫無相關資料。