Why use dependency injection

What is dependency injection

Instead of taking various arguments to construct an object, we now accept only one argument — the instantiated object.

The purpose of dependency injection

Dependency injection is designed to decouple the configuration and use of dependent components to reduce coupling between consumers and dependencies.

Benefits of dependency injection

Implementing dependency injection gives you the following advantages:

  • Reusing code makes it easier to swap out implementations of dependencies. Code reuse is improved due to inversion of control, and classes no longer control how their dependencies are created, but support any configuration.
  • Easy separation of the creation of refactored dependencies, which can be checked and modified at object creation or compile time, one change at a time, no change at a time.
  • The easy test class does not manage its dependencies, so when testing, you can pass in different implementations to test all the different use cases.

For example

Lao Wang needs a new V8 engine for his Maserati. Will he build one himself or buy one from a garage? If he builds his own, the engine is updated, he needs to learn to improve his skills, buy new parts, and buy a finished product, which is dependency injection.

class Car(private val engineParts: String,val enginePiston: String) {

    fun start() {
        val engine= Engine(engineParts,enginePiston)
        engine.start()
    }
}

class Engine(private val engineParts: String,val enginePiston: String){
}
Copy the code

If the constructor of Engine is changed, it needs to be changed in the Car class. With dependency injection, you don’t need to change the Car class.

Manual dependency injection is usually implemented in two ways, constructor pass in and field pass in. Construction method:

class Car(private val engine: Engine) {
    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val engine = Engine()
    val car = Car(engine)
    car.start()
}
Copy the code

Field pass:

class Car {
    lateinit var engine: Engine

    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.engine = Engine()
    car.start()
}
Copy the code

Although the above implementation of dependency injection, but the increase of boilerplate code, if the injection of instances, also very troublesome. Android has a Dagger and Hilt for automatic injection, and GetX also provides Binding class implementation.

Using dependency Injection

Get has a simple and powerful dependency manager that allows you to retrieve controllers or dependent classes in just one line of code, without providing context, and without requiring child nodes in the inheritedWidget.

Inject dependencies:

Get.put<PutController>(PutController());
Copy the code

Get dependencies:

Get.find<PutController>();
Copy the code

It’s that simple.

Get.put()

This is an immediate memory injection method. The call has been injected into memory.

