A comprehensive framework for building robust and scalable Flutter applications. Flutter Control streamlines state management, dependency injection, and navigation, providing a structured approach to application development.
- Modular State Management: Manage both global application state and granular widget-level state effectively.
- Powerful Dependency Injection: Built-in Service Locator with Factory and Singleton patterns for efficient dependency management.
- Flexible Navigation & Routing: Define routes, manage transitions, and pass arguments seamlessly across your app.
- Reactive Programming: Observable patterns ([ActionControl], [FieldControl]) integrated with UI builders for dynamic updates.
- Global Event System: A robust broadcast mechanism for application-wide event communication.
- Theming & Localization: Integrated support for dynamic themes and internationalization (via [Localino]).
- Modular Architecture: Organize your app into independent modules for better maintainability and scalability.
Add flutter_control to your pubspec.yaml:
dependencies:
flutter:
sdk: flutter
flutter_control: # Use the latest version from pub.devInitialize the core framework in your main.dart and wrap your app with ControlRoot.
import 'package:flutter/material.dart';
import 'package:flutter_control/control.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Control.initControl(
entries: {
MyService: MyService(),
},
);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ControlRoot(
theme: MaterialThemeConfig(
themes: {
Brightness.light: () => ThemeData.light(),
Brightness.dark: () => ThemeData.dark(),
'custom': () => ThemeData.dark().copyWith(primaryColor: Colors.purple),
}
),
states: [
AppState.init.build(builder: (_) => LoadingPage()),
AppState.main.build(builder: (_) => HomePage(), transition: CrossTransition.fade()),
],
builder: (context, home) => MaterialApp(
title: 'Flutter Control App', // Replace with your app title
theme: context.themeConfig?.value, // Dynamic theme from ControlRoot
home: home, // The currently active AppState widget
onGenerateRoute: (settings) => context.generateRoute(settings, root: () => MaterialPageRoute(builder: (_) => home)),
),
);
}
}
// Example pages
class LoadingPage extends BaseControlWidget {
@override
void onInit(CoreContext context, Map args){
super.onInit(context, args);
Future.delayed(Duration(seconds: 2), () => ControlScope.root.setMainState());
}
@override
Widget build(CoreContext context) {
return Scaffold(body: Center(child: CircularProgressIndicator()));
}
}
class HomePage extends BaseControlWidget {
@override
Widget build(CoreContext context) {
return Scaffold(appBar: AppBar(title: Text('Home Page')), body: Center(child: Text('Welcome!')));
}
}Control: The central static class for initializing and accessing the framework's core functionalities, including theControlFactory.ControlFactory: A powerful Service Locator and Dependency Injection container. It manages the lifecycle and instantiation of your app's services, models, and other dependencies, making them accessible throughout the application.ControlModule: Enables modularity by encapsulating related dependencies and configurations. Modules are loaded byControlFactoryto register their services.
ControlRoot: The root widget of your application that orchestrates global state management, including:AppState: Defines distinct states of your application (e.g.,init,auth,main).ControlRoottransitions between these states, allowing you to easily switch between different UI flows.ThemeConfig: Manages dynamic theming (light, dark, custom) and persists user theme preferences.
CoreWidget: The baseStatefulWidgetfor all control widgets, creating aCoreContextwhich acts as a powerful element for local dependency injection and state management within the widget tree.ControlWidget: A flexible base class for widgets that manage one or more [ControlModel]s, providing robust lifecycle management and automatic UI updates.SingleControlWidget<T>: Optimized for widgets that primarily depend on a single [ControlModel] of typeT, automatically resolving and providing it.ControllableWidget<T>: A reactive widget that rebuilds automatically when a providedcontrol(single or list of observables) notifies of changes.
ControlModel: The base class for defining your application's business logic and state. Models are framework-aware and can interact with the dependency injection and event systems.BaseControl: An extended version ofControlModelwith additional functionalities, typically used for more complex and robust logic components.BaseModel: A lightweight variant ofControlModel, suitable for simpler logic components.
ControlObservable: An abstraction for various observable types ([ActionControl], [FieldControl], [ValueListenable], [Stream], [Future]), providing a unified way to subscribe to changes.ActionControl: A lightweight observable primarily used for notifying listeners about value changes. Supports single, broadcast, and empty variants.final counter = ActionControl.broadcast<int>(0); // Create an observable int // ... later in your UI ... ControlBuilder<int>( // Rebuilds automatically when `counter` changes control: counter, builder: (context, value) => Text('Count: $value'), ); // To update the value: // counter.value++;
FieldControl: A more robust observable built around Dart Streams, ideal for complex data flows, validation, and transformations. Comes with specialized variants likeStringControl,NumberControl, andListControl.final usernameField = FieldControl<String>('', validator: (value) => value.isEmpty ? 'Required' : null); // ... later in your UI ... FieldBuilder<String>( // Rebuilds and handles validation messages control: usernameField, builder: (context, value) => TextField( controller: usernameField, decoration: InputDecoration(errorText: usernameField.error), ), ); // To update the value: // usernameField.value = 'new_username';
ControlBuilder/ControlBuilderGroup: Widgets that automatically subscribe toControlObservables (or a list of them) and rebuild their children when changes are notified.
Flutter Control provides powerful extensions on BuildContext (via CoreContext) to simplify common tasks like managing controllers, accessing dependencies, and handling widget-level state. These "hooks" are lazily initialized and automatically disposed of when the widget is removed from the tree.
context.core: Access the nearestCoreContext, providing a scope for local dependencies and state.context.args: Retrieve arguments passed to the widget during navigation.context.use<T>(...): Initialize or retrieve a dependency tied to the widget's lifecycle. Ideal for services or controllers used only within a specific widget.context.notifyState(): Manually request a widget rebuild.context.registerStateNotifier(object): Automatically rebuild the widget whenever the provided object (e.g.,ChangeNotifierorControlObservable) notifies changes.context.unfocus(): Quickly dismiss the keyboard by unfocusing the primary focus node.
context.animation(): Create and manageAnimationControllers effortlessly.final controller = context.animation( duration: Duration(seconds: 1), stateNotifier: true, // Rebuilds the widget on every animation tick );
context.scroll(): ManageScrollControllers with ease.final scrollController = context.scroll('my_list'); // Identified by a key
context.theme: Direct access to the currentThemeData.context.media: Direct access toMediaQueryData.context.ticker: Provides aTickerProvidertied to the widget's lifecycle.
The context.showOverlay extension provides a simple way to manage floating UI elements (like popups or tooltips) associated with a specific context or GlobalKey:
context.showOverlay(
key: 'my_popup',
builder: (parentRect) => Positioned(
left: parentRect.left,
top: parentRect.bottom,
child: MyPopupWidget(),
),
);
// Later:
context.hideOverlay('my_popup');You can define your own lazy-initialized objects using context.use. This ensures the object is only created when needed and disposed of when the widget is destroyed:
final myService = context.use<MyService>(
value: () => MyService(),
dispose: (service) => service.dispose(),
);-
ControlRoute: Defines application routes with associated widgets, dynamic path parameters, custom transitions, and navigation arguments. Routes are typically registered centrally. -
RouteStore: A central repository for all definedControlRoutes, making them discoverable and reusable throughout the application. -
RouteNavigator: An abstract interface for performing navigation actions (push, pop, replace).ControlNavigatoris the default Flutter implementation. -
RouteHandler: Binds aControlRouteto aRouteNavigator, providing a fluent API to open routes with specific configurations.// 1. Define and register routes in Control.initControl or RoutingModule await Control.initControl( modules: [ RoutingModule([ ControlRoute.build<UserPage>(builder: (_) => UserPage()), ControlRoute.build(identifier: '/profile/edit/{uid}', builder: (_) => ProfileEditPage()) .viaTransition(CrossTransition.slide()), // Custom transition ]), ], ); // 2. Navigate from any BuildContext class MyWidget extends BaseControlWidget { @override Widget build(CoreContext context) { return ElevatedButton( onPressed: () { // Navigate to UserPage using its type context.routeOf<UserPage>()?.openRoute(args: userObject); // Navigate to 'profile_edit' using its identifier and arguments context.routeOf(identifier: '/profile/edit/123')?.openRoute(); }, child: Text('Go to User Page'), ); } }
When passing arguments during navigation or defining dynamic path parameters (like {uid}), the destination widget needs to retrieve them. Using the InitProvider mixin on your CoreWidget automatically extracts these arguments from the current ModalRoute and populates them into context.args.
class ProfileEditPage extends BaseControlWidget with InitProvider {
@override
Widget build(CoreContext context) {
// Arguments and path parameters are automatically populated
final uid = context.args.get<String>(key: 'uid');
return Scaffold(
appBar: AppBar(title: Text('Edit Profile')),
body: Center(child: Text('Editing user: $uid')),
);
}
}-
ControlBroadcast: Provides an application-wide event stream. You can subscribe to specific event types/keys and broadcast data across your app, decoupled from the widget tree. -
BroadcastProvider: A utility class to easilysubscribeto andbroadcastevents via theControlBroadcastinstance managed byControlFactory.// Subscribe to an event BroadcastProvider.subscribe<int>('on_counter_update', (value) { print('Counter updated to: $value'); }); // Broadcast an event BroadcastProvider.broadcast('on_counter_update', 10);
Flutter Control is part of a larger ecosystem of packages designed to enhance your development workflow:
- Localino: Comprehensive JSON-based localization solution for Flutter, offering dynamic locale management and string formatting.
- Localino Live: Enables Over-The-Air (OTA) translation updates by connecting
Localinoto the localino.app backend. - Localino Builder: Code generation for
Localino, providing type-safe access to translations and automated setup.
Explore the Flutter Control Examples repository for practical demonstrations and more complex solutions using this library.
