The ultimate solution of fluent state management geTx Part 3 – dependency injection

Time:2021-12-5

GeTx Chapter 3 – dependency injection

Why use dependency injection

What is dependency injection

Originally, we accepted various parameters to construct an object, but now we only accept one parameter – the instantiated object.

Purpose of dependency injection

Dependency injection is to separate the configuration and use of dependent components to reduce the coupling between users and dependencies.

Benefits of dependency injection

Implementing dependency injection gives you the following benefits:

  • Reuse code
    It is easier to replace the implementation of dependencies. Due to the inversion of control, code reuse is improved, and classes no longer control how their dependencies are created, but support any configuration.
  • Easy to refactor
    Dependency creation is separated. It can be checked and modified during object creation or compilation. One modification is not required for use.
  • Easy to test
    Classes do not manage their dependencies, so when testing, you can pass in different implementations to test all different use cases.

for instance

Old Wang’s Maserati needs to change a V8 engine. Does he assemble an engine himself or buy one at a modification shop?
If he assembles an engine and the structure of the engine is updated, he needs to learn to improve his technology, buy new parts, and directly buy a finished product, which is dependent on 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){
}

If the construction method of the engine class in the above code changes, it also needs to be changed in the car class. Using dependency injection does not need to change the car class.

There are usually two ways to manually implement dependency injection, constructor input and field input.
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()
}

Field incoming:

class Car {
    lateinit var engine: Engine

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

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

Although dependency injection is implemented above, template code is added. If there are many injection instances, it is also troublesome. On AndroidDaggerandHiltTo achieve automatic injection, geTx also provides us withBindingClass implementation.

Using dependency injection

Get has a simple and powerful dependency manager, which allows you to retrieve the controller or dependent classes with only one line of code. There is no need to provide context or in the child node of inheritedwidget.

Injection dependency:

Get.put<PutController>(PutController());

Get dependencies:

Get.find<PutController>();

It’s that simple.

Get.put()

This is an immediate memory injection method. It has been injected into memory after calling.

Get.put<S>(
  //Required: the class to inject.
  //Note: "s" means that it can be any type of class.
  S dependency

  //Optional: this method can be used when you want to inject multiple classes of the same type.
  //For example, if there are two shopping cart instances, you need to use labels to distinguish different instances.
  //Must be a unique string.
  String tag,

  //Optional: by default, get will destroy the instance after it is no longer used
  //(for example, the controller of a view that has been destroyed)
  //If necessary, this instance exists throughout the application life cycle, just like an instance of SharedPreferences.
  //The default value is false
  bool permanent = false,

  //Optional: allows you to use an abstract class in the test, replace it with another abstract class, and then test.
  //The default is false
  bool overrideAbstract = false,

  //Optional: allows you to create dependencies using functions instead of dependencies themselves.
  //This is not often used
  InstanceBuilderCallback<S> builder,
)

permanentYes represents whether not to destroy. usuallyGet.put()The life cycle of the instance of is bound to the life cycle of the widget where the put is located. If the put is in the global (main method), the instance will always exist. If you put in a widget, the widget will be deleted from memory and the instance will be destroyed. Note that this is deletion, not deletiondispose, see the last part of the previous article.

Get.lazyPut

Lazy loading a dependency that is instantiated only when used. Applies to dependencies that are uncertain whether they will be used or computationally expensive dependencies. Similar to kotlin’s lazy agent.

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

Lazycontroller will not be created at this time, but will not be created until you use itinitialized, that is, when the following sentence is executedinitialized

Get.find<LazyController>();

After use, the life cycle of the used wdiget ends, that is, the widgetdisposeThe instance will be destroyed.

If you find in a widget and then exit the widget, the instance will also be destroyed, and then enter the widget of another route. If you find again, geTx will print an error message to remind you that there is no put. Timely global injection, too. It can be understood that,Get.lazyPutThe life cycle of the injected instance is andGet.findIs bound by the context of the.

If you want to get different instances each time you find, you can use thefenixParameters.

Get.lazyPut<S>(
  //Must: the method that will be executed when your class is called for the first time.
  InstanceBuilderCallback builder,
  
  //Optional: like get. Put(), it is used when you want to have multiple different instances of the same class.
  //Must be unique
  String tag,

  //Optional: whether to rebuild the next time you use it,
  //When it is not used, the instance will be discarded, but when it needs to be used again, get will recreate the instance,
  //Just like "smartmanagement. Keepfactory" in the bindings API.
  //The default value is false
  bool fenix = false
  
)

Get.putAsync

Inject an instance created asynchronously. such asSharedPreferences

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

Scope referenceGet.put

Get.create

This method can create many instances. Rarely used. Can be regarded asGet.put

Bindings class

The above implements dependency injection and use, but like the manual injection mentioned above, it needs to be injected and used in the widget in order to bind the life cycle and the used widget, which is not completely decoupled. To implement automatic injection, we need this class.

Perhaps one of the biggest differences of this package is that it can fully integrate routing, state manager and dependency manager. When a route is removed from the stack, all instances of its associated controllers, variables, and objects are removed from memory. If you use streams or timers, they will turn off automatically. We don’t have to worry about them. The bindings class is a class that decouples dependency injection, and “binding” is routed to the state manager and dependency manager. This allows geTx to know which page is being displayed when a controller is used and where and how to destroy it. In addition, the bindings class will allow us to configure control using smartmanager.

