Injectable

  • Hỗ trợ tự động inject khi triển khai dependency trong dart.

  • Vui lòng tìm hiểu getIt trước khi đọc bài này.

1. Installation
  • Thêm package vào file pubspec.yaml

    dependencies:  
      # thêm gói injectable
      injectable:  
      # thêm gói get_it  
      get_it:  
      
    dev_dependencies:  
      # thêm gói này đễ hỗ trợ tạo code tự động 
      injectable_generator:  
      # add build runner if not already added  
      build_runner:
    
2. Setup
  • Bước 1 : Tạo mới file dart và khai báo biến gobal GetIt

  • Bước 2 : Khai báo method configureDependencies()

  • Bước 3 : Thêm annotaion @InjectableInit

  • Bước 4: Gọi configureDependencies() tại hàm main()

import '<FILE_NAME>.config.dart';

// Bước 1 : Tạo mới file dart và khai báo biến gobal `GetIt`
final getIt = GetIt.instance;  

// Bước 3: Thêm annotaion `@InjectableInit`
@InjectableInit(  
  initializerName: 'init', // default  
  preferRelativeImports: true, // default  
  asExtension: true, // default  
) 
 
// Bước 2 : Khai báo method `configureDependencies()`
void configureDependencies() => getIt.init();

Note : Bạn có thể chỉ định folder để generate code với thuộc tính generateForDir của @InjectableInit

@InjectableInit(generateForDir: ['test'])  
void configureDependencies() => getIt.init();
void main() { 
 // Bước 4 : Gọi configureDependencies() tại hàm main() 
 configureDependencies();  
 runApp(MyApp());  
}
3. Đăng kí factory
  • Nhắc lại : factory là dạng đăng kí mà mỗi lần bạn access đối tượng từ getIt, thì getIt sẽ tạo ra một intance mới.

  • Để đăng kí factory, bạn chỉ cần thêm annotation @injectable vào các class mong muốn.

    @injectable
    class StorageService {}
      
    @injectable
    class LanguageService {
      final StorageService storageService;
      LanguageService(this.storageService);
    }
    
  • Dùng lệnh để tạo code tự động

    flutter packages pub run build_runner build  
    
  • Và đây là code đươc tạo tự động

    gh.factory<_i3.StorageService>(() => _i3.StorageService());
    gh.factory<_i3.LanguageService>(
        () => _i3.LanguageService(gh<_i3.StorageService>()));
    
4. Đăng kí singleton
  • Nhắc lại : singleton là dạng đăng kí mà getIt chỉ tạo một instance duy nhất cho các đối tượng này, khi bạn access đến đối tượng nó sẽ trả về instance đã tạo, mà không tạo mới.

  • Bạn chỉ cần thêm annotation@singleton hoặc lazysingleton để đăng kí một class singleton.

  • Một số sự thay thế tương đương :

    • getIt.registerSingleton(signalsReady) » @Singleton(signalsReady: true)

    • @LazySingleton() » getIt.registerLazySingleton(() => Model())

  • Ví dụ :

    @singleton
    class AppInfo {}
    
  • Và đây là code được tạo tự động

    gh.singleton<_i3.AppInfo>(_i3.AppInfo());
    
5. Huỷ singleton
  • GetIt cung cấp cách huỷ singleton và lazysingleton

    • Cách 1: Gắn annotation @disposeMethod vào một phương thức của class

      @singleton // or lazySingleton  
      class DataSource {  
          
        @disposeMethod  
        void dispose(){  
          // logic to dispose instance  
        }  
      }  
      
    • Cách 2 : Truyền hàm vào giá trị của annotation

      ```dart
      @Singleton(dispose: disposeDataSource)  
      class DataSource {  
          
        void dispose() {  
          // logic to dispose instance  
        }  
      }  
      /// dispose function signature must match Function(T instance)  
      FutureOr disposeDataSource(DataSource instance){  
         instance.dispose();  
      }  
      

      ```

    • Đây là code được tạo tự động

      gh.singleton<_i4.DataSource>(
        _i4.DataSource(),
        dispose: (i) => i.dispose(),
      );
      gh.singleton<_i4.DataSource2>(
        _i4.DataSource2(),
        dispose: _i4.disposeDataSource,
      );
      