Get. Put <S>(// Required: the class to inject. // Note: "S" means it can be any type of class. S Dependency // Optional: Use this method when you want to inject multiple classes of the same type. For example, if you have two shopping cart instances, you need to use tags to distinguish the different instances. // Must be a unique string. String tag, // Optional: By default, get is destroyed when the instance is no longer in use. Controller of a destroyed view) // If required, the instance will exist throughout the application lifecycle, like an instance of sharedPreferences. Bool permanent = false, bool permanent = false, // Optional: Allows you to use an abstract class in tests and replace it with another abstract class. // Defaults to false bool overrideAbstract = false, // Optional: allows you to create dependencies using functions rather than dependencies themselves. InstanceBuilderCallback<S> Builder,)Copy the code

Permanent means whether or not to destroy it. In general, the life cycle of an instance of get.put () is bound to the life cycle of the Widget in which it is put. If it is put globally (in the main method), then the instance will always exist. If you put in a Widget, then that Widget is removed from memory and the instance is destroyed. Note that this is delete, not dispose, please refer to the last part of the article.

Get.lazyPut

Lazy loading of a dependency is instantiated only when it is used. This works for dependencies that are uncertain whether they will be used or for costly dependencies. Kotlin’s Lazy agent.

  Get.lazyPut<LazyController>(() => LazyController());

Copy the code

LazyController is not created at this point, but initialized when you need to use it. Initialized is initialized when the following statement is executed:

Get.find<LazyController>();
Copy the code

After use, the instance is destroyed at the end of the life cycle of the Wdiget in use, which is called Widgetdispose.

If you find in one Widget, exit that Widget, and the instance is destroyed, and then enter another routed Widget and find again, GetX will print an error message indicating that there is no PUT. The same goes for global injection in time. The lifecycle of an instance injected by Get. LazyPut is bound to the context in which it was injected at Get.

If you want to get a different instance each time you find, you can use the Fenix parameter.

Get. LazyPut <S>(// Required: the method that will be executed when your class is first called. InstanceBuilderCallback Builder, // Optional: Like get.put (), this is used when you want multiple different instances of the same class. // Must be a unique String tag, // Optional: Next time when using reconstruction, / / when not in use, the instance will be discarded, but when you need to use again, will Get to create an instance, / / as bindings "SmartManagement. KeepFactory" in the API. // Default is false bool fenix = false)Copy the code

Get.putAsync

Inject an instance created asynchronously. Such as SharedPreferences.

  Get.putAsync<SharedPreferences>(() async {
    final sp = await SharedPreferences.getInstance();
    return sp;
  });
Copy the code

Scope reference Get. Put.

Get.create

This method can create many instances. Rarely used. Get. Put.

Bindings classes

Dependency injection and usage are implemented above, but as with manual injection, they need to be injected and used in the Widget for the lifecycle and the Widget bindings used, not completely decoupled. To implement automatic injection, we need this class.

Perhaps one of the biggest differences in this package is the complete integration of routing, state manager, and dependency manager. When a route is removed from the Stack, all instances of controllers, variables, and objects associated with it are removed from memory. If you’re using streams or timers, they turn off automatically, so we don’t have to worry about that. The Bindings class is a decoupled dependency injection class with “Binding “routing to both the state manager and the dependency manager. This allows GetX to know which page is being displayed when a controller is being used, and where and how to destroy it. In addition, the Bindings class will allow us to leverage SmartManager configuration controls.

  • Create a class and implement Binding
class InjectSimpleBinding implements Bindings {}
Copy the code

Because Bindings are abstract methods, the IDE prompts you to implement dependencies. Inject the desired instance into it:

class InjectSimpleBinding implements Bindings { @override void dependencies() { Get.lazyPut<Api>(() => Api()); Get.lazyPut<InjectSimpleController>(() => InjectSimpleController()); }}Copy the code
  • Notifies the route that we want to use this Binding to establish connections between the route manager, dependencies, and state.

There are two ways to do this if you are using a named routing table:

    GetPage(
      name: Routes.INJECT,
      page: () => InjectSimplePage(),
      binding:InjectSimpleBinding(),
    ),
Copy the code

If it is a direct jump:

Get.to(InjectSimplePage(), binding: InjectSimpleBinding());
Copy the code

Now we don’t have to worry about the memory management of the application, Get will do it for us.

We’ve decoupled the injected dependencies above, but fetching is still a bit cumbersome, and GetX has taken that into account for us. GetView perfect Bindings.

class InjectSimplePage extends GetView<InjectSimpleController> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('MyPage')), body: Center( child: Obx(() => Text(controller.obj.toString())), ), floatingActionButton: FloatingActionButton( onPressed: () { controller.getAge(); }, child: Icon(Icons.add), ), ); }}Copy the code

There is no Get. Find at all, but you can use controller directly because GetView encapsulates it:

abstract class GetView<T> extends StatelessWidget {
  const GetView({Key key}) : super(key: key);

  final String tag = null;

  T get controller => GetInstance().find<T>(tag: tag);

  @override
  Widget build(BuildContext context);
}
Copy the code

Statelesswidgets and StatefulWidgets are no longer required. This is the most common pattern for development and is recommended.

Of course, you might sometimes find it too cumbersome to declare one Bingings class at a time, so you can use BindingsBuilder, which simply uses a function to instantiate whatever you want to inject.

  GetPage(
    name: '/details',
    page: () => DetailsView(),
    binding: BindingsBuilder(() => {
      Get.lazyPut<DetailsController>(() => DetailsController());
    }),
Copy the code

It’s that simple, Bingings don’t even need to be created. Either way, you can choose the style that works best for your coding habits.

Working principle of Bindings

Bindings creates transition factories that are created the moment you click into another page and destroyed as soon as the route transition animation takes place. Factories take up very little memory, and instead of holding instances, they are a function that has the “shape” of the class we want. This has a low cost in memory, but since the purpose of the library is to Get maximum performance with minimal resources, Get and even factories are removed by default.

Intelligent management

GetX removes unused controllers from memory by default. But what if you want to change how the GetX control class is destroyed, you can use the SmartManagement class to set a different behavior.

How to change

If you want to change this configuration (which is usually not necessary), use this method.

Void main () {runApp (GetMaterialApp (smartManagement: smartManagement onlyBuilders/home/here: home (),))}Copy the code
  • SmartManagement.full

This is the default. Destroy classes that are not in use and have not been set to permanent. In most cases, we use this without changing it.

  • SmartManagement.onlyBuilders

With this option, only controllers that are started in init: or loaded into a Binding with get.lazyput () are destroyed.

If you use get.put () or get.putasync (), or any other method, SmartManagement does not have permission to remove the dependency.Copy the code
  • SmartManagement.keepFactory

Like SmartManagement.full, it will remove its dependencies when it is no longer in use, but it will retain their factories, which means it will recreate the dependency if the instance is needed again.