  • Create a class and implement binding
class InjectSimpleBinding implements Bindings {}

becauseBindingsIt is an abstract method, so the IDE will prompt you to implement itdependencies。 Inject the examples we need:

class InjectSimpleBinding implements Bindings {
  @override
  void dependencies() {
    Get.lazyPut<Api>(() => Api());
    Get.lazyPut<InjectSimpleController>(() => InjectSimpleController());
  }
}
  • To notify the route, we use the binding to establish the connection between the route manager, dependencies and states.

There are two ways. If you use a named routing table:

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

If it is a direct jump:

Get.to(InjectSimplePage(), binding: InjectSimpleBinding());

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

We have decoupled the injection dependency above, but it is still a little inconvenient to obtain, and geTx has also been taken into account for us.GetViewPerfect with 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),
      ),
    );
  }
}

Not at allGet.find, but it can be used directlycontrollerBecauseGetViewSealed in:

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);
}

Statelesswidget and statefulwidget are no longer required. This is also the most commonly used mode of development, which is recommended for everyone.

Of course, sometimes it’s troublesome to declare a bindings class at a time, so you can use bindingsbuilder, so you can simply use a function to instantiate anything you want to inject.

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

It’s that simple. Bingings doesn’t need to be created. Both methods are OK. You can choose the most suitable style according to your coding habits.

How bindings works

Bindings will create transitional factories. When you click to enter another page, these factories will be created. Once the routing transition animation occurs, they will be destroyed. Factories take up very little memory. They don’t hold instances, but a function with the “shape” of the kind we want. This cost in memory is very low, but because the purpose of this library is to obtain the maximum performance with the least resources, get even the factory is deleted by default.

intelligent management

GeTx removes unused controllers from memory by default. But what if you want to change the destruction method of geTx control class? You can set different behavior with smartmanagement class.

How to change

If you want to change this configuration (usually not required), use this method.

void main () {
  runApp(
    GetMaterialApp(
      Smartmanagement: smartmanagement.onlybuilders // here
      home: Home(),
    )
  )
}
  • SmartManagement.full
    This is the default. Destroy unused classes that are not set to permanent. In most cases, we use this and don’t need to change it.

  • SmartManagement.onlyBuilders
    With this option, onlyinit:Start the controller orGet.lazyPut()The controller loaded into the binding will be destroyed.

    If you use get. Put() or get. Putasync() or any other method, smartmanagement does not have permission, that is, you cannot remove this dependency.

  • SmartManagement.keepFactory
    Like smartmanagement.full, when it is no longer used, it will delete its dependencies, but it will keep their factories, which means that if the instance is needed again, it will recreate the dependencies.