Dependency Injection in Flutter using GetIt

Syed Abdul Basit
4 min readJun 16, 2023

--

This dependency will inject your code a booster

Dependency injection is a software design pattern that allows objects to obtain their dependencies from an external source rather than creating them internally. This approach decouples the classes and promotes loose coupling, making the code more modular, reusable, and testable.

Now GET IT will help us in Flutter

GetIt is a powerful service locator and dependency injection container for Flutter. It provides a straightforward API for registering and retrieving instances of objects across the application.

We normally use three registering service:

  1. Register Singleton: Method is typically called during the initialisation phase of the application, such as in the main() function. By registering a dependency as a singleton, you ensure that the same instance is shared across the entire application, reducing resource usage and maintaining consistency.
  2. Register Lazy Singleton: You to create an instance of a dependency only when it is first accessed. When the dependency is first requested, the callback function will be executed to create and return a new instance. Subsequent requests for the dependency will receive the same instance that was previously created. Optimise memory consumption and initialisation time, as the instance is only created when it is actually needed.
  3. Register Factory: You to create a new instance of a dependency every time it is requested. This is useful when you want to have a new instance for each usage instead of sharing a single instance across the application.

Example:

//------------DI class--------------
GetIt getIt = GetIt.instance;

void setupDI(){
//register shared preference
getIt.registerFactory<SharedPreferencesService>(
() => SharedPreferencesService());

//initialize app api's
//<AppAPI> is a type
// getIt.get() know which instance AppAPI wants and
//it's want sharedPrerence which is already registerFactory above
getIt.registerSingleton<AppAPI>(AppAPI(getIt.get()));

//register routes
getIt.registerLazySingleton<AppRoutes>(() => AppRoutes());
}

Shared Preferences

class SharedPreferencesService {
SharedPreferences? _prefs;

SharedPreferencesService._internal();

factory SharedPreferencesService() {
return SharedPreferencesService._internal();
}

Future<SharedPreferences?> get _initializePrefs async {
_prefs ??= await SharedPreferences.getInstance();
return _prefs;
}

Future<String> getString(String key) async {
var pref = await _initializePrefs;
return pref?.getString(key) ?? "";
}

Future<void> setString(String key, String? value) async {
var pref = await _initializePrefs;
pref?.setString(key, value ?? '');
}
}

Main.dart file

import 'package:flutter/material.dart';

import 'app_routes.dart';
import 'dependency_injection.dart';

void main() {
// call a function here to initialize
setupDI();
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
//initialize and call a route using getit
routes: getIt<AppRoutes>().routes,
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
final String title = 'Flutter Demo Home Page';

@override
void initState() {
super.initState();
}

void _incrementCounter() {
setState(() {

_counter++;
});
}

@override
Widget build(BuildContext context) {

return Scaffold(
appBar: AppBar(

title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}

Routes

import 'package:dependency_injection_demo/main.dart';
import 'package:flutter/material.dart';

class AppRoutes{
final Map<String, Widget Function(BuildContext)> routes = {
'/': (_) => const MyHomePage(),
};
}

App Api

import 'package:flutter/services.dart';

import 'shared_preference_service.dart';

class AppAPI {
final SharedPreferencesService _sharedPreferences;

const AppAPI(this._sharedPreferences);

Future<HomeModel?> getHome() async {
final response = await Future.delayed(const Duration(seconds: 2), () async {
return await rootBundle.loadString('assets/api_data/home_api.json');
});
HomeModel homeModel = HomeModel(response);
await _sharedPreferences.setString('token', homeModel.value);
return homeModel;
}
}

class HomeModel{
dynamic value;
HomeModel(this.value);
}

Now it’s time Garbage Collector

GetIt relies on Dart’s garbage collector to manage the memory and lifecycle of objects, including the instances registered within the container.

When an instance registered with GetIt is no longer referenced or needed in your application, Dart’s garbage collector will automatically mark it as eligible for garbage collection. Eventually, the garbage collector will reclaim the memory occupied by those unused instances, freeing up system resources.

If you have instances that require specific cleanup or resource release, you can provide a disposal function/callback during registration using the dispose parameter. This callback will be called when the instance is unregistered or when you manually call the GetIt.reset() method to clear the container.

GetIt.instance.registerSingleton<Database>(
Database(),
dispose: (database) {
// Perform cleanup or resource release for the database instance.
database.close();
},
);

In this example, the disposal function will be called when the Database instance is unregistered or during a container reset, allowing you to perform necessary cleanup operations.

It’s important to note that proper management of instance lifecycles, including disposal of resources, is crucial for efficient memory usage and preventing memory leaks in your application. So, ensure that you handle the disposal of resources appropriately based on your application’s requirements.

By following best practices for object lifecycle management and leveraging Dart’s garbage collector, you can ensure efficient memory usage and resource management when using GetIt in your Flutter applications.

--

--