6. FactoryMethod và PostConstruct
  • Mặc định khi đăng kí @injectable, injectable sẽ lấy contructor mặc định, nếu muốn dùng named contructor thì gắn annotation @factoryMethod

    @injectable
    class LoginRepository {
      final RestApi restApi;
      
      LoginRepository(this.restApi);
      
      @factoryMethod
      LoginRepository.from(this.restApi);
    }
    
    • Lúc này, khi generate code, injectable sẽ lấy named contructor
    gh.factory<_i5.LoginRepository>(
            () => _i5.LoginRepository.from(gh<_i5.RestApi>()));
    
  • Annotation @factoryMethod cũng hỗ trợ tạo static method bên trong một abstract class.

    @injectable
    abstract class IAutheService {
      @factoryMethod
      static LoginRepository create(RestApi client) => LoginRepository(client);
      
      @factoryMethod
      factory IAutheService.from() => AutheService();
    }
      
    class AutheService implements IAutheService {} 
    
  • @PostConstruct Annotation này hỗ trợ gọi một hàm ngay khi đối tượng được khởi tạo.

    @Injectable()
    class SomeController {
      SomeController(IAutheService service);
      
      @PostConstruct()
      void init() {
        //...init code
      }
    }
    
    • Khi code được tạo tự động
    gh.factory<_i7.SomeController>(
            () => _i7.SomeController(gh<_i5.IAutheService>())..init());
    
7. Đăng kí Async Injectable
  • Yêu cầu GetIt >= 4.0.0

  • Nếu bạn muốn tạo async factory, bạn cần khai báo một function static trả về một future.

  • Bây giờ chỉ cần chỉ định @injectable cho class, và @factoryMethod cho method static khởi tạo

    @injectable
    class ApiClient {
      @factoryMethod
      static Future<ApiClient> create() async {
        return ApiClient();
      }
    }
    
    • Lúc này, injectable sẽ tạo ra một factoryAsync
    gh.factoryAsync<_i3.ApiClient>(() => _i3.ApiClient.create());
    
8. Pre-Resolving
  • Hỗ trợ await đối tượng trong quá trình đăng kí, thay vì await lúc bạn sử dụng

    @module
    abstract class RegisterModule {
      @preResolve
      Future<SharedPreferences> get prefs => SharedPreferences.getInstance();
    }
    
  • Khi code được generate :

     await gh.factoryAsync<_i7.SharedPreferences>(
          () => registerModule.prefs,
          preResolve: true,
        );
    
  • Annotate @FactoryMethod@Injectable() cũng hỗ trợ điều này

    @FactoryMethod(preResolve: true)  
    @PostConstruct(preResolve: true)  
    
9. Passing Parameters
  • Yêu cầu : GetIt >= 4.0.0

  • Truyền tham số vào đối tượng sử dụng annotation @factoryParam

    @injectable  
    class BackendService {  
      BackendService(@factoryParam String url);  
    }  
    
  • Code được generate như sau :

    factoryParam<BackendService, String, dynamic>(  
       (url, _) => BackendService(url),  
     );  
    
10. Binding abstract classes to implementations
  • Khi sử dụng dependency, chúng ta thường dùng interface truyền vào contructors

  • Dependency sẽ hỗ trợ resolve đối tượng tương ứng. Trong Injectable để làm được điều này chúng ta cần sử dụng

    @Injectable(as: Service)  
    class ServiceImpl implements Service {}  
        
    // or  
    @Singleton(as: Service)  
    class ServiceImpl implements Service {}  
        
    // or  
    @LazySingleton(as: Service)  
    class ServiceImpl implements Service {}  
        
    
  • Ví dụ :

    abstract class IFacebookLogin {
      Future<void> login();
    }
      
    @Injectable(as: IFacebookLogin)
    class FacebookLogin implements IFacebookLogin {
      @override
      Future<void> login() async {
        //
      }
    }
    
  • Code sau khi được generate

    gh.factory<_i7.IFacebookLogin>(() => _i7.FacebookLogin());
    
