An Interest In:
Web News this Week
- April 1, 2024
- March 31, 2024
- March 30, 2024
- March 29, 2024
- March 28, 2024
- March 27, 2024
- March 26, 2024
Dependency Injection with Flutter
You know dependency injection? You love dependency injection!
Unfortunately, Flutter don't provide any built-in DI feature.
For this, I created last year the flutter_catalyst
package with is a port of the catalyst
package which is only supported for Dart native.
flutter_catalyst
was a good starting point for me to implement DI in my Flutter apps but in large projects it's a mess to configure.
In the last two months I created a new package catalyst_builder
which supports all platforms and is easy to configure.
This package uses the build_runner which performs tasks when you run it. catalyst_builder
has a build_runner task that reads annotations from your dart files and generate a service provider for DI.
Setup
Run flutter pub add catalyst_builder
or add the package to your pubspec.yaml
# pubspec.yamldependencies: catalyst_builder: ^1.0.1
Since we use the build_runner you need to add this to your dev_dependencies:
# pubspec.yamldev_dependencies: build_runner: ^2.0.4
Create a build.yaml
beside your pubspec.yaml
. This file contains the configuration for the service provider (output file name and provider class name)
targets: $default: auto_apply_builders: true builders: catalyst_builder|buildServiceProvider: options: providerClassName: 'AppServiceProvider' outputName: 'app_service_provider.dart'
Run flutter pub get
to install the packages
Now run flutter pub pub run build_runner watch --delete-conflicting-outputs
which watches for changes and create the service provider dart file
Usage
You can declare every class as a service with the @Service
annotation from the catalyst_builder
package:
@Service()class MyService { final String username = 'TestUser';}
Ensure that flutter pub pub run build_runner watch --delete-conflicting-outputs
is running. You should see now a app_service_provider.dart
file that you can include in your project.
Create the service provider and retrieve the service from it:
var myProvider = AppServiceProvider();myProvider.boot(); // This is importantvar myService = myProvider.resolve<MyService>();// also works: MyService myService = myProvider.resolve();print(myService.username); // prints TestUser
Thats all for a simple service.
Nested services a.k.a. Dependency Injection
In the real world you've services that depend on other services that depend on configuration parameters etc.
catalyst_builder
also supports this scenario:
@Service()class ServiceA {}@Service()class ServiceB { final ServiceA serviceA; ServiceB(this.ServiceA);}class ServiceC {} @Service()class ServiceD { final ServiceC serviceC; ServiceD(@Parameter('otherService') this.ServiceC);}void main() { var myProvider = AppServiceProvider(); myProvider.boot(); // This works: var serviceB = myProvider.resolve<ServiceB>(); // This not because ServiceC is not known as a service: var serviceD = myProvider.resolve<ServiceD>(); // But this works, because the provider contains a // parameter with the same name as the required argument: myProvider.parameters['serviceC'] = ServiceC(); var serviceD = myProvider.resolve<ServiceD>(); // This also works, because the provider contains a // parameter with the name which is given in the // Parameter annotation. myProvider.parameters['otherService'] = ServiceC(); var serviceD = myProvider.resolve<ServiceD>();}
Service lifetime
By default, all services are singeltons. You will get the same instance everytime you call resolve<T>
.
You can specify the lifetime with the lifetime argument in the @Service
annotation:
/// Transient services are always recreated@Service(lifetime: ServiceLifetime.transient)class TransientService {}/// Default is singleton@Service(lifetime: ServiceLifetime.singleton)class SingletonService {}
Code Against Interfaces, Not Implementations.
Every programmer would tell you that you shouldn't depend on implementations but interfaces.
Also this is possible with the exposeAs
Property in the @Service
annotation. Expose as will return the implementation if you request the type that you provide as exposeAs
. This also works for nested services.
// interfaceabstract class BaseService {}// implementation@Service(exposeAs: BaseService)class MyService implements BaseService {}
Preloading services
Some services are background services (connectivity checks for example).
Decorate this services with @Preload()
to create a instance of the service while boot()
-ing the provider.
@Service()@Preload()class MyService { MyService(){ print('Service was created'); }}void main() { ServiceProvider provider; provider.boot(); // prints "Service was created" provider.resolve<MyService>(); // Nothing printed}
Flutter specific tips:
- Screens (widgets) should be always transient services.
- You can use
resolve<T>
in the router:
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, initialRoute: '/', routes: { '/': (_) => container.resolve<HomeScreen>(), }, ); }}
Hope you like and use the package ;-)
Original Link: https://dev.to/devtronic/dependency-injection-with-flutter-54pl
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To