### Run Example App Source: https://github.com/flutter-it/watch_it/blob/main/CLAUDE.md Execute the example application. Navigate to the example directory first. ```bash cd example flutter run ``` -------------------------------- ### Get Dependencies Source: https://github.com/flutter-it/watch_it/blob/main/CLAUDE.md Download and install all project dependencies. Run this after cloning a project or modifying pubspec.yaml. ```bash flutter pub get ``` -------------------------------- ### Run Example App on Specific Device Source: https://github.com/flutter-it/watch_it/blob/main/CLAUDE.md Execute the example application on a specified device, such as Chrome. Ensure the device is available and connected. ```bash flutter run -d chrome ``` -------------------------------- ### Installation Configuration Source: https://github.com/flutter-it/watch_it/blob/main/example/linux/CMakeLists.txt Sets up the installation prefix to create a relocatable bundle in the build directory. It ensures a clean build bundle directory on each installation and defines destinations for data and libraries. ```cmake set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() install(CODE " file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") " COMPONENT Runtime) set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") ``` -------------------------------- ### Simple Initialization with callOnce Source: https://github.com/flutter-it/watch_it/blob/main/WATCH_IT_PATTERNS.md Execute a simple initialization method on a manager. A concise way to perform single setup actions. ```dart // Simple initialization callOnce((_) => manager.initFields()); ``` -------------------------------- ### Install Application Bundle Components Source: https://github.com/flutter-it/watch_it/blob/main/example/linux/CMakeLists.txt Installs the main executable, Flutter ICU data, the Flutter library, and any bundled plugin libraries to their respective destinations within the installation bundle. This ensures all necessary components are included. ```cmake install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) install(FILES "${bundled_library}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endforeach(bundled_library) ``` -------------------------------- ### Install watch_it and get_it Source: https://github.com/flutter-it/watch_it/blob/main/README.md Add watch_it and the recommended get_it dependency to your pubspec.yaml file. ```yaml dependencies: watch_it: ^2.2.0 get_it: ^9.0.5 # Recommended - watch_it builds on get_it ``` -------------------------------- ### Basic watch_it Example Source: https://github.com/flutter-it/watch_it/blob/main/README.md Demonstrates creating a model with ValueNotifier, registering it with get_it, and watching its properties in a widget for automatic UI updates. ```dart import 'package:watch_it/watch_it.dart'; // 1. Create a model with ValueNotifier properties class CounterModel { final count = ValueNotifier(0); void increment() => count.value++; } // 2. Register with get_it (using exported 'di' instance) di.registerSingleton(CounterModel()); // 3. Watch it - widget rebuilds automatically class CounterWidget extends WatchingWidget { @override Widget build(BuildContext context) { final count = watchValue((CounterModel m) => m.count); return Column( children: [ Text('$count'), ElevatedButton( onPressed: di().increment, child: Text('Increment'), ), ], ); } } ``` -------------------------------- ### Complete Weather App Example with WatchIt Source: https://context7.com/flutter-it/watch_it/llms.txt This example showcases the integration of WatchItStatefulWidgetMixin, registerHandler, watch, and watchValue in a full-fledged weather application. It includes model definitions, command registration, and widget implementation for a reactive UI. ```dart class WeatherManager { late final Command> updateWeatherCommand; late final Command setRestrictionStateCommand; late final Command textChangedCommand; WeatherManager() { setRestrictionStateCommand = Command.createSync((b) => b, initialValue: false); updateWeatherCommand = Command.createAsync>( _fetchWeather, initialValue: [], restriction: setRestrictionStateCommand, ); textChangedCommand = Command.createSync((s) => s, initialValue: ''); // Debounced search: wait 500ms after typing stops before fetching textChangedCommand .debounce(const Duration(milliseconds: 500)) .listen((filter, _) => updateWeatherCommand.run(filter)); updateWeatherCommand.run(); // initial load } Future> _fetchWeather(String? filter) async { /* ... */ } } // Registration void main() { di.registerSingleton(WeatherManager()); enableSubTreeTracing = true; runApp(const MyApp()); } // Widget class HomePage extends StatefulWidget with WatchItStatefulWidgetMixin { const HomePage({super.key}); @override State createState() => _HomePageState(); } class _HomePageState extends State { @override Widget build(BuildContext context) { // Side effect: show dialog on error (no rebuild) registerHandler( select: (WeatherManager m) => m.updateWeatherCommand.errors, handler: (context, error, cancel) { showDialog( context: context, builder: (_) => AlertDialog( title: const Text('Error'), content: Text(error.toString()), ), ); }, ); // Reactive values: rebuild when these change final isRunning = watch(di().updateWeatherCommand.isRunning).value; final canUpdate = watchValue((WeatherManager m) => m.updateWeatherCommand.canRun); final isDisabled = watchValue((WeatherManager m) => m.setRestrictionStateCommand); return Scaffold( appBar: AppBar(title: const Text('Weather Demo')), body: Column( children: [ TextField( onChanged: di().textChangedCommand, decoration: const InputDecoration(hintText: 'Filter cities'), ), Expanded( child: Stack(children: [ const WeatherListView(), if (isRunning) const Center(child: CircularProgressIndicator()), ]), ), Row(children: [ Expanded( child: ElevatedButton( onPressed: canUpdate ? di().updateWeatherCommand.call : null, child: const Text('Update'), ), ), Switch( value: !isDisabled, onChanged: (v) => di().setRestrictionStateCommand(!v), ), ]), ], ), ); } } ``` -------------------------------- ### Startup Orchestration with watch_it Source: https://github.com/flutter-it/watch_it/blob/main/skills/watch-it-expert/SKILL.md Demonstrates various ways to orchestrate application startup using watch_it's `allReady`, `allReadyHandler`, and `isReady` functions. Includes an example of using `FutureBuilder` without watch_it for comparison. ```dart // Option 1: watch_it allReady() - returns bool, rebuilds reactively class SplashScreen extends WatchingWidget { @override Widget build(BuildContext context) { final ready = allReady( onReady: (context) => Navigator.pushReplacement(context, ...), onError: (context, error) => showErrorDialog(context, error), timeout: Duration(seconds: 10), ); if (!ready) return CircularProgressIndicator(); return MainApp(); } } ``` ```dart // Option 2: allReadyHandler (side effect only, no rebuild) allReadyHandler( (context) => Navigator.pushReplacement(context, mainRoute), onError: (context, error) => showErrorDialog(context, error), ); return CircularProgressIndicator(); // Always shows until handler fires ``` ```dart // Option 3: isReady() - Wait for specific type final dbReady = isReady(timeout: Duration(seconds: 5)); ``` ```dart // Option 4: Without watch_it (FutureBuilder) FutureBuilder( future: getIt.allReady(), builder: (context, snapshot) { if (!snapshot.hasData) return SplashScreen(); return MainApp(); }, ); ``` -------------------------------- ### Two-Phase Dependency Injection Setup Source: https://github.com/flutter-it/watch_it/blob/main/skills/get-it-expert/SKILL.md Use this pattern to separate base service setup from throwable scopes. This allows for easier error recovery by resetting only the affected scope. ```dart void setupBaseServices() { di.registerSingleton(createApiClient()); di.registerSingleton(WcImageCacheManager()); } Future setupThrowableScope() async { di.pushNewScope(scopeName: 'throwableScope'); di.registerLazySingletonAsync( () async => StoryManager().init(), dispose: (m) => m.dispose(), dependsOn: [UserManager], ); } // On error recovery: reset throwable scope await di.popScopesTill('throwableScope', inclusive: true); await setupThrowableScope(); ``` -------------------------------- ### Setup Base Services (Persistent) Source: https://github.com/flutter-it/watch_it/blob/main/skills/flutter-architecture-expert/SKILL.md Registers singletons for services that should survive errors and remain available throughout the application's lifecycle. ```dart // Base services (survive errors) void setupBaseServices() { di.registerSingleton(createApiClient()); di.registerSingleton(WcImageCacheManager()); } ``` -------------------------------- ### Dependency Injection Setup Source: https://github.com/flutter-it/watch_it/blob/main/WATCH_IT_PATTERNS.md Configure dependency injection using `get_it` (which watch_it builds on). Register singletons and lazy singletons for services and managers. Disable global command exception reporting if desired. ```dart import 'package:watch_it/watch_it.dart'; void setupDi() { // Configure command error reporting Command.reportAllExceptions = false; // Register singletons di.registerSingleton(appState); di.registerSingleton(storageService); di.registerSingleton(apiClient); // Register lazy singletons (created when first accessed) di.registerLazySingleton(() => UserManager()); di.registerLazySingleton(() => DataManager()); di.registerLazySingleton(() => SettingsManager()); } ``` -------------------------------- ### Install Native Assets and Flutter Assets Source: https://github.com/flutter-it/watch_it/blob/main/example/linux/CMakeLists.txt Installs native assets provided by build.dart and copies the Flutter assets directory. It ensures assets are up-to-date by removing the old directory before copying the new one. ```cmake set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") install(DIRECTORY "${NATIVE_ASSETS_DIR}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) ``` -------------------------------- ### One-Time Initialization with callOnce Source: https://github.com/flutter-it/watch_it/blob/main/WATCH_IT_PATTERNS.md Execute initialization logic exactly once, similar to `initState` but in a stateless context. Useful for triggering initial data loads or setup tasks. ```dart callOnce((_) { // Initialize data, trigger commands di().loadCommand.run(); }); ``` -------------------------------- ### Initialize with Context using callOnce Source: https://github.com/flutter-it/watch_it/blob/main/WATCH_IT_PATTERNS.md Perform initialization that requires widget context, such as accessing navigation or route arguments. Ensures context-aware setup. ```dart // Initialize with context callOnce((context) { di().markAsViewed(widget.itemId); }); ``` -------------------------------- ### Install AOT Library Source: https://github.com/flutter-it/watch_it/blob/main/example/linux/CMakeLists.txt Conditionally installs the Ahead-Of-Time (AOT) compiled library on non-Debug builds. This is used for performance optimization in release and profile builds. ```cmake if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() ``` -------------------------------- ### Command Button with Loading State Source: https://github.com/flutter-it/watch_it/blob/main/skills/watch-it-expert/SKILL.md Example of a `WatchingWidget` that displays a command button with a loading state, reacting to `command.isRunning` and `command.canRun` properties. ```dart class WcCommandButton extends WatchingWidget { final Command command; @override Widget build(BuildContext context) { final isRunning = watch(command.isRunning).value; final canRun = watch(command.canRun).value; return ElevatedButton( onPressed: canRun ? () => command.run() : null, child: isRunning ? CircularProgressIndicator() : Text('Submit'), ); } } ``` -------------------------------- ### Widget Granularity: Fine vs. Split Source: https://github.com/flutter-it/watch_it/blob/main/skills/watch-it-expert/SKILL.md Illustrates when to split `WatchingWidget`s based on the frequency of watched value changes to optimize performance. Shows examples of a fine-grained widget watching multiple values and a split approach for values changing at different rates. ```dart // ✅ Fine - watching multiple values that change together or in a simple widget class MyScreen extends WatchingWidget { @override Widget build(BuildContext context) { final user = watchValue((Auth x) => x.user); final count = watchValue((Counter x) => x.count); return Column(children: [Header(user), Counter(count)]); } } ``` ```dart // ✅ Split when values change at different frequencies and widget tree is expensive class MyScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Column(children: [_Header(), _Counter()]); } } class _Header extends WatchingWidget { @override Widget build(BuildContext context) { final user = watchValue((Auth x) => x.user); return HeaderWidget(user); } } class _Counter extends WatchingWidget { @override Widget build(BuildContext context) { final count = watchValue((Counter x) => x.count); return CounterWidget(count); } } ``` -------------------------------- ### Register a Singleton with get_it Source: https://context7.com/flutter-it/watch_it/llms.txt Register a singleton instance of a model using the exported 'di' alias for GetIt.I. This setup is typically done once, often in the main function. ```dart import 'package:watch_it/watch_it.dart'; // di and sl are both exported aliases for GetIt.I di.registerSingleton(CounterModel()); ``` -------------------------------- ### WatchingStatefulWidget with Animation Controllers Source: https://github.com/flutter-it/watch_it/blob/main/WATCH_IT_PATTERNS.md This example demonstrates using WatchingStatefulWidget with TickerProviderStateMixin for animations, integrating reactive state watching with Flutter's animation lifecycle. ```dart class AnimatedButton extends WatchingStatefulWidget { const AnimatedButton({ super.key, required this.onTap, }); final VoidCallback onTap; @override State createState() => _AnimatedButtonState(); } class _AnimatedButtonState extends State with TickerProviderStateMixin { late final AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(seconds: 1), ); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { // Use watch_it for reactive state final isLoading = watch(di().command.isRunning).value; return AnimatedBuilder( animation: _controller, builder: (context, child) => content, ); } } ``` -------------------------------- ### Setup Throwable Scope (Resettable) Source: https://github.com/flutter-it/watch_it/blob/main/skills/flutter-architecture-expert/SKILL.md Creates a new scope that can be reset or disposed of, useful for managing services that might encounter errors and need to be re-initialized. Services registered here depend on UserManager. ```dart // Throwable scope (can be reset on errors) void setupThrowableScope() { di.pushNewScope(scopeName: 'throwable'); di.registerLazySingletonAsync( () async => StoryManager().init(), dispose: (m) => m.dispose(), dependsOn: [UserManager], ); } ``` -------------------------------- ### Testing with Scopes Source: https://github.com/flutter-it/watch_it/blob/main/skills/get-it-expert/SKILL.md Use scopes for testing by pushing a new scope with mock registrations in setUp and popping it in tearDown. Constructor injection can also be used for convenience. ```dart // Option 1: Scope-based (preferred) - mocks shadow real registrations setUp(() { GetIt.I.pushNewScope( init: (getIt) { getIt.registerSingleton(MockApiClient()); }, ); }); tearDown(() async { await GetIt.I.popScope(); }); ``` ```dart // Option 2: Hybrid constructor injection (optional convenience) class MyService { final ApiClient api; MyService({ApiClient? api}) : api = api ?? getIt(); } // Test: MyService(api: MockApiClient()) ``` -------------------------------- ### Async Initialization Source: https://github.com/flutter-it/watch_it/blob/main/skills/get-it-expert/SKILL.md Explains how to handle asynchronous initialization of services using the `init()` pattern and `registerSingletonAsync`. ```APIDOC ## Async Initialization ### Description Handle asynchronous initialization of services using the preferred `init()` pattern within service classes and `registerSingletonAsync` for registration. This ensures services are ready before being accessed. ### Code Examples ```dart class DatabaseService { late final Database _db; Future init() async { _db = await Database.open('app.db'); return this; // Always return this } } void configureDependencies() { // init() pattern - concise, self-contained initialization getIt.registerSingletonAsync( () => DatabaseService().init(), ); // With dependency ordering getIt.registerSingletonAsync( () => ApiClient().init(), dependsOn: [DatabaseService], ); // Sync factory that needs async dependencies getIt.registerSingletonWithDependencies( () => AppModel(getIt()), dependsOn: [ApiClient], ); } ``` ``` -------------------------------- ### Register Services with GetIt Source: https://github.com/flutter-it/watch_it/blob/main/skills/get-it-expert/SKILL.md Demonstrates various ways to register services with GetIt, including singletons, lazy singletons, factories, and named instances. Ensure all services are registered before runApp(). ```dart final getIt = GetIt.instance; void configureDependencies() { // Singleton - created immediately getIt.registerSingleton(ApiClient()); // Singleton with dispose callback getIt.registerSingleton( StreamController(), dispose: (c) => c.close(), ); // Lazy singleton - created on first access getIt.registerLazySingleton(() => Database()); // Factory - new instance every call getIt.registerFactory(() => Logger()); // Factory with parameters getIt.registerFactoryParam( (tag, _) => Logger(tag), ); // Named instances - use when registering multiple instances of the same type getIt.registerSingleton(devConfig, instanceName: 'dev'); getIt.registerSingleton(prodConfig, instanceName: 'prod'); } ``` -------------------------------- ### Querying Scopes Source: https://github.com/flutter-it/watch_it/blob/main/skills/get-it-expert/SKILL.md Check if a scope exists or get the current scope name. ```dart getIt.hasScope('user-session'); // bool ``` ```dart getIt.currentScopeName; // String? ``` -------------------------------- ### Registering with get_it and Watching in Widgets Source: https://github.com/flutter-it/watch_it/blob/main/README.md Demonstrates how to register services with get_it and then watch them reactively within a widget using watch_it. Ensure services are registered before being watched. ```dart /// Register with get_it di.registerLazySingleton(() => UserManager()); di.registerLazySingleton(() => TodoManager()); /// Watch them in any widget class MyWidget extends WatchingWidget { @override Widget build(BuildContext context) { final user = watchIt(); final todos = watchValue((TodoManager m) => m.todos); return ListView(...); } } ``` -------------------------------- ### App Startup Configuration Source: https://github.com/flutter-it/watch_it/blob/main/skills/flutter-architecture-expert/SKILL.md Sets up the Flutter application by ensuring the binding is initialized, configuring dependencies, and running the main application widget. Includes a splash screen that waits for asynchronous services to be ready before navigating to the main app. ```dart void main() { WidgetsFlutterBinding.ensureInitialized(); configureDependencies(); // Register all services (sync) runApp(MyApp()); } // Splash screen waits for async services class SplashScreen extends WatchingWidget { @override Widget build(BuildContext context) { final ready = allReady( onReady: (context) => Navigator.pushReplacement(context, mainRoute), ); if (!ready) return CircularProgressIndicator(); return MainApp(); } } ``` -------------------------------- ### Service Registration Source: https://github.com/flutter-it/watch_it/blob/main/skills/get-it-expert/SKILL.md Demonstrates various ways to register services with get_it, including singletons, lazy singletons, factories, and named instances. ```APIDOC ## Service Registration ### Description Register services with `get_it` using different strategies like `registerSingleton`, `registerLazySingleton`, `registerFactory`, and `registerSingletonWithDependencies`. ### Code Examples ```dart final getIt = GetIt.instance; void configureDependencies() { // Singleton - created immediately getIt.registerSingleton(ApiClient()); // Singleton with dispose callback getIt.registerSingleton( StreamController(), dispose: (c) => c.close(), ); // Lazy singleton - created on first access getIt.registerLazySingleton(() => Database()); // Factory - new instance every call getIt.registerFactory(() => Logger()); // Factory with parameters getIt.registerFactoryParam( (tag, _) => Logger(tag), ); // Named instances - use when registering multiple instances of the same type getIt.registerSingleton(devConfig, instanceName: 'dev'); getIt.registerSingleton(prodConfig, instanceName: 'prod'); } ``` ``` -------------------------------- ### Async Initialization with init() Pattern Source: https://github.com/flutter-it/watch_it/blob/main/skills/get-it-expert/SKILL.md Illustrates the preferred pattern for asynchronous service initialization using an `init()` method within the service class. This pattern keeps initialization logic self-contained and allows for concise registration with dependency ordering. ```dart class DatabaseService { late final Database _db; Future init() async { _db = await Database.open('app.db'); return this; // Always return this } } void configureDependencies() { // init() pattern - concise, self-contained initialization getIt.registerSingletonAsync( () => DatabaseService().init(), ); // With dependency ordering getIt.registerSingletonAsync( () => ApiClient().init(), dependsOn: [DatabaseService], ); // Sync factory that needs async dependencies getIt.registerSingletonWithDependencies( () => AppModel(getIt()), dependsOn: [ApiClient], ); } ``` -------------------------------- ### Project and Build Configuration Source: https://github.com/flutter-it/watch_it/blob/main/example/linux/CMakeLists.txt Sets up the minimum CMake version, project name, executable name, and application ID. It also defines the build type (Debug, Profile, Release) and enables modern CMake behaviors. ```cmake cmake_minimum_required(VERSION 3.13) project(runner LANGUAGES CXX) set(BINARY_NAME "flutter_weather_demo") set(APPLICATION_ID "com.example.flutter_weather_demo") cmake_policy(SET CMP0063 NEW) set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") if(FLUTTER_TARGET_PLATFORM_SYSROOT) set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) endif() if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() ``` -------------------------------- ### GetIt Utility Methods Source: https://github.com/flutter-it/watch_it/blob/main/skills/get-it-expert/SKILL.md Provides methods for checking registrations, unregistering, resetting singletons, and managing instance creation. ```dart getIt.isRegistered(); // bool ``` ```dart getIt.unregister(); // remove registration ``` ```dart getIt.resetLazySingleton(); // recreate on next access ``` ```dart getIt.resetLazySingletons(inAllScopes: true); // bulk reset ``` ```dart getIt.checkLazySingletonInstanceExists(); // is it instantiated? ``` ```dart getIt.reset(); // clear everything (for tests) ``` ```dart getIt.allowReassignment = true; // allow overwriting registrations ``` ```dart getIt.enableRegisteringMultipleInstancesOfOneType(); // allow unnamed multiples ``` -------------------------------- ### Custom Log Function with `watchItLogFunction` Source: https://context7.com/flutter-it/watch_it/llms.txt Override the global `watchItLogFunction` to redirect watch_it trace events to your custom logging system or analytics. This example shows how to track widget rebuilds. ```dart void main() { // Override the default print-based logger watchItLogFunction = ({ String? sourceLocationOfWatch, required WatchItEvent eventType, Object? observedObject, Object? parentObject, Object? lastValue, }) { if (eventType == WatchItEvent.rebuild) { MyAnalytics.track('widget_rebuild', properties: { 'location': sourceLocationOfWatch, 'observer': observedObject?.runtimeType.toString(), }); } }; runApp(const MyApp()); } ``` -------------------------------- ### Async Initialization with Watch It Source: https://github.com/flutter-it/watch_it/blob/main/CLAUDE.md Handle asynchronous initialization by using `allReady`. This function allows you to define an `onReady` callback that executes once all necessary conditions are met, or after a specified timeout. ```dart class MyWidget extends WatchingWidget { @override Widget build(BuildContext context) { final ready = allReady( onReady: (context) => Navigator.pushReplacement(...), timeout: Duration(seconds: 5), ); if (!ready) return CircularProgressIndicator(); return MainContent(); } } ``` -------------------------------- ### Data Source Pattern with createOnce and watch Source: https://github.com/flutter-it/watch_it/blob/main/WATCH_IT_PATTERNS.md Use `createOnce` to initialize data sources and `watch` to observe their state. This pattern is useful for managing lists or streams of data. ```dart final feedSource = createOnce(() => createFeedSource()); final isLoading = watch(feedSource.isFetchingNextPage).value; final itemCount = watch(feedSource.itemCount).value; final errors = watch(feedSource.errors).value; // Use in list view if (isLoading && itemCount == 0) { return const CircularProgressIndicator(); } return ListView.builder( itemCount: itemCount, itemBuilder: (context, index) => buildItem(index), ); ``` -------------------------------- ### Choosing Widget Types with watch_it Source: https://github.com/flutter-it/watch_it/blob/main/skills/watch-it-expert/SKILL.md Demonstrates the recommended widget types for integrating watch_it. Prefer `WatchingWidget` or `WatchingStatefulWidget` for new code. Use mixin variants only when additional mixins are required or when retrofitting existing widgets. ```dart class MyWidget extends WatchingWidget { @override Widget build(BuildContext context) { ... } } ``` ```dart class MyWidget extends WatchingStatefulWidget { @override State createState() => _MyWidgetState(); } ``` ```dart class MyWidget extends StatelessWidget with WatchItMixin { ... } ``` ```dart class MyWidget extends StatefulWidget with WatchItStatefulWidgetMixin { ... } ``` -------------------------------- ### Load Initial Data with callOnce Source: https://github.com/flutter-it/watch_it/blob/main/WATCH_IT_PATTERNS.md Perform initial data fetching and settings loading when a widget is first built. Ensures data is available upon display. ```dart // Load initial data on first build callOnce((_) { di().fetchDataCommand.run(); di().loadSettingsCommand.run(); }); ``` -------------------------------- ### Reacting to Command Completion with registerHandler Source: https://github.com/flutter-it/watch_it/blob/main/skills/watch-it-expert/SKILL.md Demonstrates different ways to use `registerHandler` to react to command states: best practice for successful completion, handling errors, and reacting to all state changes. It also warns against using `isRunning` for success detection. ```dart // ✅ BEST — Watch the command itself: fires ONLY on successful completion registerHandler( select: (MyManager m) => m.saveCommand, handler: (context, _, __) { navigateAway(); // Only called on success }, ); ``` ```dart // ✅ Watch .errors: fires ONLY on errors registerHandler( select: (MyManager m) => m.saveCommand.errors, handler: (context, error, _) { showError(error!.error.toString()); }, ); ``` ```dart // Watch .results: fires on EVERY state change (isRunning, success, error) registerHandler( select: (MyManager m) => m.saveCommand.results, handler: (context, result, _) { if (result.isSuccess) { ... } if (result.hasError) { ... } if (result.isRunning) { ... } }, ); ``` ```dart // ❌ DON'T use isRunning to detect success — fragile and ambiguous registerHandler( select: (MyManager m) => m.saveCommand.isRunning, handler: (context, isRunning, _) { if (!isRunning && noError) { ... } // Easy to get wrong }, ); ``` -------------------------------- ### Anti-Pattern: Single Command for Initial Load and Pagination Source: https://github.com/flutter-it/watch_it/blob/main/skills/feed-datasource-expert/SKILL.md Use separate commands for initial data loading and pagination requests. This allows for independent management of loading states, errors, and restrictions for each operation. ```dart // ❌ Single command for both initial load and pagination // ✅ Separate commands: updateDataCommand + requestNextPageCommand // Allows independent loading/error states and restrictions ``` -------------------------------- ### Configure Flutter Library and Dependencies Source: https://github.com/flutter-it/watch_it/blob/main/example/linux/flutter/CMakeLists.txt This section finds necessary system libraries using PkgConfig and sets up variables for the Flutter library, ICU data file, and project build directories. It then defines include directories and links against system libraries. ```cmake find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) ``` ```cmake list(APPEND FLUTTER_LIBRARY_HEADERS "fl_basic_message_channel.h" "fl_binary_codec.h" "fl_binary_messenger.h" "fl_dart_project.h" "fl_engine.h" "fl_json_message_codec.h" "fl_json_method_codec.h" "fl_message_codec.h" "fl_method_call.h" "fl_method_channel.h" "fl_method_codec.h" "fl_method_response.h" "fl_plugin_registrar.h" "fl_plugin_registry.h" "fl_standard_message_codec.h" "fl_standard_method_codec.h" "fl_string_codec.h" "fl_value.h" "fl_view.h" "flutter_linux.h" ) list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") target_link_libraries(flutter INTERFACE PkgConfig::GTK PkgConfig::GLIB PkgConfig::GIO ) add_dependencies(flutter flutter_assemble) ``` -------------------------------- ### Run All Tests Source: https://github.com/flutter-it/watch_it/blob/main/CLAUDE.md Execute all tests in the project. Use this command to ensure code integrity. ```bash flutter test ``` -------------------------------- ### Feed Creation Pattern Source: https://github.com/flutter-it/watch_it/blob/main/skills/feed-datasource-expert/SKILL.md Demonstrates how to create and manage the lifecycle of a feed data source within a widget using `createOnce`. This ensures the feed is created only once and properly disposed of. ```dart // Create with createOnce in the widget that owns the feed class PostsFeedPage extends WatchingWidget { @override Widget build(BuildContext context) { final feedSource = createOnce( () => PostsFeedSource(PostFeedType.latest), dispose: (source) => source.dispose(), ); return FeedView( feedSource: feedSource, itemBuilder: (context, post) => PostCard(post: post), emptyListWidget: Text('No posts yet'), ); } } ``` -------------------------------- ### Scope Management with watch_it Source: https://github.com/flutter-it/watch_it/blob/main/skills/watch-it-expert/SKILL.md Shows how to manage scopes tied to widget lifetimes using `pushScope` and how to trigger rebuilds when any scope changes with `rebuildOnScopeChanges`. ```dart // Push scope tied to widget lifetime (auto-popped on dispose) pushScope( init: (getIt) { getIt.registerSingleton(PageData()); }, dispose: () => print('scope cleaned up'), ); ``` ```dart // Rebuild when any scope changes rebuildOnScopeChanges(); ``` -------------------------------- ### Manager Initialization vs. Commands Source: https://github.com/flutter-it/watch_it/blob/main/skills/flutter-architecture-expert/SKILL.md Distinguish between Manager init() for direct API calls and Commands for UI-facing reactive interfaces. Manager init() should load initial data directly, while Commands handle UI-triggered actions and expose their state. ```dart class MyManager { final items = ValueNotifier>([]); // Command for UI-triggered refresh (widget watches isRunning) late final loadCommand = Command.createAsyncNoParam>( () async { final result = await di().getItems(); items.value = result; return result; }, initialValue: [], ); // init() calls API directly — no command needed Future init() async { items.value = await di().getItems(); return this; } } ``` -------------------------------- ### Apply Standard Build Settings Source: https://github.com/flutter-it/watch_it/blob/main/example/linux/runner/CMakeLists.txt Applies a standard set of build settings to the specified target. This can be removed if custom build settings are required. ```cmake # Apply the standard set of build settings. This can be removed for applications # that need different build settings. apply_standard_settings(${BINARY_NAME}) ``` -------------------------------- ### Add Dependency Libraries Source: https://github.com/flutter-it/watch_it/blob/main/example/linux/runner/CMakeLists.txt Links necessary libraries to the application target. Add any application-specific dependencies here. ```cmake # Add dependency libraries. Add any application-specific dependencies here. target_link_libraries(${BINARY_NAME} PRIVATE flutter) target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) ``` -------------------------------- ### `watchIt` — observe a Listenable registered in get_it Source: https://context7.com/flutter-it/watch_it/llms.txt A shortcut for `watch(di())`. It retrieves an object from get_it, subscribes to it, and rebuilds when `notifyListeners()` is called. It returns the object itself for direct property access. ```APIDOC ## `watchIt` — observe a Listenable registered in get_it ### Description Shortcut for `watch(di())`. Retrieves the object from get_it and subscribes to it, rebuilding on every `notifyListeners()` call. Returns the object so you can read its properties directly. ### Usage Example ```dart // Assuming UserModel is registered with get_it final user = watchIt(); // Rebuilds whenever notifyListeners() is called on UserModel ``` ``` -------------------------------- ### Push and Pop Scopes (Synchronous and Asynchronous) Source: https://github.com/flutter-it/watch_it/blob/main/skills/get-it-expert/SKILL.md Use pushNewScope for synchronous initialization and pushNewScopeAsync for asynchronous initialization. Pop scopes using popScope or popScopesTill to remove registrations and restore shadowed ones. ```dart getIt.pushNewScope( scopeName: 'user-session', init: (getIt) { getIt.registerSingleton(currentUser); getIt.registerLazySingleton(() => UserPrefs(currentUser.id)); }, ); ``` ```dart await getIt.pushNewScopeAsync( scopeName: 'user-session', init: (getIt) async { final prefs = await UserPrefs.load(currentUser.id); getIt.registerSingleton(prefs); }, ); ``` ```dart await getIt.popScope(); ``` ```dart await getIt.popScopesTill('base-scope', inclusive: false); ``` ```dart await getIt.dropScope('user-session'); ``` -------------------------------- ### Create Async Command with Parameters and Result Source: https://github.com/flutter-it/watch_it/blob/main/WATCH_IT_PATTERNS.md Use `createAsync` for commands that accept parameters and return a result. An `errorFilter` can be specified for custom error handling, such as `LocalOnlyErrorFilter`. ```dart late final createItemCommand = Command.createAsync( (params) async { final api = ItemApi(di()); final dto = await api.createItem( title: params.title, description: params.description, ); return Item.fromDto(dto); }, debugName: 'createItem', errorFilter: const LocalOnlyErrorFilter(), ); ``` -------------------------------- ### Podcast Proxy with Smart Fallbacks Source: https://github.com/flutter-it/watch_it/blob/main/skills/flutter-architecture-expert/SKILL.md Handles cases where full data is loaded asynchronously. Getters fall back to initial lightweight data if the full data is not yet available, ensuring the UI remains responsive. ```dart class PodcastProxy extends ChangeNotifier { PodcastProxy({required this.item}); final SearchItem item; // Initial lightweight data Podcast? _podcast; // Full data loaded later List? _episodes; // Getters fall back to initial data if full data not yet loaded String? get title => _podcast?.title ?? item.collectionName; String? get image => _podcast?.image ?? item.bestArtworkUrl; late final fetchCommand = Command.createAsyncNoParam>( () async { if (_episodes != null) return _episodes!; // Cache final result = await di().findEpisodes(item: item); _podcast = result.podcast; _episodes = result.episodes; return _episodes!; }, initialValue: [], ); } ``` -------------------------------- ### Retrieving Services from GetIt Source: https://github.com/flutter-it/watch_it/blob/main/skills/get-it-expert/SKILL.md Shows various methods for retrieving registered services from GetIt, including direct retrieval, safe retrieval, asynchronous retrieval, retrieving all instances of a type, and accessing named or parameterized instances. ```dart final api = getIt(); // get() - throws if missing final api = getIt.maybeGet(); // returns null if missing final api = await getIt.getAsync(); // waits for async registration final all = getIt.getAll(); // all instances of type final config = getIt(instanceName: 'dev'); // named instance final logger = getIt(param1: 'MyClass'); // factory with params ``` -------------------------------- ### Conditional Initialization with callOnce Source: https://github.com/flutter-it/watch_it/blob/main/WATCH_IT_PATTERNS.md Conditionally execute initialization logic based on certain criteria. Useful for refreshing data only when necessary. ```dart // Conditional initialization callOnce((_) { if (di().needsRefresh) { di().refreshCommand.run(); } }); ``` -------------------------------- ### Wait for All Async Registrations to Complete Source: https://context7.com/flutter-it/watch_it/llms.txt Utilize `allReady` to monitor the completion of all asynchronous registrations in `get_it`. It triggers a rebuild upon readiness and calls `onReady` or `onError` callbacks. A timeout can be specified. ```dart // Setup async registrations void setupDI() { di.registerSingletonAsync(() async { final db = DatabaseService(); await db.initialize(); return db; }); di.registerSingletonAsync(() async { return ConfigService()..await loadRemoteConfig(); }); } class AppLoadingScreen extends WatchingWidget { @override Widget build(BuildContext context) { final ready = allReady( onReady: (context) { Navigator.of(context).pushReplacementNamed('/home'); }, onError: (context, error) { showDialog( context: context, builder: (_) => AlertDialog( title: const Text('Startup Error'), content: Text(error.toString()), ), ); }, timeout: const Duration(seconds: 10), ); return Scaffold( body: Center( child: ready ? const Text('All services ready') : const CircularProgressIndicator(), ), ); } } ``` -------------------------------- ### UI Handling of Asynchronous Initialization State Source: https://github.com/flutter-it/watch_it/blob/main/skills/flutter-architecture-expert/SKILL.md Place the `allReady()` check in the UI layer (`WatchingWidget`) to display a loading indicator until all asynchronous singletons are initialized. This ensures the UI correctly reflects the application's readiness. ```dart // ✅ UI handles loading state class MyApp extends WatchingWidget { @override Widget build(BuildContext context) { if (!allReady()) return LoadingScreen(); return MainApp(); } } // ✅ Push scope, let UI react Future onAuthenticated(Client client) async { di.pushNewScope(scopeName: 'auth', init: (scope) { scope.registerSingleton(client); scope.registerSingletonAsync(() => MyManager().init(), dependsOn: [Client]); }); // No await di.allReady() here — UI handles it } ``` -------------------------------- ### Set Up Global Exception Handler for Commands Source: https://github.com/flutter-it/watch_it/blob/main/skills/flutter-architecture-expert/SKILL.md Assign a static method to `Command.globalExceptionHandler` in `main()` to catch any command errors that do not have specific local error listeners. This provides a fallback for unhandled exceptions. ```dart // In TheApp static void globalErrorHandler(CommandError error, StackTrace stackTrace) { debugPrint('Command error [${error.commandName}]: ${error.error}'); di().showToast(error.error.toString(), isError: true); } // In main() Command.globalExceptionHandler = TheApp.globalErrorHandler; ``` -------------------------------- ### Create Async Command with No Parameters and No Result Source: https://github.com/flutter-it/watch_it/blob/main/WATCH_IT_PATTERNS.md Use `createAsyncNoParamNoResult` for asynchronous operations that do not return a value. Ensure the required service is registered in the dependency injection container. ```dart late final initializeCommand = Command.createAsyncNoParamNoResult( () async { await di().initialize(); }, debugName: 'initialize', ); ``` -------------------------------- ### Run Initialization Code Once Per Widget Lifetime Source: https://context7.com/flutter-it/watch_it/llms.txt Use `callOnce` to execute initialization logic only on the first build of a widget. An optional `dispose` callback can be provided to run cleanup code when the widget is destroyed. ```dart class ProductListWidget extends WatchingWidget { @override Widget build(BuildContext context) { // Runs once on first build, not on rebuilds callOnce( (_) => di().loadProductsCommand.run(), dispose: () => di().cancelAll(), ); final products = watchValue((ProductManager m) => m.products); final isLoading = watchValue((ProductManager m) => m.isLoading); if (isLoading) return const CircularProgressIndicator(); return ListView.builder( itemCount: products.length, itemBuilder: (_, i) => ListTile(title: Text(products[i].name)), ); } } ``` -------------------------------- ### Run Tests with Coverage Source: https://github.com/flutter-it/watch_it/blob/main/CLAUDE.md Generate a test coverage report. This helps identify areas of the code not adequately tested. ```bash flutter test --coverage ``` -------------------------------- ### Flutter and System Dependencies Source: https://github.com/flutter-it/watch_it/blob/main/example/linux/CMakeLists.txt Includes the Flutter managed directory and finds system-level GTK dependencies using PkgConfig. This is essential for integrating Flutter's build system and required libraries. ```cmake set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) ``` -------------------------------- ### Anti-Pattern: Registering Feed as Singleton Source: https://github.com/flutter-it/watch_it/blob/main/skills/feed-datasource-expert/SKILL.md Do not register feeds as singletons in get_it. Instead, create them using `createOnce` within the widget that owns the feed to ensure proper lifecycle management. ```dart // ❌ Registering feed in get_it as singleton di.registerSingleton(PostsFeedSource()); // ✅ Create with createOnce in the widget that owns it final feed = createOnce(() => PostsFeedSource()); ``` -------------------------------- ### Command-First Pattern for Async Operations Source: https://github.com/flutter-it/watch_it/blob/main/WATCH_IT_PATTERNS.md Replace raw async methods with Commands for better state management and testability. Use `Command.createAsyncNoParam` for commands without parameters. Run commands using `.run()` without `await` in the UI. ```dart late final loadDataCommand = Command.createAsyncNoParam( () async { final result = await api.fetchData(); return result; }, ); // In widget callOnce((_) => di().loadDataCommand.run()); ``` ```dart Future loadData() async { // ❌ Don't use raw async methods final result = await api.fetchData(); } ``` -------------------------------- ### Format Code Source: https://github.com/flutter-it/watch_it/blob/main/CLAUDE.md Format all Dart code in the project according to standard style guidelines. This is a required step before committing changes. ```bash dart format . ``` -------------------------------- ### Create Async Command with No Parameters and List Result Source: https://github.com/flutter-it/watch_it/blob/main/WATCH_IT_PATTERNS.md Use `createAsyncNoParam` for commands that perform an asynchronous operation without parameters and return a list. Ensure `ItemApi` and `ApiClient` are available via dependency injection. ```dart late final loadItemsCommand = Command.createAsyncNoParam>( () async { final api = ItemApi(di()); final response = await api.getItems(); return response.map(Item.fromDto).toList(); }, debugName: 'loadItems', ); ``` -------------------------------- ### Upgrade Dependencies Source: https://github.com/flutter-it/watch_it/blob/main/CLAUDE.md Upgrade project dependencies to their latest compatible versions. Always check compatibility before running this command. ```bash flutter pub upgrade ``` -------------------------------- ### Enable Subtree Tracing with `WatchItSubTreeTraceControl` Source: https://context7.com/flutter-it/watch_it/llms.txt Wrap a part of your widget tree with `WatchItSubTreeTraceControl` to enable tracing for all `WatchingWidget`s within that subtree. This requires `enableSubTreeTracing` to be set to `true` globally. ```dart void main() { setupDI(); enableSubTreeTracing = true; // enable subtree tracing globally runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: WatchItSubTreeTraceControl( logRebuilds: true, logHandlers: true, logHelperFunctions: false, child: const HomePage(), ), ); } } ``` -------------------------------- ### DataProxy and DataRepository with Reference Counting Source: https://github.com/flutter-it/watch_it/blob/main/skills/flutter-architecture-expert/SKILL.md Implement a DataRepository to deduplicate proxies and manage their lifecycle using reference counting. This is useful when the same entity appears in multiple UI locations. ```dart abstract class DataProxy extends ChangeNotifier { DataProxy(this._target); T _target; int _referenceCount = 0; T get target => _target; set target(T value) { _target = value; notifyListeners(); } @override void dispose() { assert(_referenceCount == 0); super.dispose(); } } abstract class DataRepository, TId> { final _proxies = {}; TId identify(T item); TProxy makeProxy(T entry); // Returns existing proxy (updated) or creates new one TProxy createProxy(T item) { final id = identify(item); if (!_proxies.containsKey(id)) { _proxies[id] = makeProxy(item); } else { _proxies[id]!.target = item; // Update with fresh data } _proxies[id]!._referenceCount++; return _proxies[id]!; } void releaseProxy(TProxy proxy) { proxy._referenceCount--; if (proxy._referenceCount == 0) { proxy.dispose(); _proxies.remove(identify(proxy.target)); } } } ```