A hands-on Flutter application designed as a deep-dive learning resource for understanding StatefulWidget lifecycle, ListView lazy rendering behavior, and resource cleanup patterns in Flutter.
Built as part of my Flutter fundamentals learning path, this project turns abstract lifecycle concepts into interactive, observable demos with real-time console logging.
- StatefulWidget Lifecycle — the complete sequence from
createState()todispose(), observed in real-time through 5 interactive scenarios - ListView Lazy Rendering — how Flutter creates and destroys widgets on scroll, and why
didChangeDependencies()fires when you don't expect it - Resource Cleanup Patterns — when to use
dispose(),if (!mounted) return, andCancelableOperationto prevent memory leaks and crashes - copyWith Pattern — why immutable models matter and how
copyWithenables clean state updates in Bloc/Cubit/Riverpod
Five scenarios that let you observe each lifecycle method in action:
| Scenario | What You Observe |
|---|---|
| First Mount | createState → initState → didChangeDependencies → build |
| setState | Only build() is called — not initState or didChangeDependencies |
| didUpdateWidget | Parent passes new props → didUpdateWidget → build, state preserved |
| didChangeDependencies | Toggle theme (InheritedWidget) → triggers didChangeDependencies |
| dispose | Hide/show widget → full deactivate → dispose and fresh creation cycle |
Four tabs comparing different rendering strategies:
| Tab | Behavior |
|---|---|
| ListView (Lazy) | Scroll out = dispose, scroll back = recreate from scratch (state lost) |
| KeepAlive | Same ListView but with AutomaticKeepAliveClientMixin — state preserved |
| SCSV + Column | SingleChildScrollView + Column — all widgets stay alive, no lifecycle on scroll |
| ListView.separated | ListView.separated with automatic dividers between items |
Simple counter demonstrating layered architecture: UI → Provider → Repository → Model.
lib/
├── main.dart # Entry point
├── app.dart # MaterialApp + navigation menu
├── core/
│ ├── constants/app_constants.dart # App-wide constants
│ └── theme/app_theme.dart # Light & dark theme config
├── data/
│ ├── models/counter_model.dart # Data model
│ └── repositories/counter_repository.dart
├── providers/
│ └── counter_provider.dart # ChangeNotifier for counter
└── ui/
├── screens/
│ ├── home/ # Counter demo screen
│ ├── lifecycle/ # Widget lifecycle demo
│ └── listview/ # ListView deep dive demo
└── shared/
└── custom_button.dart
Comprehensive Bahasa Indonesia documentation covering theory, diagrams, and practical examples:
| Document | Topics Covered |
|---|---|
docs/struktur-project/README.md |
Project structure & layered architecture |
docs/struktur-project/widget-lifecycle.md |
Complete StatefulWidget lifecycle guide with 5 scenario walkthroughs |
docs/list-view/penjelasan.md |
ListView internals: lazy rendering, cacheExtent, pagination, memory optimization |
docs/performance/penjelasan.md |
Resource cleanup: dispose patterns, mounted guard, 6 real-world cleanup cases |
docs/performance/Penjelasan-copywith.md |
copyWith pattern: immutable models, state management, memory visualization |
- Flutter SDK
>=3.11.1 - Dart SDK (included with Flutter)
# Clone the repository
git clone https://github.com/your-username/flutter-widget-lifecycle.git
# Navigate to project directory
cd flutter-widget-lifecycle
# Install dependencies
flutter pub get
# Run on your device or emulator
flutter run- Open the app — you'll see the main menu with three demo options
- Select Widget Lifecycle Demo — interact with each scenario and watch the console output
- Select ListView Deep Dive — tap +1 on items, scroll away, scroll back, and compare behavior across tabs
- Keep the Debug Console open to see real-time lifecycle logs with timestamps
ListView is lazy — it only renders what's visible. Widgets that scroll off-screen get dispose()d and are recreated from scratch when scrolling back. This is memory-efficient for large lists, but means widget state is lost unless you use AutomaticKeepAliveClientMixin or hold data in a parent state.
Always clean up resources — every StreamSubscription, AnimationController, TextEditingController, FocusNode, ScrollController, and Timer created in initState() must be disposed in dispose(). For async operations, guard with if (!mounted) return before calling setState().
Data belongs in the parent, not in list items — ListView is just a renderer. Store data in parent state or state management (Provider, Bloc, Riverpod) so scrolling doesn't trigger unnecessary API calls.
- Flutter 3.11+
- Dart 3.11+
- Provider 6.1 — state management for counter demo
- Material 3 — modern UI components
This project is open-source and available for learning purposes. Feel free to fork, study, and build upon it.
Built with the goal of making Flutter's internal mechanics visible and interactive — because the best way to learn lifecycle is to watch it happen in real-time.