modular
modular copied to clipboard
class that bind with singleton in core shared module should not recreate
We need as described in docs.
class AppModule extends Module { @override List<Module> get imports => [CoreModule()];
and inside core module injecting Dio instance as in examples
but when getting this httclient via Modular.get() the dio instance class construction called again.
so what is should we do in order to get a real singleton binding ?
Show more parts of your code so we can reproduce the issue
you can see in the screenshots Dio client instantiated two times on app start.


CoreModule that binds Dio client used only in AppModule imports
class AppModule extends Module {
@override
List<Module> get imports => [CoreModule()];
I'm having the same issue with Modular 6.3.0
.
Part of my code:
void main() async {
runApp(
ModularApp(
module: AppModule(),
child: const AppWidget(),
),
);
await Hive.initFlutter();
await Modular.get<CacheLoginService>().openBox();
await Modular.get<AuthController>().loadCachedUser();
await Future.delayed(const Duration(seconds: 5));
Modular.to.navigate('/login/');
}
class CoreModule extends Module {
@override
void exportedBinds(i) {
i.addInstance(Dio());
i.addLazySingleton<IClientHttp>(ClapsClientApi.new);
i.addLazySingleton<AuthService>(AuthService.new);
i.addLazySingleton<AuthController>(AuthController.new);
i.addLazySingleton<EncryptedDBService>(EncryptedDBService.new);
i.addLazySingleton<CacheLoginService>(CacheLoginService.new);
}
}
class AppModule extends Module {
@override
List<Module> get imports => [CoreModule()];
@override
void routes(r) {
...
}
}
class LoginModule extends Module {
@override
List<Module> get imports => [CoreModule()];
@override
void binds(i) {
// new dependencies different from CoreModule
...
}
@override
void routes(r) {
...
}
}
Screenshot notes:
- The
#
are the hashCode of each instance - The instances listed below
SplashPage
areModular.get
calls executed withinmain
afterrunApp
- The instances listed below of
AccessLoginPage
are dependencies from theModular.get<AccessLoginController>()
(last log) inside the pageAccessLoginPage
within theLoginModule
.
Complete code to help test the issue. AuthServiceDefault is instantiated again on every import.
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
void main() async {
runApp(ModularApp(module: AppModule(), child: const AppWidget()));
await Future.delayed(const Duration(seconds: 2));
Modular.to.navigate('/auth/');
}
class Counter {
int count = 0;
Counter() {
print('****INSTANCE: $runtimeType - $hashCode');
}
void add() {
count++;
}
}
class CoreModule extends Module {
@override
void exportedBinds(Injector i) {
i.addSingleton<AuthService>(AuthServiceDefault.new);
i.addSingleton(Counter.new);
}
}
class AppModule extends Module {
@override
List<Module> get imports => [CoreModule()];
@override
void routes(RouteManager r) {
r.child('/', child: (context) => const SplashScreen());
r.module('/auth', module: AuthModule());
r.module('/home', module: HomeModule());
}
}
class AppWidget extends StatelessWidget {
const AppWidget({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: Modular.routerConfig,
);
}
}
class SplashScreen extends StatelessWidget {
const SplashScreen({super.key});
@override
Widget build(BuildContext context) {
return const Material(child: Center(child: CircularProgressIndicator.adaptive()));
}
}
// Auth
class AuthModule extends Module {
@override
List<Module> get imports => [CoreModule()];
@override
void binds(Injector i) {
i.addLazySingleton(AuthController.new);
}
@override
void routes(RouteManager r) {
r.child('/', child: (context) => LoginPage(authController: Modular.get()));
}
}
abstract interface class AuthService {
Future<bool> login();
}
class AuthServiceDefault implements AuthService {
AuthServiceDefault() {
print('***INSTANCE: $runtimeType - $hashCode');
}
@override
Future<bool> login() async {
await Future.delayed(const Duration(seconds: 1));
return true;
}
}
class AuthController {
AuthController({required this.authService});
final AuthService authService;
Future<bool> login() async {
return authService.login();
}
}
class LoginPage extends StatelessWidget {
const LoginPage({super.key, required this.authController});
final AuthController authController;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () async {
final counter = Modular.get<Counter>();
counter.add();
counter.add();
counter.add();
await authController.login();
Modular.to.navigate('/home/');
},
child: const Text('Login'),
),
),
);
}
}
// Home
class HomeModule extends Module {
@override
List<Module> get imports => [CoreModule()];
@override
void routes(RouteManager r) {
r.child('/', child: (context) => HomePage(counter: Modular.get()));
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key, required this.counter});
final Counter counter;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: Text('Home ${counter.count} ${counter.hashCode}')),
);
}
}
Hi @guilherme-ma9,
In Modular 6.3.1 the problem with addLazySingleton
in CoreModule
is not happening.
The problem happens if we use addSingleton
, but it's just a side effect, as the first instance will be shared by all imports.
Use my code above and you will see that the same Counter instance is being shared.
Hi @eduardoflorence,
The problem still remains.
I have a dev.log
inside each constructor, which means that every time a new object of the class is instantiated, it will call this log.
@guilherme-ma9, Can you provide a complete code for us to reproduce the problem? Or are you able to change the code I provided above to reproduce the problem? Did you see that although there is more than one instance, it is always the first one that is used in all imports?
@guilherme-ma9 try to log your binds instead your imported modules. The imported modules are instantiated again, but there is a validation that gets the first instance created. The instance that wasn't used is removed by the Garbage Collector.
It's working normally now
Hello, How do you apply this same concept for version 5? I'm trying to replicate but its telling me that "The method doesn't override an inherited method."
I tried with both: List<Bind> get exportedBinds => [ Bind.instance(AuthenticationManager.new), ];
and
@override void exportedBinds(i) { i.addLazySingleton<AuthenticationManager>(AuthenticationManager.new); }
I can't migrate to versino 6 because many dependencies my project has.
@agustin-garcia, in version 5:
@override
List<Bind> get binds => [
Bind.singleton((i) => AuthenticationManager(), export: true),
];
Hello, How do you apply this same concept for version 5? I'm trying to replicate but its telling me that "The method doesn't override an inherited method."
class CoreModule extends Module { @override List<Bind> get binds => [ Bind<AuthenticationManager>((i) => AuthenticationManager(), export: true), Bind<StorageSettingsController>((i) => StorageSettingsController(), export: true), ]; }
class AppModule extends Module { @override List<Module> get imports => [CoreModule()]; }
class LoginModule extends Module { @override List<Module> get imports => [CoreModule()];
@override List<ModularRoute> get routes => [ ChildRoute('/', child: (_, __) => LoginWidget()), ]; }
Then in the LoginWidget, I call it like: final authMgr = Modular.get<AuthenticationManager>(); authMgr.X = 'X';
The setting of properties work while in the same module, but if I move to another module, the Object's properties are all null. What do I need to do for changes on a child Module are reflected in the CoreModule's object so that all Modules can update/read the properties? Thanks
Use .singleton
:
@override
List<Bind> get binds => [
Bind.singleton((i) => AuthenticationManager(), export: true),
Bind.singleton((i) => StorageSettingsController(), export: true),
];
Thanks Eduardo, I tried as per your suggestion, but unfortunately I'm still missing something, because when I try the code below, within the LOGIN module I can do: Modular.get<AuthenticationManager>().isLogged = true; print("isLogged: ${Modular.get<AuthenticationManager>().isLogged}"); and everything seems all right.
But as soon as I navigate to the HOME module from the LOGIN module via the "Modular.to.pushReplacementNamed(AppPaths.home);" instruction, the AuthenticationManager properties become NULL again
Could you please look at the code snippet and try to find out what am I missing?
Thanks again
` class CoreModule extends Module { @override List<Bind> get binds => [Bind.singleton((i) => AuthenticationManager(), export: true),]; }
class AppModule extends Module { @override List<Module> get imports => [CoreModule()]; @override List<ModularRoute> get routes => [ ChildRoute('/', child: (_, __) => const AppRoot(), guards: [AuthGuard()]), ModuleRoute(AppPaths.home, module: HomeModule()), ModuleRoute(AppPaths.login, module: LoginModule()), ]; }
class LoginModule extends Module { @override List<Module> get imports => [CoreModule()]; @override List<ModularRoute> get routes => [ChildRoute('/', child: (_, __) => LoginWidget()),]; }
class HomeModule extends Module { @override List<Module> get imports => [CoreModule()]; @override List<ModularRoute> get routes => [ChildRoute('/', child: (_, __) => const HomePage()),]; }
class AuthGuard extends RouteGuard {
AuthGuard() : super(redirectTo: AppPaths.login);
@override
Future
}
}
`
Any guidance on this or where else to look for it?
@agustin-garcia, do it:
Modular.get<AuthenticationManager>().isLogged