How to Migrate a Flutter Application from GetIt to Bloc (Without Losing Your Code)
If you’re here, you’re probably searching something like:
Table Of Content
- 🧭 Why Migrate GetIt to Bloc?
- 🚦 When Bloc Makes Sense
- Step 1 — Identify What GetIt Manages
- Step 2 — Choose Which Feature to Convert First
- Step 3 — Before & After (GetIt → Bloc)
- 🧵 Before: Using GetIt + setState
- After: Using Bloc
- Step 4 — Inject Blocs at the App Level
- Step 5 — Slowly Remove GetIt (When Ready)
- 💡 Common Mistakes
- 🎯 Final Thoughts
- Related Reads
“How to migrate GetIt to Bloc?”
“Is it worth moving from GetIt to Bloc?”
“Should I refactor my existing Flutter app to Bloc?”
Trust me — I searched the same things.
My first Flutter project wasn’t big. I built it fast using GetIt because dependency injection felt clean and effortless. But as the project grew, something changed — the state became harder to track, debugging felt unpredictable, and suddenly my widget tree held too much logic.
That’s when I knew it was time to migrate GetIt to Bloc.
This guide is everything I wish I knew before starting the migration.

🧭 Why Migrate GetIt to Bloc?
Before you start refactoring, you should ask:
Why do I want to migrate GetIt to Bloc?
For me — the answer was:
-
I needed predictable state behavior
-
I wanted clearer event → state → UI flow
-
I needed better scalability and testing
-
Multiple screens were sharing state and things felt messy
🚦 When Bloc Makes Sense
👍 You should migrate if:
-
The project is growing or becoming long-term
-
Multiple screens need shared state
-
You want cleaner business logic separation
-
You need robust unit testing
👎 You should NOT migrate if:
-
The app is small and static
-
No shared/global state exists
-
GetIt already works perfectly for your use case
Migration should solve a problem — not create one.

Step 1 — Identify What GetIt Manages
Before removing anything, list what GetIt currently handles.
For example, in one of my projects, I had:
- ThemeService - AuthService - ApiClient - UserPreferences - CartService
GetIt makes it easy to write this:
final locator = GetIt.instance;
void setupLocator() {
locator.registerSingleton<ThemeService>(ThemeService());
}
But the problem?
Services contain logic, and widgets eventually rely on them directly. Which means UI + logic blend together — and that’s not ideal long-term.

Step 2 — Choose Which Feature to Convert First
Don’t migrate the entire app at once.
Start with something simple — like theme switching.
Why theme?
Because it:
-
touches the UI
-
requires state sharing
-
is easy to test
This will help you validate the flow before migrating more complex features like authentication.
Instead of throwing everything into one giant Bloc, break things down:
-
AuthBloc
-
ThemeBloc
-
UserBloc
-
SettingsBloc
Each Bloc handles one responsibility.
Bloc embraces the Single Responsibility Principle — something GetIt doesn’t enforce.
Step 3 — Before & After (GetIt → Bloc)

Let’s look at a real example.
🧵 Before: Using GetIt + setState
final themeService = GetIt.instance<ThemeService>();
class SettingsPage extends StatefulWidget {
@override
_SettingsPageState createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
bool isDark = themeService.isDarkMode;
void toggleTheme() {
themeService.toggleTheme();
setState(() {
isDark = themeService.isDarkMode;
});
}
@override
Widget build(BuildContext context) {
return Switch(value: isDark, onChanged: (_) => toggleTheme());
}
}
Notice how:
-
UI triggers business logic
-
Service updates state
-
UI updates manually
After: Using Bloc
We first create a Bloc:
class ThemeEvent {}
class ToggleThemeEvent extends ThemeEvent {}
class ThemeState {
final bool isDark;
ThemeState(this.isDark);
}
class ThemeBloc extends Bloc<ThemeEvent, ThemeState> {
final ThemeService themeService;
ThemeBloc(this.themeService) : super(ThemeState(themeService.isDarkMode)) {
on<ToggleThemeEvent>((event, emit) {
themeService.toggleTheme();
emit(ThemeState(themeService.isDarkMode));
});
}
}
Now the UI becomes clean and reactive:
BlocBuilder<ThemeBloc, ThemeState>(
builder: (context, state) {
return Switch(
value: state.isDark,
onChanged: (_) => context.read<ThemeBloc>().add(ToggleThemeEvent()),
);
},
);
No more setState().
No direct service call in widgets.
UI reacts automatically.
This is what migrating GetIt to Bloc feels like — structured, predictable, and scalable.
Step 4 — Inject Blocs at the App Level

We now use MultiBlocProvider:
void main() {
setupLocator();
runApp(
MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => ThemeBloc(locator<ThemeService>()),
),
],
child: const MyApp(),
),
);
}
At this stage, GetIt still exists — and that’s okay.
Migration happens feature-by-feature.
Step 5 — Slowly Remove GetIt (When Ready)
Once all features move to Bloc, you can decide:
-
Keep GetIt ONLY for dependency injection
or -
Remove GetIt entirely if Bloc + Repository pattern feels enough
In my case, removing GetIt fully made the project feel clean — but this depends on your architecture preferences.
💡 Common Mistakes
| Mistake | Fix |
|---|---|
| Migrating everything at once | Do it feature by feature |
| Keeping logic in widgets | Move to Bloc |
| One giant Bloc managing everything | Create multiple feature Blocs |
| Not writing tests | Bloc makes testing easy — use it |
🎯 Final Thoughts
Migrating a Flutter app is not just moving code — it’s improving how the app thinks and behaves.
If you’re ready to scale, test, and build long-term maintainable apps, then migrating GetIt to Bloc is one of the best decisions you’ll make as a Flutter developer.
And when you finally delete that last GetIt.instance, trust me…
you’ll feel like you just cleaned your entire room at 2 AM while listening to nostalgic songs. 😅✨
Want to learn Flutter Course, Javascript, or React Js Course, Frontend Development Course, Full Stack Development Course and More, Visit Our Website www.kaashivinfotech.com.

