Lifecycle Provider
The LifecycleProvider is built upon the LifecycleOwner, a mixin that provides fine-grained control over a widget’s lifecycle.While LifecycleOwner focuses on managing lifecycle events, dependency scopes, and observing live data, LifecycleProvider encapsulates this functionality into a reusable widget.
This encapsulation makes it easier to integrate lifecycle management and dependency injection directly into the UI, providing a cleaner and more maintainable structure for Flutter applications.
Key Features:
- Encapsulates the functionality of LifecycleOwner for easier reuse.
- Automatically manages dependency scope registration and cleanup.
- Simplifies the lifecycle callbacks with onInit, onReady, and onDispose.
- Integrates seamlessly with ViewModel and live data observers.
- Reduces boilerplate code, allowing developers to focus on business logic.
How It Works:
The LifecycleOwner mixin provides the foundation for managing a widget’s lifecycle. It handles:- Initializing and disposing of dependencies via DiScope.
- Observing changes in live data through ObserverMixin.
- Registering lifecycle events (onInitState, onReady, and onDispose)
LifecycleProvider
The LifecycleProvider encapsulates the LifecycleOwner logic into a StatefulWidget, allowing developers to manage lifecycles declaratively.Instead of directly using LifecycleOwner, the LifecycleProvider enables you to define your dependencies, lifecycle events, and UI in a single place.
/// manage widget life-cycle with your state-holder (viewModel) and observing data,
/// and drop passed scope when widget destroyed.
class LifecycleProvider<T extends BaseViewModel> extends StatefulWidget {
/// [DiScope] that [LifecycleOwner] will take care, register it on [initState] and drop it on [dispose]
final DiScope diScope;
/// [builder] is Your UI Tree after registering [diScope]
/// use your [T] registered [instance] inside the tree
final Widget Function(BuildContext context, T instance) builder;
/// [onInit] is callback fired immediately after the widget is allocated in memory.
final Function(BuildContext context, T instance)? onInit;
/// [onReady] is callback fired 1 frame after onInit(). It is the perfect place to enter
/// navigation, events, like snackBar, dialogs, or a new route
final Function(BuildContext context, T instance)? onReady;
/// [onDispose] is callback fired when widget destroyed.
final Function(BuildContext context, T instance)? onDispose;
const LifecycleProvider({
required this.diScope,
required this.builder,
this.onInit,
this.onReady,
this.onDispose,
super.key,
});
}
Implement LifecycleProvider:
class LoginScreen extends StatelessWidget {
const LoginScreen({super.key});
@override
Widget build(BuildContext context) {
return LifecycleProvider<LoginViewModel>(
diScope: DiScope(
name: 'login', // same of @LazySingleton scope name
factory: () => GetIt.instance.initLoginScope(), // 'initLoginScope()' generated by injectable generator
dependencies: [
// add 'login' scope required dependencies (list of DiScope) if exist, such as 'authentication' repository or data-source scopes
],
),
onInit: (context, viewModel) {
print('widget initialized');
};
onReady: (context, viewModel) {
print('widget 1st frame rendered');
};
onDispose: (context, viewModel) {
print('widget disposed');
};
builder: (BuildContext context, LoginViewModel viewModel) {
return Container(); /* UI Tree */
},
);
}
}
When to Use LifecycleProvider vs LifecycleOwner:
The choice between LifecycleProvider and LifecycleOwner depends on the complexity of your widget’s logic and lifecycle requirements.LifecycleOwner
Use LifecycleOwner when you need direct control over lifecycle events, particularly in scenarios where you need to:- Handle complex logic: Managing navigation, listening for real-time events, or handling data changes.
- React to live data updates: Managing navigation, listening for real-time events, or handling data changes.
- Customize lifecycle behavior: Directly observe and handle changes in live data variables.
Example Use Case:
- A widget that displays real-time updates (e.g., a live sports score app).
- Navigating to another screen or showing dialogs based on events.
- Widgets with highly interactive behaviors like animations triggered by data updates.
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> with LifecycleOwner<LoginScreen, LoginViewModel>, ObserverMixin {
@override
void observeChanges(ObserverMixin observer) {
viewModel.loginResponse.observe(observer, (LoginResponse response) {
// navigate to home when login success
if (response != null) _navigateHome();
}
}
// navigate to home screen
void _navigateHome() {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => HomeScreen()),
);
}
}
LifecycleProvider
Use LifecycleProvider when your widget has simple lifecycle requirements or you want to encapsulate lifecycle handling for cleaner integration with the UI.- No complex logic: When your widget doesn’t require navigation, listening to events, or managing intricate data flows.
- Focus on UI building: When the main goal is rendering UI based on a ViewModel without extensive lifecycle customization.
- Quick integration: When you want to manage dependencies and live data without manually implementing lifecycle callbacks.
Example Use Case:
- A widget that displays static or moderately dynamic data.
- Simple state management based on a ViewModel.
- Reducing boilerplate in standard widget implementations.
class ProductScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return LifecycleProvider<ProductViewModel>(
diScope: DiScope(
name: 'products',
factory: getIt.initProductsScope,
),
builder: (BuildContext context, ProductViewModel viewModel) {
return LiveDataBuilder<bool>(
data: viewModel.loading,
builder: (BuildContext context, bool loading) {
// display loading
if (loading) return const CircularProgressIndicator.adaptive();
// display product info
return LiveDataBuilder<Product>(
data: viewModel.product,
builder: (context, product) => Column(
children: [
Image.network(product.image),
Text(product.title),
],
),
);
},
);
},
);
}
}
Guidelines for Choosing
Scenario | Use |
---|---|
Need direct control over lifecycle callbacks | LifecycleOwner |
Require complex navigation or event handling | LifecycleOwner |
Observing and reacting to live data changes | LifecycleOwner |
Only rendering UI with a ViewModel instance | LifecycleProvider |
Minimal or no custom lifecycle logic required | LifecycleProvider |
Quick setup for dependency management in a widget | LifecycleProvider |