Injectable – Flutter & Dart Equivalent to Dagger & Angular Dependency Injection

Injectable – Flutter & Dart Equivalent to Dagger & Angular Dependency Injection

Injectable – Flutter & Dart Equivalent to Dagger & Angular Dependency Injection. Dependency injection is necessary if you're not coding spaghetti and you want to keep nice layers of separation in your Flutter app's codebase. Dagger solves it elegantly for native Android and Angular is also known for its powerful dependency injection framework. Now, we Flutter developers can finally use something similar - the injectable package which is a code generator for get_it.

Dependency injection is necessary if you're not coding spaghetti 🍝 and you want to keep nice layers of separation in your Flutter app's codebase.

Dagger solves it elegantly for native Android and Angular is also known for its powerful dependency injection framework. Now, we Flutter developers can finally use something similar - the injectable package which is a code generator for get_it.

Starter project we all love

Yes, it's true. We're going to build yet another counter app BUT this one will be special. We won't always increment the counter by one (1), instead, based on whether we're in development environment or a production environment, the counter is going to get incremented by different values. Where are we going to get the increment values from, you ask? Well, from ICounterRepository, of course!

Starter project file structure

In order to keep our code testable, CounterChangeNotifier depends on an abstract class ICounterRepository which is then in turn implemented by CounterRepository (production) and DevCounterRepository.

counter_change_notifier.dart

class CounterChangeNotifier extends ChangeNotifier {
  final ICounterRepository _counterRepository;

  CounterChangeNotifier(this._counterRepository);

  int _value = 0;
  int get value => _value;

  void increment() {
    _value += _counterRepository.getIncrement();
    notifyListeners();
  }
}

The problem with the starter project is that we're doing all the injection manually inside main.dart. I mean, we don't even use **get_it **and instead we populate the constructors completely by ourselves.

main.dart

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Material App',
      home: ChangeNotifierProvider(
        // Manually passing in the production repository
        create: (_) => CounterChangeNotifier(CounterRepository()),
        child: CounterPage(),
      ),
    );
  }
}

While using get_it in its plain form would help with maintainability, it surely wouldn't cut down on the boilerplate... That's precisely the reason why injectable is so awesome!

This tutorial assumes that you're at least a bit familiar with get_it or other service locators in general.

Setting up injectable

The first step is obviously adding all of the packages to pubspec.yaml. In addition to injectable and its generator, we're also going to add **build_runner **and get_it.

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  provider: ^4.0.2
  injectable: ^0.1.0
  get_it: ^3.1.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  build_runner:
  injectable_generator: ^0.1.0

There's really not much actual setup code to write. Apart from annotating injectable classes, we're going to create only one setup function configureInjection which will call a function generated by the library called $initGetIt.

injection.dart

import 'package:injectable/injectable.dart';
import 'package:injectable_prep/injection.iconfig.dart';

@injectableInit
void configureInjection(String environment) =>
    $initGetIt(environment: environment);

abstract class Env {
  static const dev = 'dev';
  static const prod = 'prod';
}

The environment string is what allows us to easily manage between multiple implementations of ICounterRepository. While can be any kind of a String, I find that creating constants is better than passing around String literals.
We created our own constants for the environment strings, injectable also comes with some predefined constants that you can use if you'd like to. Check out this file for more info.

The @injectableInit annotation is very important as this is what triggers code generation. In our case, the generated code will be located at injection.iconfig.dart. After running everyone's favorite command:

👨‍💻 terminal

flutter pub run build_runner watch --delete-conflicting-outputs

...we can go to main.dart and call configureInjection(Env.prod) from the main function. Of course, this won't do anything meaningful yet as we haven't registered any classes with injectable.

main.dart

void main() {
  configureInjection(Env.prod);
  runApp(MyApp());
}

To change the injection environment in this simple app, you need to modify the single main function to pass in, for example, Env.dev.

Annotating the classes

Let's start off at the ICounterRepository. We want to mark it as @injectable and also bind it to specific implementations based on the current environment with @Bind.toType.

i_counter_repository.dart

@Bind.toType(DevCounterRepository, env: Env.dev)
@Bind.toType(CounterRepository, env: Env.prod)
@injectable
abstract class ICounterRepository {
  int getIncrement();
}

Our journey with the repository doesn't end here though. We also need to mark its concrete implementations with @injectable. Doing so will firstly add appropriate import statements to the generated file. Secondly, we'll also be able to resolve dependencies by their concrete types under any environment.

counter_repository.dart

@injectable
class CounterRepository implements ICounterRepository {
  @override
  int getIncrement() => 1;
}