11. Binding mabstract classes to mulit implementations
  • Trường hợp abstract được implement trên nhiều class thì như thế nào ?

    • Khi sử dụng làm sao biết lấy class nào để inject.
  • Injectable hỗ trợ annnotation @Named

    import 'package:injectable/injectable.dart';
      
    abstract class ISocialLogin {
      Future<void> login();
    }
      
    @Named('facebook')
    @Injectable(as: ISocialLogin)
    class FacebookLogin implements ISocialLogin {
      @override
      Future<void> login() async {
        //
      }
    }
      
    @Named('google')
    @Injectable(as: ISocialLogin)
    class GoogleLogin implements ISocialLogin {
      @override
      Future<void> login() async {
        //
      }
    }
      
    @injectable
    class LoginAuth {
      final ISocialLogin socialLogin;
      
      //Khi sử dụng cần xác định @Named
      LoginAuth(@Named('google') this.socialLogin);
    }
      
    
  • Khi code được generate

    gh.factory<_i7.ISocialLogin>(
      () => _i7.FacebookLogin(),
      instanceName: 'facebook',
    );
    gh.factory<_i7.ISocialLogin>(
      () => _i7.GoogleLogin(),
      instanceName: 'google',
    );
    gh.factory<_i7.LoginAuth>(
        () => _i7.LoginAuth(gh<_i7.ISocialLogin>(instanceName: 'google')));
    
12. Auto Tagging
  • Khi sử dụng @Name bạn cần chuyển một chuỗi cứng vào như thế này :

    @Named('facebook')
    @Named('google')
    
  • Trong khi class nó implement lại là như thế này :

    class GoogleLogin implements ISocialLogin
    class FacebookLogin implements ISocialLogin
    
  • Khi sử dụng cần chỉ định chuỗi cứng đấy như vậy dễ truyền sai và không thống nhất. Để tránh điều này chúng ta có thể dùng tên của class implement luôn.

    abstract class ISocialLogin {
      Future<void> login();
    }
      
    @named
    @Injectable(as: ISocialLogin)
    class FacebookLogin implements ISocialLogin {
      @override
      Future<void> login() async {
        //
      }
    }
      
    @named
    @Injectable(as: ISocialLogin)
    class GoogleLogin implements ISocialLogin {
      @override
      Future<void> login() async {
        //
      }
    }
      
    @injectable
    class LoginAuth {
      final ISocialLogin socialLogin;
      LoginAuth(@Named.from(GoogleLogin) this.socialLogin);
    }
    
14. Register under different environments
  • Annotation @Environment('name') hỗ trợ chỉ định class được generate theo từng môi trường.
15. Registering third party types
  • TBD
16. Auto registering
  • Thay vì phải gắn annotation ở mỗi class bạn muốn inject. Bạn có thể chỉ định bất cứ class nào kết thúc với key cụ thể như Service, Repository hoặc Bloc

  • Để generate tự động, bạn tạo file build.yaml cùng thư mục với pubspec.yaml và thêm đoạn cấu hình sau :

    targets:  
      $default:  
        builders:  
          injectable_generator:injectable_builder:  
            options:  
              auto_register: true  
              # auto registers any class with a name matches the given pattern  
              class_name_pattern:  
                "Service$|Repository$|Bloc$"  
                # auto registers any class inside a file with a  
                # name matches the given pattern  
              file_name_pattern: "_service$|_repository$|_bloc$"
    
  • Manual Order : Mặc định injectable cố gắng đặt phụ thuộc dựa trên các phụ thuộc khác. Nghĩa là nếu A phụ thuộc B thì B sẽ được đăng kí đầu tiên.

  • Bạn có thể chỉ định thủ công thứ tự đăng kí thông qua thuộc tính order

    // @Order(-1) this works too  
    @Injectable(order: -1)  
    class Service{}
    
  • Using Scopes : GetIt 5.0 hỗ trợ scope, nghĩa là nó cho phép đăng kí trong phạm vi khác nhau. Vì vậy nó chỉ được khởi tạo khi cần và huỷ khi không cần nữa

  • Để dùng GetIt scope, gắn annotation @Scope('scope-name') hoặc truyền vào tham số của @Injectable(scope: 'scope-name')

    // @Scope('auth') this works too  
    @Injectable(scope: 'auth')  
    class AuthController{}
    
  • Khi bạn đã sẵn sàng dùng auth-scope, gọi method init hoặc extension

     // using extensions  
     getIt.initAuthScope();  
     // using methods  
     initAuthScope(getIt); 
       
     // scope-init method will return future if it has pre-resolved dependencies  
     // so make sure you await it  
     await getIt.initAuthScope();