Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/components/wizard/PackageInfoPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ const PACKAGE_INFO: Record<string, {
title: "MobX",
packageName: "flutter_mobx",
},
get_it: {
title: "GetIt",
packageName: "get_it",
},
go_router: {
title: "go_router",
packageName: "go_router",
Expand Down
1 change: 1 addition & 0 deletions app/components/wizard/steps/GenerateStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export function GenerateStep({ error, isGenerating }: { error: string | null, is
<SummaryItem label="Theme" value={config.theme.preset} />
<SummaryItem label="Architecture" value={config.architecture} />
<SummaryItem label="State" value={config.stateManagement} />
<SummaryItem label="Dependency Injection" value={config.dependencyInjection} />
<SummaryItem label="Navigation" value={config.navigation} />
<SummaryItem label="Backend" value={config.backend.provider} />
<SummaryTagItem
Expand Down
51 changes: 49 additions & 2 deletions app/components/wizard/steps/MiscStep.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client"

import { MiscConfig } from "@/app/lib/config/schema"
import { DependencyInjectionStyle, MiscConfig, dependencyInjectionOptions } from "@/app/lib/config/schema"
import { useWizard } from "@/app/lib/state/useWizardStore"
import {
Accordion,
Expand All @@ -9,11 +9,13 @@ import {
AccordionTrigger,
} from "@/components/ui/accordion"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { cn } from "@/lib/utils"
import {
AiImageIcon,
CloudIcon,
DatabaseIcon,
InformationCircleIcon,
PackageIcon,
SmartPhone01Icon,
SourceCodeIcon,
Expand All @@ -36,7 +38,7 @@ interface Category {
}

export function MiscStep() {
const { config, updateConfig, next, prev } = useWizard()
const { config, updateConfig, setSelectedItem } = useWizard()
const { misc } = config

const handleToggle = (key: keyof MiscConfig, value: boolean) => {
Expand Down Expand Up @@ -232,6 +234,51 @@ export function MiscStep() {
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="rounded-xl border border-border/40 bg-card/30 p-4 space-y-3">
<div>
<p className="text-sm font-semibold text-foreground/90">Dependency Injection</p>
<p className="text-xs text-muted-foreground">Choose how dependencies are wired in generated code.</p>
</div>
<RadioGroup
value={config.dependencyInjection}
onValueChange={(value) => {
updateConfig({ dependencyInjection: value as DependencyInjectionStyle })
}}
className="grid gap-2"
>
{dependencyInjectionOptions.map((option) => (
<label
key={option.value}
className={cn(
"flex items-center justify-between rounded-xl border p-3 cursor-pointer transition-all duration-200",
config.dependencyInjection === option.value
? "border-primary/50 bg-primary/5 shadow-md shadow-primary/5 ring-1 ring-primary/20"
: "border-border/40 bg-card/30 hover:bg-card/50 hover:border-primary/20"
)}
>
<div className="flex items-center gap-3">
<RadioGroupItem value={option.value} className="text-primary border-primary" />
<div className="space-y-1">
<span className="text-sm font-semibold text-foreground/90">{option.label}</span>
<p className="text-xs text-muted-foreground">{option.description}</p>
</div>
</div>
<button
type="button"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
setSelectedItem(option.value)
}}
className="p-1.5 rounded-full hover:bg-primary/20 text-muted-foreground hover:text-primary transition-colors focus:outline-hidden cursor-pointer"
title="View details"
>
<HugeiconsIcon icon={InformationCircleIcon} size={18} />
</button>
</label>
))}
Comment on lines +249 to +279
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid nesting a <button> inside a <label> (invalid HTML / a11y).

The HTML spec disallows interactive descendants other than a single labelable control inside <label>. Nesting the info <button> inside the label can trigger accessibility warnings and inconsistent click behavior across browsers (some will still activate the radio even with preventDefault/stopPropagation). Prefer a sibling layout: wrap the row in a <div> and keep the <label> scoped to the radio + text only.

♻️ Proposed refactor
-                        {dependencyInjectionOptions.map((option) => (
-                            <label
-                                key={option.value}
-                                className={cn(
-                                    "flex items-center justify-between rounded-xl border p-3 cursor-pointer transition-all duration-200",
-                                    config.dependencyInjection === option.value
-                                        ? "border-primary/50 bg-primary/5 shadow-md shadow-primary/5 ring-1 ring-primary/20"
-                                        : "border-border/40 bg-card/30 hover:bg-card/50 hover:border-primary/20"
-                                )}
-                            >
-                                <div className="flex items-center gap-3">
-                                    <RadioGroupItem value={option.value} className="text-primary border-primary" />
-                                    <div className="space-y-1">
-                                        <span className="text-sm font-semibold text-foreground/90">{option.label}</span>
-                                        <p className="text-xs text-muted-foreground">{option.description}</p>
-                                    </div>
-                                </div>
-                                <button
-                                    type="button"
-                                    onClick={(e) => {
-                                        e.preventDefault()
-                                        e.stopPropagation()
-                                        setSelectedItem(option.value)
-                                    }}
-                                    className="p-1.5 rounded-full hover:bg-primary/20 text-muted-foreground hover:text-primary transition-colors focus:outline-hidden cursor-pointer"
-                                    title="View details"
-                                >
-                                    <HugeiconsIcon icon={InformationCircleIcon} size={18} />
-                                </button>
-                            </label>
-                        ))}
+                        {dependencyInjectionOptions.map((option) => {
+                            const id = `di-${option.value}`
+                            const selected = config.dependencyInjection === option.value
+                            return (
+                                <div
+                                    key={option.value}
+                                    className={cn(
+                                        "flex items-center justify-between rounded-xl border p-3 transition-all duration-200",
+                                        selected
+                                            ? "border-primary/50 bg-primary/5 shadow-md shadow-primary/5 ring-1 ring-primary/20"
+                                            : "border-border/40 bg-card/30 hover:bg-card/50 hover:border-primary/20"
+                                    )}
+                                >
+                                    <label htmlFor={id} className="flex items-center gap-3 cursor-pointer flex-1">
+                                        <RadioGroupItem id={id} value={option.value} className="text-primary border-primary" />
+                                        <div className="space-y-1">
+                                            <span className="text-sm font-semibold text-foreground/90">{option.label}</span>
+                                            <p className="text-xs text-muted-foreground">{option.description}</p>
+                                        </div>
+                                    </label>
+                                    <button
+                                        type="button"
+                                        aria-label={`View details for ${option.label}`}
+                                        onClick={() => setSelectedItem(option.value)}
+                                        className="p-1.5 rounded-full hover:bg-primary/20 text-muted-foreground hover:text-primary transition-colors focus:outline-hidden cursor-pointer"
+                                        title="View details"
+                                    >
+                                        <HugeiconsIcon icon={InformationCircleIcon} size={18} />
+                                    </button>
+                                </div>
+                            )
+                        })}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{dependencyInjectionOptions.map((option) => (
<label
key={option.value}
className={cn(
"flex items-center justify-between rounded-xl border p-3 cursor-pointer transition-all duration-200",
config.dependencyInjection === option.value
? "border-primary/50 bg-primary/5 shadow-md shadow-primary/5 ring-1 ring-primary/20"
: "border-border/40 bg-card/30 hover:bg-card/50 hover:border-primary/20"
)}
>
<div className="flex items-center gap-3">
<RadioGroupItem value={option.value} className="text-primary border-primary" />
<div className="space-y-1">
<span className="text-sm font-semibold text-foreground/90">{option.label}</span>
<p className="text-xs text-muted-foreground">{option.description}</p>
</div>
</div>
<button
type="button"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
setSelectedItem(option.value)
}}
className="p-1.5 rounded-full hover:bg-primary/20 text-muted-foreground hover:text-primary transition-colors focus:outline-hidden cursor-pointer"
title="View details"
>
<HugeiconsIcon icon={InformationCircleIcon} size={18} />
</button>
</label>
))}
{dependencyInjectionOptions.map((option) => {
const id = `di-${option.value}`
const selected = config.dependencyInjection === option.value
return (
<div
key={option.value}
className={cn(
"flex items-center justify-between rounded-xl border p-3 transition-all duration-200",
selected
? "border-primary/50 bg-primary/5 shadow-md shadow-primary/5 ring-1 ring-primary/20"
: "border-border/40 bg-card/30 hover:bg-card/50 hover:border-primary/20"
)}
>
<label htmlFor={id} className="flex items-center gap-3 cursor-pointer flex-1">
<RadioGroupItem id={id} value={option.value} className="text-primary border-primary" />
<div className="space-y-1">
<span className="text-sm font-semibold text-foreground/90">{option.label}</span>
<p className="text-xs text-muted-foreground">{option.description}</p>
</div>
</label>
<button
type="button"
aria-label={`View details for ${option.label}`}
onClick={() => setSelectedItem(option.value)}
className="p-1.5 rounded-full hover:bg-primary/20 text-muted-foreground hover:text-primary transition-colors focus:outline-hidden cursor-pointer"
title="View details"
>
<HugeiconsIcon icon={InformationCircleIcon} size={18} />
</button>
</div>
)
})}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/wizard/steps/MiscStep.tsx` around lines 249 - 279, The info
<button> is nested inside the <label>, which is invalid and breaks a11y;
refactor the map so each option is a wrapper <div> (or other non-interactive
container) that contains a <label> scoped only to the RadioGroupItem and its
text (use dependencyInjectionOptions and
RadioGroupItem/option.label/option.description), and place the info button
(HugeiconsIcon with InformationCircleIcon) as a sibling element outside the
<label>; keep the existing onClick handler that calls setSelectedItem but ensure
the button still calls e.preventDefault()/e.stopPropagation() so it doesn't
toggle the radio, and preserve the original conditional className logic that
uses config.dependencyInjection to maintain the visual selected state; move the
key={option.value} to the outer wrapper so list keys remain stable.

</RadioGroup>
</div>
<Accordion type="multiple" defaultValue={["networking"]} className="space-y-3">
{categories.map((category) => (
<AccordionItem
Expand Down
13 changes: 13 additions & 0 deletions app/lib/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ export const architectureSchema = z.enum([
])
export type ArchitectureStyle = z.infer<typeof architectureSchema>

export const dependencyInjectionSchema = z.enum([
"none",
"get_it",
])
export type DependencyInjectionStyle = z.infer<typeof dependencyInjectionSchema>

const iconPackSchema = z.object({
default: z.literal(true),
iconsax_plus: z.boolean(),
Expand Down Expand Up @@ -173,6 +179,7 @@ export const scaffoldConfigSchema = z.object({
localization: localizationSchema,
navigation: navigationSchema,
architecture: architectureSchema,
dependencyInjection: dependencyInjectionSchema.default("none"),
icons: iconPackSchema.default({
default: true,
iconsax_plus: false,
Expand Down Expand Up @@ -249,6 +256,7 @@ export const defaultConfig: ScaffoldConfig = {
localization: { enabled: true, supportedLocales: ["en", "es"] },
navigation: "go_router",
architecture: "feature-first",
dependencyInjection: "none",
misc: {
usesScreenutil: true,
usesFlutterNativeSplash: true,
Expand Down Expand Up @@ -337,6 +345,11 @@ export const architectureOptions = [
{ value: "layer-first", label: "Layer-first", description: "Groups code by technical layers." },
] as const satisfies Array<{ value: ArchitectureStyle; label: string; description: string }>

export const dependencyInjectionOptions = [
{ value: "none", label: "None", description: "Manual constructor wiring without a DI container." },
{ value: "get_it", label: "GetIt", description: "Service locator with centralized dependency registration." },
] as const satisfies Array<{ value: DependencyInjectionStyle; label: string; description: string }>

export const navigationOptions = [
{ value: "imperative", label: "Imperative (Navigator 1.0)", description: "Standard Navigator 1.0; simple for small apps." },
{ value: "go_router", label: "go_router", description: "Declarative routing with deep linking support." },
Expand Down
10 changes: 7 additions & 3 deletions app/lib/generator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type TemplateContext = ScaffoldConfig & {
usesDeviceInfoPlus: boolean
usesAppVersionUpdate: boolean
usesGeolocator: boolean
usesGetItDi: boolean
}
}

Expand Down Expand Up @@ -98,7 +99,8 @@ export async function generateFlutterScaffold(input: unknown) {
}
}

const zipBuffer = await zipDirectory(workingDir)
const zipRootDir = context.flags.appSlug || "flutter-app"
const zipBuffer = await zipDirectory(workingDir, zipRootDir)
Comment on lines +102 to +103
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Zip root sanitization is permissive — consider tightening.

zipRoot only strips / and \. Other path-traversal-ish or filesystem-hostile characters (e.g., .., leading/trailing dots, colons, control chars) pass through into archive entry names. Since appSlug is derived from user-supplied appName (only lowercased + whitespace→-), an appName like "my..app" produces a zip root of my..app/.... Not exploitable by itself (entries are still under one folder), but it can produce surprising or invalid directory names on some hosts/clients.

🛡️ Suggested hardening
-    const zipRoot = rootFolderName.trim().replace(/[/\\]/g, "") || "flutter-app"
+    const zipRoot =
+        rootFolderName
+            .trim()
+            .replace(/[/\\:*?"<>|\x00-\x1f]/g, "")
+            .replace(/\.+/g, ".")
+            .replace(/^\.+|\.+$/g, "") || "flutter-app"

Also applies to: 321-334

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/lib/generator/index.ts` around lines 102 - 103, zipRootDir derived from
context.flags.appSlug is too permissive (only strips slashes); tighten
sanitization in the assignment for zipRootDir and any other uses (see
occurrences around where zipRootDir is set and passed to zipDirectory and the
code block at 321-334). Replace the lax strip with a strict sanitizer that
rejects or normalizes path-traversal and filesystem-hostile characters (remove
sequences like "..", leading/trailing dots, control chars, colons, backticks,
and collapse repeated dots), allow only a safe whitelist (e.g., alphanumerics,
dashes, underscores), and if the result is empty/invalid fall back to
"flutter-app"; apply the same sanitizer to context.flags.appSlug before passing
into zipDirectory(workingDir, zipRootDir) and other places that build archive
entry names.

return zipBuffer
} finally {
await fs.rm(workingDir, { recursive: true, force: true }).catch(() => { })
Expand Down Expand Up @@ -170,6 +172,7 @@ function buildTemplateContext(config: ScaffoldConfig): TemplateContext {
usesDeviceInfoPlus: config.misc.usesDeviceInfoPlus,
usesAppVersionUpdate: config.misc.usesAppVersionUpdate,
usesGeolocator: config.misc.usesGeolocator,
usesGetItDi: config.dependencyInjection === "get_it",
},
}
}
Expand Down Expand Up @@ -315,8 +318,9 @@ async function copyAndRenderDirectory(
}
}

async function zipDirectory(dir: string) {
async function zipDirectory(dir: string, rootFolderName: string) {
const zip = new JSZip()
const zipRoot = rootFolderName.trim().replace(/[/\\]/g, "") || "flutter-app"

async function walk(current: string) {
const entries = await fs.readdir(current, { withFileTypes: true })
Expand All @@ -327,7 +331,7 @@ async function zipDirectory(dir: string) {
await walk(fullPath)
} else if (entry.isFile()) {
const data = await fs.readFile(fullPath)
zip.file(relPath, data)
zip.file(`${zipRoot}/${relPath}`, data)
}
}
}
Expand Down
1 change: 1 addition & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ A complete reference of all available options, flags, and values you can configu
| `appName` | `string` | The display name of your Flutter app. | `Flutter Starter` |
| `packageId` | `string` | The bundled identifier (e.g. `com.example.app`). | *derived* |
| `stateManagement` | `provider`, `riverpod`, `bloc`, `getx`, `mobx`, `none` | State management library for injected controllers. | `riverpod` |
| `dependencyInjection` | `none`, `get_it` | Dependency wiring strategy for repositories/services. | `none` |
| `navigation` | `imperative`, `go_router`, `getx`, `auto_route` | Strategy used for routing and navigation flow. | `go_router` |
| `architecture` | `mvc`, `mvvm`, `clean`, `feature-first`, `layer-first` | Parent folder structure and logic segregation pattern. | `feature-first` |

Expand Down
20 changes: 15 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion templates/flutter/base/README.md.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Generated with the Flutter Scaffolding Wizard.
- Onboarding presentation starter
- Routing scaffold {{#if flags.routerPackage}}using `{{flags.routerPackage}}`{{else}}with `MaterialApp` routes{{/if}}
- {{> state_label}}
- Dependency injection: {{dependencyInjection}}
- Backend: {{backend.provider}}

## Getting started
Expand All @@ -16,4 +17,4 @@ flutter pub get
flutter pub run build_runner build --delete-conflicting-outputs
{{/if}}
flutter run
```
```
3 changes: 2 additions & 1 deletion templates/flutter/base/lib/main.dart.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Future<void> main() async {
{{/if}}

await AppConfig.init();
await initDependencies();
{{#if flags.usesHive}}
await HiveService.instance.init();
{{/if}}
Expand All @@ -40,4 +41,4 @@ Future<void> main() async {
){{/if}},
{{/if}}
);
}
}
30 changes: 30 additions & 0 deletions templates/flutter/base/lib/src/di/service_locator.dart.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'package:{{flags.appSnake}}/src/{{#if (eq architecture "layer-first")}}domain/repositories/{{else if (eq architecture "mvc")}}services/{{else if (eq architecture "mvvm")}}data/repositories/{{else}}features/auth/domain/repositories/{{/if}}auth_repository.dart';
import 'package:{{flags.appSnake}}/src/{{#if (eq architecture "layer-first")}}data/repositories/{{else if (eq architecture "mvc")}}services/{{else if (eq architecture "mvvm")}}data/repositories/{{else}}features/auth/data/repositories/{{/if}}auth_repository_impl.dart';
{{#if flags.usesGetItDi}}
import 'package:{{flags.appSnake}}/src/services/auth_service.dart';
import 'package:get_it/get_it.dart';

final sl = GetIt.instance;
{{/if}}

Future<void> initDependencies() async {
{{#if flags.usesGetItDi}}
if (!sl.isRegistered<AuthService>()) {
sl.registerLazySingleton<AuthService>(() => AuthService.instance);
}

if (!sl.isRegistered<AuthRepository>()) {
sl.registerLazySingleton<AuthRepository>(
() => AuthRepositoryImpl(authService: sl<AuthService>()),
);
}
{{/if}}
}

AuthRepository createAuthRepository() {
{{#if flags.usesGetItDi}}
return sl<AuthRepository>();
{{else}}
return AuthRepositoryImpl();
{{/if}}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export '../routing/global_navigator.dart';
{{#if flags.isGetX}}
export '../routing/app_bindings.dart';
{{/if}}
export '../di/service_locator.dart';
export '../services/services.dart';
export '../shared/shared.dart';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:get/get.dart';
import 'package:{{flags.appSnake}}/src/{{#if (eq architecture "layer-first")}}data/repositories/{{else if (eq architecture "mvc")}}services/{{else if (eq architecture "mvvm")}}data/repositories/{{else}}features/auth/data/repositories/{{/if}}auth_repository_impl.dart';
import 'package:{{flags.appSnake}}/src/di/service_locator.dart';

{{#if (eq architecture "layer-first")}}
import 'package:{{flags.appSnake}}/src/presentation/controllers/auth/auth_controller.dart';
Expand All @@ -16,7 +16,7 @@ class AppBindings implements Bindings {
void dependencies() {
{{#if flags.isGetX}}
Get.lazyPut<AuthController>(
() => AuthController(repository: AuthRepositoryImpl()),
() => AuthController(repository: createAuthRepository()),
);
{{/if}}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import '../../imports/imports.dart';
{{#if flags.isBloc}}
import '../../{{#if (eq architecture "layer-first")}}data/repositories/{{else if (eq architecture "mvc")}}services/{{else if (eq architecture "mvvm")}}data/repositories/{{else}}features/auth/data/repositories/{{/if}}auth_repository_impl.dart';
import '../../{{#if (eq architecture "layer-first")}}presentation/providers/session_bloc.dart{{else if (eq architecture "mvc")}}controllers/auth/session_bloc.dart{{else if (eq architecture "mvvm")}}ui/auth/bloc/session_bloc.dart{{else}}features/auth/presentation/providers/session_bloc.dart{{/if}}';
{{else if flags.isProvider}}
import '../../{{#if (eq architecture "layer-first")}}data/repositories/{{else if (eq architecture "mvc")}}services/{{else if (eq architecture "mvvm")}}data/repositories/{{else}}features/auth/data/repositories/{{/if}}auth_repository_impl.dart';
import '../../{{#if (eq architecture "layer-first")}}presentation/providers/session_provider.dart{{else if (eq architecture "mvc")}}controllers/auth/session_provider.dart{{else if (eq architecture "mvvm")}}ui/auth/providers/session_provider.dart{{else}}features/auth/presentation/providers/session_provider.dart{{/if}}';
{{else if flags.isMobX}}
import 'package:provider/provider.dart';
import '../../{{#if (eq architecture "layer-first")}}data/repositories/{{else if (eq architecture "mvc")}}services/{{else if (eq architecture "mvvm")}}data/repositories/{{else}}features/auth/data/repositories/{{/if}}auth_repository_impl.dart';
import '../../{{#if (eq architecture "layer-first")}}presentation/providers/session_store.dart{{else if (eq architecture "mvc")}}controllers/auth/session_store.dart{{else if (eq architecture "mvvm")}}ui/auth/stores/session_store.dart{{else}}features/auth/presentation/providers/session_store.dart{{/if}}';
{{/if}}

Expand All @@ -27,21 +24,21 @@ class StateWrapper extends StatelessWidget {
{{else if flags.isProvider}}
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => SessionProvider(repository: AuthRepositoryImpl())),
ChangeNotifierProvider(create: (_) => SessionProvider(repository: createAuthRepository())),
],
child: child,
);
{{else if flags.isBloc}}
return MultiBlocProvider(
providers: [
BlocProvider(create: (_) => SessionBloc(repository: AuthRepositoryImpl())),
BlocProvider(create: (_) => SessionBloc(repository: createAuthRepository())),
],
child: child,
);
{{else if flags.isMobX}}
return MultiProvider(
providers: [
Provider(create: (_) => SessionStore(repository: AuthRepositoryImpl())),
Provider(create: (_) => SessionStore(repository: createAuthRepository())),
],
child: child,
);
Expand Down
3 changes: 3 additions & 0 deletions templates/flutter/base/pubspec.yaml.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ dependencies:
# State Management
fpdart: ^1.2.0
equatable: ^2.0.7
{{#if flags.usesGetItDi}}
get_it: ^8.0.3
{{/if}}
Comment on lines 28 to +33
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🌐 Web query:

What is the latest stable version of the get_it Dart/Flutter package on pub.dev?

💡 Result:

The latest stable version of the get_it Dart/Flutter package on pub.dev is 9.2.1.

Citations:


Move get_it to a dedicated "Dependency Injection" section and update version constraint.

get_it is a DI/service-locator package, not a state management library. Grouping it under # State Management is misleading. Additionally, the version constraint ^8.0.3 is outdated; the latest stable version is 9.2.1. Update to at least ^9.0.0.

♻️ Proposed fix
   # State Management
   fpdart: ^1.2.0
   equatable: ^2.0.7
-  {{`#if` flags.usesGetItDi}}
-  get_it: ^8.0.3
-  {{/if}}
   {{`#if` flags.usesFlutterHooks}}
   flutter_hooks: ^0.20.5
   {{/if}}
@@
   {{`#if` flags.isMobX}}
   mobx: ^2.6.0
   flutter_mobx: ^2.3.0
   {{/if}}
+
+  # Dependency Injection
+  {{`#if` flags.usesGetItDi}}
+  get_it: ^9.0.0
+  {{/if}}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@templates/flutter/base/pubspec.yaml.hbs` around lines 28 - 33, The get_it
dependency is incorrectly listed under the "State Management" section and uses
an outdated constraint; when flags.usesGetItDi is true, remove the get_it:
^8.0.3 entry from under the State Management block and instead add a new
commented section "# Dependency Injection" (or similar) where you include get_it
with an updated constraint (at least ^9.0.0, e.g., ^9.0.0 or ^9.2.1); update the
Handlebars template around the conditional (flags.usesGetItDi) so the get_it
line is emitted under that new section rather than next to fpdart/equatable.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@noeljabraham kindly fix this

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

{{#if flags.usesFlutterHooks}}
flutter_hooks: ^0.20.5
{{/if}}
Expand Down
Loading