dev_counter_repository.dart

@injectable
class DevCounterRepository implements ICounterRepository {
  @override
  int getIncrement() => 2;
}

Lastly, let's annotate the CounterChangeNotifier. In contrast with the repository, it contains a non-empty constructor. This is where the injectable package shines the most! It will figure out to pass in an argument on its own.

counter_change_notifier.dart

@injectable
class CounterChangeNotifier extends ChangeNotifier {
  final ICounterRepository _counterRepository;

  CounterChangeNotifier(this._counterRepository);

  ...
}

A look at the generated file

Boom 💥 We literally added a few annotations to our classes and our work is done! Opening up injection.iconfig.dart reveals an image familiar to developers used to get_it. The addition of automatic environment resolving for abstract classes is 🔥🔥🔥

injection.iconfig.dart

final getIt = GetIt.instance;
void $initGetIt({String environment}) {
  getIt
    ..registerFactory<CounterChangeNotifier>(
        () => CounterChangeNotifier(getIt<ICounterRepository>()))
    ..registerFactory<CounterRepository>(() => CounterRepository())
    ..registerFactory<DevCounterRepository>(() => DevCounterRepository());
  if (environment == 'dev') {
    _registerDevDependencies();
  }
  if (environment == 'prod') {
    _registerProdDependencies();
  }
}

void _registerDevDependencies() {
  getIt..registerFactory<ICounterRepository>(() => DevCounterRepository());
}

void _registerProdDependencies() {
  getIt..registerFactory<ICounterRepository>(() => CounterRepository());
}
Using get_it to resolve dependencies

Having everything else in place, we just need to get rid of the manual instantiation in main.dart and use the provided GetIt instance instead.

main.dart

void main() {
  configureInjection(Env.prod);
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Material App',
      home: ChangeNotifierProvider(
        create: (_) => getIt<CounterChangeNotifier>(),
        child: CounterPage(),
      ),
    );
  }
}

When we run the app, the counter will be incremented by 1 since we're running under the production environment, as specified by Env.prod. Changing it to Env.dev...
... and then hot restarting will make the counter be incremented by 2.

I know this is just a silly counter app example but you can probably imagine that the production environment would communicate with production APIs containing actual user data while the **development environment **would operate with something where it's safe to do silly things like deleting everything from the database without having a backup 😎

Bonus: Test environment

Doing test-driven development or just testing after the code has been written (😬) highly benefits from mocks. If you've ever mocked something, you know how time-consuming it is to create mock classes manually. Thankfully injectable and its environments can help even with testing. First, let's add **mockito **into pubspec.yaml.

pubspec.yaml

dev_dependencies:
  ...
  mockito: ^4.1.1

Add an additional test environment to the Env abstract class.

injection.dart

abstract class Env {
  static const test = 'test';
  static const dev = 'dev';
  static const prod = 'prod';
}

Create a mock_counter_repository.dart file next to all of the other repository implementation files and mark it as @injectable.

mock_counter_repository.dart

@injectable
class MockCounterRepository extends Mock implements ICounterRepository {}

Lastly, bind the abstract ICounterRepository to the MockCounterRepository "implementation" under the test environment.

i_counter_repository.dart

@Bind.toType(MockCounterRepository, env: Env.test)
@Bind.toType(DevCounterRepository, env: Env.dev)
@Bind.toType(CounterRepository, env: Env.prod)
@injectable
abstract class ICounterRepository {
  int getIncrement();
}

Gone are the days of constantly defining mocks throughout multiple test files! Now it's enough to call configureInjection(Env.test); from within the setUpAll callback and you can resolve dependencies just like in your regular non-test code.

example_test.dart

void main() {
  setUpAll(() {
    // Just like in the regular main() function inside main.dart
    configureInjection(Env.test);
  });

  test(
    "should do something",
    () async {
      // arrange
      final mockCounterRepository = getIt<ICounterRepository>();
      when(mockCounterRepository.getIncrement()).thenReturn(123);
      // act
      // TODO: Some action here
      // assert
      verify(mockCounterRepository.getIncrement()).called(10);
    },
  );
}

The amount of boilerplate and maintenance nightmare that's prevented by using the injectable package is totally incredible. You're literally one or two simple annotations away from having a robust dependency injection system working with the battle-tested get_it package in place. Use it and save your time!

Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly

What's new in Bootstrap 5 and when Bootstrap 5 release date?

What’s new in HTML6

How to Build Progressive Web Apps (PWA) using Angular 9

What is new features in Javascript ES2020 ECMAScript 2020

ECommerce Mobile App Development | Ecommerce Mobile App Development Services

