パッケージ、リポジトリ、アプリカテゴリを検索

パッケージ詳細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 のウィジェットライフサイクルの理解が必要です
  • ライフサイクル固有の使用事例に限定
  • 完全な状態管理ソリューションを置き換えるものではありません
  • 他の状態管理アプローチと併用するのが最適です

元の 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.

カテゴリー

ソースリポジトリ

このパッケージを使用しているプロジェクト

データはまだありません。