From 0a8ea34e8323cc3fa82c6ef6afe9b35fcfba3590 Mon Sep 17 00:00:00 2001 From: codeByForam Date: Mon, 25 May 2026 12:43:09 +0530 Subject: [PATCH 1/2] fix(templates): resolve GetX strict analyzer warnings and refactor auth UI to GetView This commit addresses several Dart compilation issues and GetX anti-patterns within the generator templates when `feature-first` and `getx` are selected: 1. UI Architecture Refactor (`GetView`) - Migrated `login_screen`, `signup_screen`, and `forgot_password_screen` from standard `StatelessWidget` to conditionally extend `GetView` when GetX is enabled. - Pushed mutable state (e.g. `obscureText`), `TextEditingController` instances, and `GlobalKey` keys out of the UI and into `AuthController`. - Injected proper `onClose()` cleanup logic into `auth_logic.hbs` to prevent memory leaks. 2. Import & Routing Conflicts - Added `hide ContextExtensionss, Trans, StringExtension;` to the global GetX export in `packages_imports.dart.hbs` to prevent ambiguous conflicts with ScreenUtil and EasyLocalization. - Removed redundant individual screen imports in `app_router.dart.hbs` (GetX section) since `imports.dart` already exposes them. - Fixed `app_bindings.dart.hbs` import path to correctly target the `providers/` directory for `feature-first` instead of `controllers/`. - Updated `app.dart.hbs` to use `imports.dart` instead of `core_imports.dart` so `GetMaterialApp` is properly defined. 3. Strict Type Inference & YAML Fixes - Upgraded `List` to explicit `List>` in `app_router.dart.hbs`. - Upgraded raw navigation calls like `Get.offAllNamed(...)` to explicit `Get.offAllNamed(...)` in `session_listener_wrapper.dart.hbs`. - Wrapped the GetX state-management dependency in `pubspec.yaml.hbs` with an `{{#unless}}` block to prevent fatal YAML `Duplicate mapping key` errors when GetX is used for both State Management and Routing simultaneously. These changes guarantee that generating a GetX + feature-first template compiles cleanly with exactly `0` `flutter analyze` errors. --- templates/flutter/base/lib/src/app.dart.hbs | 2 +- .../lib/src/imports/packages_imports.dart.hbs | 9 +--- .../routing/(isGetX)@app_bindings.dart.hbs | 2 +- .../base/lib/src/routing/app_router.dart.hbs | 49 +++---------------- .../session_listener_wrapper.dart.hbs | 4 +- templates/flutter/base/pubspec.yaml.hbs | 2 + .../partials/features/auth/auth_logic.hbs | 37 ++++++++++++-- .../features/auth/forgot_password_screen.hbs | 38 +++++++++++++- .../partials/features/auth/login_screen.hbs | 46 +++++++++++++++-- .../partials/features/auth/signup_screen.hbs | 49 ++++++++++++++++++- 10 files changed, 174 insertions(+), 64 deletions(-) diff --git a/templates/flutter/base/lib/src/app.dart.hbs b/templates/flutter/base/lib/src/app.dart.hbs index ff77c93..2fb3ac9 100644 --- a/templates/flutter/base/lib/src/app.dart.hbs +++ b/templates/flutter/base/lib/src/app.dart.hbs @@ -1,4 +1,4 @@ -import 'package:{{flags.appSnake}}/src/imports/core_imports.dart'; +import 'package:{{flags.appSnake}}/src/imports/imports.dart'; class App extends StatelessWidget { const App({super.key}); diff --git a/templates/flutter/base/lib/src/imports/packages_imports.dart.hbs b/templates/flutter/base/lib/src/imports/packages_imports.dart.hbs index a2b0e06..a3ba1a8 100644 --- a/templates/flutter/base/lib/src/imports/packages_imports.dart.hbs +++ b/templates/flutter/base/lib/src/imports/packages_imports.dart.hbs @@ -22,14 +22,7 @@ export 'package:provider/provider.dart' hide Dispose; export 'package:flutter_bloc/flutter_bloc.dart'; {{/if}} {{#if (or flags.isGetX (eq flags.routerPackage "getx"))}} -// For Navigation -export 'package:get/get_navigation/get_navigation.dart' hide GetContextExtensions; -// For State Management (.obs, GetxController, Obx) -{{#if flags.isGetX}} -export 'package:get/get_state_manager/get_state_manager.dart'; -// For Dependency Management (Get.put, Get.find) -export 'package:get/get_instance/get_instance.dart'; -{{/if}} +export 'package:get/get.dart' hide ContextExtensionss, Trans, StringExtension; {{/if}} {{#if flags.isMobX}} export 'package:mobx/mobx.dart' hide version, StringExtension, Action, Listener, Listenable, Interceptor, Interceptors; diff --git a/templates/flutter/base/lib/src/routing/(isGetX)@app_bindings.dart.hbs b/templates/flutter/base/lib/src/routing/(isGetX)@app_bindings.dart.hbs index 6c5b3e6..d9d9726 100644 --- a/templates/flutter/base/lib/src/routing/(isGetX)@app_bindings.dart.hbs +++ b/templates/flutter/base/lib/src/routing/(isGetX)@app_bindings.dart.hbs @@ -8,7 +8,7 @@ import 'package:{{flags.appSnake}}/src/controllers/auth/auth_controller.dart'; {{else if (eq architecture "mvvm")}} import 'package:{{flags.appSnake}}/src/ui/auth/controllers/auth_controller.dart'; {{else}} -import 'package:{{flags.appSnake}}/src/features/auth/presentation/controllers/auth_controller.dart'; +import 'package:{{flags.appSnake}}/src/features/auth/presentation/providers/auth_controller.dart'; {{/if}} class AppBindings implements Bindings { diff --git a/templates/flutter/base/lib/src/routing/app_router.dart.hbs b/templates/flutter/base/lib/src/routing/app_router.dart.hbs index 835311d..f3281f2 100644 --- a/templates/flutter/base/lib/src/routing/app_router.dart.hbs +++ b/templates/flutter/base/lib/src/routing/app_router.dart.hbs @@ -108,63 +108,26 @@ class AppRouter extends RootStackRouter { } {{else if (eq flags.routerPackage "getx")}} import 'package:{{flags.appSnake}}/src/imports/imports.dart'; -import 'package:{{flags.appSnake}}/src/routing/app_routes.dart'; - -{{#if (eq architecture "layer-first")}} -import 'package:{{flags.appSnake}}/src/presentation/screens/auth/login_screen.dart'; -import 'package:{{flags.appSnake}}/src/presentation/screens/auth/signup_screen.dart'; -import 'package:{{flags.appSnake}}/src/presentation/screens/auth/forgot_password_screen.dart'; -{{else if (eq architecture "mvc")}} -import 'package:{{flags.appSnake}}/src/views/auth/login_screen.dart'; -import 'package:{{flags.appSnake}}/src/views/auth/signup_screen.dart'; -import 'package:{{flags.appSnake}}/src/views/auth/forgot_password_screen.dart'; -{{else if (eq architecture "mvvm")}} -import 'package:{{flags.appSnake}}/src/ui/auth/login_screen.dart'; -import 'package:{{flags.appSnake}}/src/ui/auth/signup_screen.dart'; -import 'package:{{flags.appSnake}}/src/ui/auth/forgot_password_screen.dart'; -{{else}} -import 'package:{{flags.appSnake}}/src/features/auth/presentation/screens/login_screen.dart'; -import 'package:{{flags.appSnake}}/src/features/auth/presentation/screens/signup_screen.dart'; -import 'package:{{flags.appSnake}}/src/features/auth/presentation/screens/forgot_password_screen.dart'; -{{/if}} - -{{#if (eq architecture "layer-first")}} -import 'package:{{flags.appSnake}}/src/presentation/screens/home/home_page.dart'; -import 'package:{{flags.appSnake}}/src/presentation/screens/onboarding/onboarding_page.dart'; - -{{else if (eq architecture "mvc")}} -import 'package:{{flags.appSnake}}/src/views/home/home_page.dart'; -import 'package:{{flags.appSnake}}/src/views/onboarding/onboarding_page.dart'; - -{{else if (eq architecture "mvvm")}} -import 'package:{{flags.appSnake}}/src/ui/home/home_page.dart'; -import 'package:{{flags.appSnake}}/src/ui/onboarding/onboarding_page.dart'; - -{{else}} -import 'package:{{flags.appSnake}}/src/features/home/presentation/screens/home_page.dart'; -import 'package:{{flags.appSnake}}/src/features/onboarding/presentation/screens/onboarding_page.dart'; - -{{/if}} class AppRouter { - static List get getPages => [ - GetPage( + static List> get getPages => [ + GetPage( name: AppRoutes.onboarding, page: () => const OnboardingPage(), ), - GetPage( + GetPage( name: AppRoutes.login, page: () => const LoginScreen(), ), - GetPage( + GetPage( name: AppRoutes.signup, page: () => const SignupScreen(), ), - GetPage( + GetPage( name: AppRoutes.forgotPassword, page: () => const ForgotPasswordScreen(), ), - GetPage( + GetPage( name: AppRoutes.home, page: () => const HomePage(), ), diff --git a/templates/flutter/base/lib/src/shared/wrappers/session_listener_wrapper.dart.hbs b/templates/flutter/base/lib/src/shared/wrappers/session_listener_wrapper.dart.hbs index 6c35c75..c7063d7 100644 --- a/templates/flutter/base/lib/src/shared/wrappers/session_listener_wrapper.dart.hbs +++ b/templates/flutter/base/lib/src/shared/wrappers/session_listener_wrapper.dart.hbs @@ -116,9 +116,9 @@ class _SessionListenerWrapperState extends State { FlutterNativeSplash.remove(); {{/if}} if (status == SessionStatus.authenticated) { - Get.offAllNamed(AppRoutes.home); + Get.offAllNamed(AppRoutes.home); } else if (status == SessionStatus.unauthenticated) { - Get.offAllNamed(AppRoutes.onboarding); + Get.offAllNamed(AppRoutes.onboarding); } } }, condition: () => session.status.value != SessionStatus.unknown); diff --git a/templates/flutter/base/pubspec.yaml.hbs b/templates/flutter/base/pubspec.yaml.hbs index 90b5e8a..d802437 100644 --- a/templates/flutter/base/pubspec.yaml.hbs +++ b/templates/flutter/base/pubspec.yaml.hbs @@ -45,7 +45,9 @@ dependencies: flutter_bloc: ^9.1.1 {{/if}} {{#if flags.isGetX}} + {{#unless (eq flags.routerPackage "getx")}} get: ^4.7.3 + {{/unless}} {{/if}} {{#if flags.isProvider}} provider: ^6.1.5+1 diff --git a/templates/flutter/partials/features/auth/auth_logic.hbs b/templates/flutter/partials/features/auth/auth_logic.hbs index 584552f..28e8287 100644 --- a/templates/flutter/partials/features/auth/auth_logic.hbs +++ b/templates/flutter/partials/features/auth/auth_logic.hbs @@ -451,6 +451,37 @@ class AuthController extends GetxController { final isLoading = false.obs; + // Login controllers & states + final loginFormKey = GlobalKey(); + final emailController = TextEditingController(); + final passwordController = TextEditingController(); + final obscurePassword = true.obs; + + // SignUp controllers & states + final signUpFormKey = GlobalKey(); + final nameController = TextEditingController(); + final signUpEmailController = TextEditingController(); + final signUpPasswordController = TextEditingController(); + final signUpConfirmPasswordController = TextEditingController(); + final signUpObscurePassword = true.obs; + final signUpObscureConfirmPassword = true.obs; + + // ForgotPassword controllers & states + final forgotPasswordFormKey = GlobalKey(); + final forgotPasswordEmailController = TextEditingController(); + + @override + void onClose() { + emailController.dispose(); + passwordController.dispose(); + nameController.dispose(); + signUpEmailController.dispose(); + signUpPasswordController.dispose(); + signUpConfirmPasswordController.dispose(); + forgotPasswordEmailController.dispose(); + super.onClose(); + } + void login({required BuildContext context, required String email, required String password}) async { isLoading.value = true; @@ -464,7 +495,7 @@ class AuthController extends GetxController { } }, (user) { - Get.offAllNamed(AppRoutes.home); + Get.offAllNamed(AppRoutes.home); }, ); } @@ -482,7 +513,7 @@ class AuthController extends GetxController { } }, (user) { - Get.offAllNamed(AppRoutes.home); + Get.offAllNamed(AppRoutes.home); }, ); } @@ -503,7 +534,7 @@ class AuthController extends GetxController { if (context.mounted) { showToast(context, message: 'Password reset link sent successfully', status: 'success'); } - Get.offNamed(AppRoutes.login); + Get.offNamed(AppRoutes.login); }, ); } diff --git a/templates/flutter/partials/features/auth/forgot_password_screen.hbs b/templates/flutter/partials/features/auth/forgot_password_screen.hbs index 992af62..39181f7 100644 --- a/templates/flutter/partials/features/auth/forgot_password_screen.hbs +++ b/templates/flutter/partials/features/auth/forgot_password_screen.hbs @@ -8,7 +8,7 @@ import 'package:{{flags.appSnake}}/src/{{#if (eq architecture "layer-first")}}pr {{else if flags.isProvider}} import 'package:{{flags.appSnake}}/src/{{#if (eq architecture "layer-first")}}presentation/providers/{{else if (eq architecture "mvc")}}controllers/auth/{{else if (eq architecture "mvvm")}}ui/auth/providers/{{else}}features/auth/presentation/providers/{{/if}}auth_provider.dart'; {{else if flags.isGetX}} -import 'package:{{flags.appSnake}}/src/{{#if (eq architecture "layer-first")}}presentation/controllers/auth/{{else if (eq architecture "mvc")}}controllers/auth/{{else if (eq architecture "mvvm")}}ui/auth/controllers/{{else}}features/auth/presentation/controllers/{{/if}}auth_controller.dart'; +import 'package:{{flags.appSnake}}/src/{{#if (eq architecture "layer-first")}}presentation/controllers/auth/{{else if (eq architecture "mvc")}}controllers/auth/{{else if (eq architecture "mvvm")}}ui/auth/controllers/{{else}}features/auth/presentation/providers/{{/if}}auth_controller.dart'; {{else if flags.isMobX}} {{#if flags.usesFlutterHooks}} import 'package:{{flags.appSnake}}/src/{{#if (eq architecture "layer-first")}}presentation/providers/{{else if (eq architecture "mvc")}}controllers/auth/{{else if (eq architecture "mvvm")}}ui/auth/stores/{{else}}features/auth/presentation/providers/{{/if}}auth_store.dart'; @@ -22,6 +22,41 @@ import 'package:{{flags.appSnake}}/src/{{#if (eq architecture "layer-first")}}pr {{#if (eq flags.routerPackage "auto_route")}} @RoutePage() {{/if}} +{{#if flags.isGetX}} +{{#if (eq flags.routerPackage "auto_route")}} +@RoutePage() +{{/if}} +class ForgotPasswordScreen extends GetView { + const ForgotPasswordScreen({super.key}); + + @override + Widget build(BuildContext context) { + final cs = context.theme.colorScheme; + final tt = context.theme.textTheme; + + Future handleForgotPassword() async { + if (!(controller.forgotPasswordFormKey.currentState?.validate() ?? false)) return; + + controller.forgotPassword( + context: context, + email: controller.forgotPasswordEmailController.text, + ); + } + + return Obx(() { + final isLoading = controller.isLoading.value; + return _ForgotPasswordView( + formKey: controller.forgotPasswordFormKey, + emailController: controller.forgotPasswordEmailController, + isLoading: isLoading, + onForgotPassword: handleForgotPassword, + cs: cs, + tt: tt, + ); + }); + } +} +{{else}} {{#if flags.usesFlutterHooks}} class ForgotPasswordScreen extends {{#if flags.isRiverpod}}HookConsumerWidget{{else}}HookWidget{{/if}} { const ForgotPasswordScreen({super.key}); @@ -274,6 +309,7 @@ class _ForgotPasswordScreenState extends {{#if flags.isRiverpod}}ConsumerState { + const LoginScreen({super.key}); + + @override + Widget build(BuildContext context) { + final cs = context.theme.colorScheme; + final tt = context.theme.textTheme; + + Future handleLogin() async { + if (!(controller.loginFormKey.currentState?.validate() ?? false)) return; + + controller.login( + context: context, + email: controller.emailController.text, + password: controller.passwordController.text, + ); + } + + return Obx(() { + final isLoading = controller.isLoading.value; + return _LoginView( + formKey: controller.loginFormKey, + emailController: controller.emailController, + passwordController: controller.passwordController, + obscurePassword: controller.obscurePassword.value, + isLoading: isLoading, + onToggleObscure: () => controller.obscurePassword.toggle(), + onLogin: handleLogin, + cs: cs, + tt: tt, + ); + }); + } +} +{{else}} {{#if flags.usesFlutterHooks}} class LoginScreen extends {{#if flags.isRiverpod}}HookConsumerWidget{{else}}HookWidget{{/if}} { const LoginScreen({super.key}); @@ -300,6 +339,7 @@ class _LoginScreenState extends {{#if flags.isRiverpod}}ConsumerState(AppRoutes.forgotPassword); {{else}} Navigator.pushNamed(context, AppRoutes.forgotPassword); {{/if}} @@ -509,7 +549,7 @@ class _LoginView extends StatelessWidget { {{else if (eq flags.routerPackage "auto_route")}} context.pushRoute(const SignupRoute()); {{else if flags.isGetX}} - Get.toNamed(AppRoutes.signup); + Get.toNamed(AppRoutes.signup); {{else}} Navigator.pushNamed(context, AppRoutes.signup); {{/if}} diff --git a/templates/flutter/partials/features/auth/signup_screen.hbs b/templates/flutter/partials/features/auth/signup_screen.hbs index df4970e..141e736 100644 --- a/templates/flutter/partials/features/auth/signup_screen.hbs +++ b/templates/flutter/partials/features/auth/signup_screen.hbs @@ -8,7 +8,7 @@ import 'package:{{flags.appSnake}}/src/{{#if (eq architecture "layer-first")}}pr {{else if flags.isProvider}} import 'package:{{flags.appSnake}}/src/{{#if (eq architecture "layer-first")}}presentation/providers/{{else if (eq architecture "mvc")}}controllers/auth/{{else if (eq architecture "mvvm")}}ui/auth/providers/{{else}}features/auth/presentation/providers/{{/if}}auth_provider.dart'; {{else if flags.isGetX}} -import 'package:{{flags.appSnake}}/src/{{#if (eq architecture "layer-first")}}presentation/controllers/auth/{{else if (eq architecture "mvc")}}controllers/auth/{{else if (eq architecture "mvvm")}}ui/auth/controllers/{{else}}features/auth/presentation/controllers/{{/if}}auth_controller.dart'; +import 'package:{{flags.appSnake}}/src/{{#if (eq architecture "layer-first")}}presentation/controllers/auth/{{else if (eq architecture "mvc")}}controllers/auth/{{else if (eq architecture "mvvm")}}ui/auth/controllers/{{else}}features/auth/presentation/providers/{{/if}}auth_controller.dart'; {{else if flags.isMobX}} {{#if flags.usesFlutterHooks}} import 'package:{{flags.appSnake}}/src/{{#if (eq architecture "layer-first")}}presentation/providers/{{else if (eq architecture "mvc")}}controllers/auth/{{else if (eq architecture "mvvm")}}ui/auth/stores/{{else}}features/auth/presentation/providers/{{/if}}auth_store.dart'; @@ -22,6 +22,50 @@ import 'package:{{flags.appSnake}}/src/{{#if (eq architecture "layer-first")}}pr {{#if (eq flags.routerPackage "auto_route")}} @RoutePage() {{/if}} +{{#if flags.isGetX}} +{{#if (eq flags.routerPackage "auto_route")}} +@RoutePage() +{{/if}} +class SignupScreen extends GetView { + const SignupScreen({super.key}); + + @override + Widget build(BuildContext context) { + final cs = context.theme.colorScheme; + final tt = context.theme.textTheme; + + Future handleSignup() async { + if (!(controller.signUpFormKey.currentState?.validate() ?? false)) return; + + controller.signUp( + context: context, + name: controller.nameController.text, + email: controller.signUpEmailController.text, + password: controller.signUpPasswordController.text, + ); + } + + return Obx(() { + final isLoading = controller.isLoading.value; + return _SignupView( + formKey: controller.signUpFormKey, + nameController: controller.nameController, + emailController: controller.signUpEmailController, + passwordController: controller.signUpPasswordController, + confirmPasswordController: controller.signUpConfirmPasswordController, + obscurePassword: controller.signUpObscurePassword.value, + obscureConfirmPassword: controller.signUpObscureConfirmPassword.value, + isLoading: isLoading, + onToggleObscure: () => controller.signUpObscurePassword.toggle(), + onToggleConfirmObscure: () => controller.signUpObscureConfirmPassword.toggle(), + onSignup: handleSignup, + cs: cs, + tt: tt, + ); + }); + } +} +{{else}} {{#if flags.usesFlutterHooks}} class SignupScreen extends {{#if flags.isRiverpod}}HookConsumerWidget{{else}}HookWidget{{/if}} { const SignupScreen({super.key}); @@ -332,6 +376,7 @@ class _SignupScreenState extends {{#if flags.isRiverpod}}ConsumerState(AppRoutes.login); {{else}} Navigator.pushNamed(context, AppRoutes.login); {{/if}} From a524c2d37f46c166f064465211c0f6ea2c3201b5 Mon Sep 17 00:00:00 2001 From: codeByForam Date: Mon, 25 May 2026 13:10:50 +0530 Subject: [PATCH 2/2] fix(templates): remove duplicate auto_route annotation inside isGetX block --- .../flutter/partials/features/auth/forgot_password_screen.hbs | 3 --- templates/flutter/partials/features/auth/login_screen.hbs | 3 --- templates/flutter/partials/features/auth/signup_screen.hbs | 3 --- 3 files changed, 9 deletions(-) diff --git a/templates/flutter/partials/features/auth/forgot_password_screen.hbs b/templates/flutter/partials/features/auth/forgot_password_screen.hbs index 39181f7..b418ad6 100644 --- a/templates/flutter/partials/features/auth/forgot_password_screen.hbs +++ b/templates/flutter/partials/features/auth/forgot_password_screen.hbs @@ -23,9 +23,6 @@ import 'package:{{flags.appSnake}}/src/{{#if (eq architecture "layer-first")}}pr @RoutePage() {{/if}} {{#if flags.isGetX}} -{{#if (eq flags.routerPackage "auto_route")}} -@RoutePage() -{{/if}} class ForgotPasswordScreen extends GetView { const ForgotPasswordScreen({super.key}); diff --git a/templates/flutter/partials/features/auth/login_screen.hbs b/templates/flutter/partials/features/auth/login_screen.hbs index feec958..fb32972 100644 --- a/templates/flutter/partials/features/auth/login_screen.hbs +++ b/templates/flutter/partials/features/auth/login_screen.hbs @@ -23,9 +23,6 @@ import 'package:{{flags.appSnake}}/src/{{#if (eq architecture "layer-first")}}pr @RoutePage() {{/if}} {{#if flags.isGetX}} -{{#if (eq flags.routerPackage "auto_route")}} -@RoutePage() -{{/if}} class LoginScreen extends GetView { const LoginScreen({super.key}); diff --git a/templates/flutter/partials/features/auth/signup_screen.hbs b/templates/flutter/partials/features/auth/signup_screen.hbs index 141e736..572201a 100644 --- a/templates/flutter/partials/features/auth/signup_screen.hbs +++ b/templates/flutter/partials/features/auth/signup_screen.hbs @@ -23,9 +23,6 @@ import 'package:{{flags.appSnake}}/src/{{#if (eq architecture "layer-first")}}pr @RoutePage() {{/if}} {{#if flags.isGetX}} -{{#if (eq flags.routerPackage "auto_route")}} -@RoutePage() -{{/if}} class SignupScreen extends GetView { const SignupScreen({super.key});