We are leading ecommerce mobile application development company. Hire our ecommerce mobile app developer for your custom Ecommerce project at competitive rates. **Know about [Top ECommerce Mobile App Development...

We are leading ecommerce mobile application development company. Hire our ecommerce mobile app developer for your custom Ecommerce project at competitive rates.

Know about Top ECommerce Mobile App Development Company

Flutter for Beginners 2020 - Build a Flutter App with Google's Flutter & Dart

Flutter for Beginners 2020 - Build a Flutter App with Google's Flutter & Dart

Flutter tutorial for Beginners 2020 - Build a Flutter App with Google's Flutter & Dart. Flutter Introduction for Beginners: Get Started with Flutter and learn how to build an iOS and Android app with Flutter! What is Flutter? How Flutter & Dart Code Gets Compiled? Why Choose Flutter? Flutter is Google's UI toolkit for crafting beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.

Free Flutter Introduction for Beginners: Get Started with Flutter and learn how to build an iOS and Android app with Flutter!


Content:

  • Introduction 00:03
  • What is Flutter 01:50
  • Flutter's Architecture 07:44
  • How Flutter & Dart Code Gets Compiled
  • To Native Apps 12:31
  • Understanding Flutter Versions 15:44
  • Flutter macOS Setup 18:34
  • macOS Development Environment 37:24
  • Flutter Windows Setup 41:32
  • Windows Development Environment 01:00:48
  • Flutter & Material Design 01:04:34
  • Flutter Alternatives 01:05:49
  • Course Outline 01:11:56
  • How To Get The Most Out Of The
  • Course 01:18:36
  • Module Introduction 01:21:17
  • Creating a New Project 01:23:42
  • An Overview of the Generated Files &
  • Folders 01:35:57
  • Analyzing the Default App 01:44:18
  • Dart Basics 01:49:50
  • More Dart Basics 02:07:08
  • Building an App From Scratch 02:20:15
  • Running the App on an Emulator 02:31:18
  • Class Constructors & Named
  • Arguments 02:34:43
  • First Summary & Additional Syntax 02:43:13
  • Building a Widget Tree 02:51:24
  • Visible (Input / Output) & Invisible
  • (Layout / Control) Widgets 02:59:04
  • Adding Layout Widgets 03:02:17
  • Connecting Functions & Buttons 03:09:09
  • Anonymous Functions 03:16:40
  • Updating Widget Data (Or: Using
  • StatelessWidget Incorrectly) 03:20:01
  • Updating Correctly with Stateful
  • Widgets 03:26:38
  • A Brief Look Under The Hood 03:38:36
  • Using Private Properties 03:41:04
  • Creating a New, Custom Widget 03:46:21
  • First Styling & Layouting Steps 03:58:11
  • Enums & Multiple Constructors 04:06:12
  • Official Docs & The Widget Catalog 04:10:19
  • Passing Callback Functions Around 04:12:59
  • Introducing Maps 04:23:37
  • Mapping Lists to Widgets 04:29:30
  • final vs const 04:39:54
  • Introducing "if" Statements 04:50:45
  • [DART DEEP DIVE] More on "if"
  • Statements 04:57:36
  • [DART DEEP DIVE] The "null" Value 05:08:52
  • Outputting Widgets Conditionally 05:10:55
  • Splitting the App Into Widgets 05:13:16
  • Calculating a Total Score 05:23:13
  • Getters & "else-if" 05:30:59
  • Resetting the Quiz 05:37:34
  • Wrap Up 05:42:28

Top Mobile App Development Agency in Massachusetts

Top Mobile App Development Agency in Massachusetts

**Looking for Top Mobile App Development Agency at reasonable rates?** AppClues Studio is one of the top Mobile App Development Agencies in Massachusetts. We offer user-oriented mobile solutions to our clients globally. We offer Mobile app...

Looking for Top Mobile App Development Agency at reasonable rates?

AppClues Studio is one of the top Mobile App Development Agencies in Massachusetts. We offer user-oriented mobile solutions to our clients globally.

We offer Mobile app development services like iOS app development, Android app development, M-commerce development, Cross-Platform mobile app development, AR & VR app development, IoT app development, Wallet app development, Hybrid app development etc.

Why Choose AppClues Studio?
• Domain Expertise
• On-time Delivery
• 24/7 Support
• Aesthetic Designs
• Agile Development
• Smooth Handover

If you looking for your mobile application development, then get in touch with us!
Visit: https://appcluesstudio.com/mobile-applications-development-company-massachusetts
Email: [email protected]
Contact No.: +1-978-309-9910