Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

class that bind with singleton in core shared module should not recreate #794

Open
fullflash opened this issue Sep 21, 2022 · 16 comments
Open
Labels
new New issue request attention

Comments

@fullflash
Copy link

We need as described in docs.

class AppModule extends Module {
@OverRide
List 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 ?

@fullflash fullflash added the new New issue request attention label Sep 21, 2022
@eduardoflorence
Copy link

Show more parts of your code so we can reproduce the issue

@fullflash
Copy link
Author

you can see in the screenshots Dio client instantiated two times on app start.

Screen Shot 2022-09-22 at 21 41 50

Screen Shot 2022-09-22 at 21 41 18

CoreModule that binds Dio client used only in AppModule imports

class AppModule extends Module {
  @override
  List<Module> get imports => [CoreModule()];

@guilherme-ma9
Copy link

guilherme-ma9 commented Aug 31, 2023

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) {
    ...
  }
}

image

Screenshot notes:

  • The # are the hashCode of each instance
  • The instances listed below SplashPage are Modular.get calls executed within main after runApp
  • The instances listed below of AccessLoginPage are dependencies from the Modular.get<AccessLoginController>() (last log) inside the page AccessLoginPage within the LoginModule.

@eduardoflorence
Copy link

eduardoflorence commented Sep 1, 2023

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}')),
    );
  }
}

@eduardoflorence
Copy link

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.

@guilherme-ma9
Copy link

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.

log
pubspec.yaml
pubspec.lock

@eduardoflorence
Copy link

@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?

@davidsdearaujo
Copy link
Member

davidsdearaujo commented Sep 6, 2023

@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.

@guilherme-ma9
Copy link

It's working normally now

@agustin-garcia
Copy link

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 get exportedBinds => [
Bind.instance(AuthenticationManager.new),
];

and

@OverRide
void exportedBinds(i) {
i.addLazySingleton(AuthenticationManager.new);
}

I can't migrate to versino 6 because many dependencies my project has.

@eduardoflorence
Copy link

@agustin-garcia, in version 5:

@override
List<Bind> get binds => [
  Bind.singleton((i) => AuthenticationManager(), export: true),
];

@agustin-garcia
Copy link

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 get binds => [
Bind((i) => AuthenticationManager(), export: true),
Bind((i) => StorageSettingsController(), export: true),
];
}

class AppModule extends Module {
@OverRide
List get imports => [CoreModule()];
}

class LoginModule extends Module {
@OverRide
List get imports => [CoreModule()];

@OverRide
List get routes => [
ChildRoute('/', child: (_, __) => LoginWidget()),
];
}

Then in the LoginWidget, I call it like:
final authMgr = Modular.get();
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

@eduardoflorence
Copy link

Use .singleton:

@override
List<Bind> get binds => [
  Bind.singleton((i) => AuthenticationManager(), export: true),
  Bind.singleton((i) => StorageSettingsController(), export: true),
];

@agustin-garcia
Copy link

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().isLogged = true;
print("isLogged: ${Modular.get().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 get binds => [Bind.singleton((i) => AuthenticationManager(), export: true),];
}

class AppModule extends Module {
@OverRide
List get imports => [CoreModule()];
@OverRide
List get routes => [
ChildRoute('/', child: (_, __) => const AppRoot(), guards: [AuthGuard()]),
ModuleRoute(AppPaths.home, module: HomeModule()),
ModuleRoute(AppPaths.login, module: LoginModule()),
];
}

class LoginModule extends Module {
@OverRide
List get imports => [CoreModule()];
@OverRide
List get routes => [ChildRoute('/', child: (_, __) => LoginWidget()),];
}

class HomeModule extends Module {
@OverRide
List get imports => [CoreModule()];
@OverRide
List get routes => [ChildRoute('/', child: (_, __) => const HomePage()),];
}

class AuthGuard extends RouteGuard {
AuthGuard() : super(redirectTo: AppPaths.login);
@OverRide
Future canActivate(String url, ModularRoute router) async {
return Modular.get().isLogged;
}
}
`

@agustin-garcia
Copy link

Any guidance on this or where else to look for it?

@edugemini
Copy link
Contributor

@agustin-garcia, do it:

Modular.get<AuthenticationManager>().isLogged

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new New issue request attention
Projects
None yet
Development

No branches or pull requests

6 participants