📜 ⬆️ ⬇️

Dagger 2. Part Three. New facets of the possible

Hello! Finally came the third part of a series of articles on Dagger 2!


Before further reading, I strongly recommend that you familiarize yourself with the first and second parts.


Many thanks for the feedback and comments. I am very pleased that my articles really help developers plunge into the world of Dagger. This is what gives you the power to create for you further.
In the third part, we will consider various interesting and important features of the library, which may be very useful to you.


In general, the library already exists a decent time, but the documentation is still extremely disgusting. To the developer, who is just beginning his acquaintance with Dagger, I would even advise not to look at the official documentation at the beginning, so as not to be disappointed in this tough and unjust world.


There are, of course, moments that are painted more or less. But here all sorts of new features are described in such a way that I had to try and error, crawling into the generated code, to figure out how it all works. Fortunately, good people write good articles, but even sometimes they do not give a clear and precise answer right away.


So stop talking and move on to new knowledge!


Qualifier annotation


In the last article in the comments asked to highlight this issue. We will not be shelved.


It often happens that we need to provide several objects of the same type. For example, we want to have two Executor on the system: one-threaded, the other with CachedThreadPool . In this case, qualifier annotation comes to the rescue. This is a custom annotation that has the @Qualifier annotation. It sounds a bit like butter, but the example is much simpler.


In general, Dagger2 provides us with one ready-made "qualifier annotation", which, perhaps, is quite enough in everyday life:


 @Qualifier @Documented @Retention(RUNTIME) public @interface Named { /** The name. */ String value() default ""; } 

And now let's see how it all looks in battle:


Qualifier annotation example
 @Module public class AppModule { @Provides @Singleton @Named("SingleThread") public Executor provideSingleThreadExecutor() { return Executors.newSingleThreadExecutor(); } @Provides @Singleton @Named("MultiThread") public Executor provideMultiThreadExecutor() { return Executors.newCachedThreadPool(); } } public class MainActivity extends AppCompatActivity { @Inject @Named("SingleThread") Executor singleExecutor; @Inject @Named("MultiThread") Executor multiExecutor; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MyApplication.getInstance().getAppComponent().inject(this); setContentView(R.layout.activity_main); } } 

As a result, we have two different instances ( singleExecutor , multiExecutor ) of the same class ( Executor ). What we need! I note that objects of the same class annotated with @Named can be @Named also from completely different and independent components as well as from each other.


Delayed initialization


One of our common development problems is the long start of the application. Usually the reason is one thing - we will load and initialize too much at startup. In addition, Dagger2 builds a dependency graph in the main thread. And often, not all objects constructed by Dagger are needed right away. Therefore, the library allows us to delay the initialization of the object until the first call using the Provider<> and Lazy<> interfaces.


Immediately turn our gaze to an example:


Delayed Initialization Example
 @Module public class AppModule { @Provides @Named("SingleThread") public Executor provideSingleThreadExecutor() { return Executors.newSingleThreadExecutor(); } @Provides @Named("MultiThread") public Executor provideMultiThreadExecutor() { return Executors.newCachedThreadPool(); } } public class MainActivity extends AppCompatActivity { @Inject @Named("SingleThread") Provider<Executor> singleExecutorProvider; @Inject @Named("MultiThread") Lazy<Executor> multiExecutorLazy; @Inject @Named("MultiThread") Lazy<Executor> multiExecutorLazyCopy; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MyApplication.getInstance().getAppComponent().inject(this); setContentView(R.layout.activity_main); // Executor singleExecutor = singleExecutorProvider.get(); Executor singleExecutor2 = singleExecutorProvider.get(); // Executor multiExecutor = multiExecutorLazy.get(); Executor multiExecutor2 = multiExecutorLazy.get(); Executor multiExecutor3 = multiExecutorLazyCopy.get(); } } 

Let's start with the Provider<Executor> singleExecutorProvider . Until the first call to singleExecutorProvider.get() Dagger does not initialize the corresponding Executor . But each subsequent singleExecutorProvider.get() call will create a new instance. Thus, singleExecutor and singleExecutor2 are two different objects. This behavior is essentially identical to the behavior of an unscoped object.


In what situations is Provider appropriate? It comes in handy when we provide some kind of mutable relationship that changes its state over time, and with each call we need to get the current state. "What a curve architecture?" - say you, and I agree with you. But when working with legacy code, you will not see that.


I note that the authors of the library also do not advise abusing the Provider interface in places where it is enough to get by with the usual unscope , as this is fraught with "curved architecture", as mentioned above, and difficult to catch bugs.


Now Lazy<Executor> multiExecutorLazy and Lazy<Executor> multiExecutorLazyCopy . Dagger2 initializes the corresponding Executor only when it is first called multiExecutorLazy.get() and multiExecutorLazyCopy.get() . Next, Dagger caches initialized values ​​for each Lazy<> and on the second call, multiExecutorLazy.get() and multiExecutorLazyCopy.get() cached objects.


Thus, multiExecutor and multiExecutor2 refer to one object, and multiExecutor3 to the second object.


But, if we add the @Singleton annotation to the provideMultiThreadExecutor() method in the provideMultiThreadExecutor() , then the object will be cached for the entire dependency tree, and multiExecutor , multiExecutor2 , multiExecutor3 will refer to one object.


Be careful.


Asynchronous loading


We have come up with a very nontrivial task. But what if we want the construction of a dependency graph to take place in the background? It sounds promising? Yes, yes, I'm about Producers .


Honestly, this topic deserves a separate review. There are many features and nuances. For her enough good stuff. Now I will touch only on the pros and cons of Producers .


Pros. Well, the most important advantage is the loading in the background and the ability to manage this loading process.


Minuses. Producers are “dragging” Guava behind them, and that’s plus 15 thousand methods for the apk. But the worst thing is that the application of Producers "spoils" the overall architecture a bit and makes the code more confusing. If you already had a Dagger, and then you decided to move the initialization of objects to the background, you will have to try hard.


In the official documentation, this topic is highlighted in a special section . But I highly recommend the Miroslaw Stanek articles . He generally has a very good blog, and there are many articles about Dagger2. Actually, I even borrowed some models of pictures from past articles from him.
About Producers he writes in this article .


But the next one offers a very interesting alternative for loading the dependency tree in the background. Native RxJava comes to the rescue. I really like his solution, since it is completely free from the drawbacks of using Producers , but it also solves the issue of asynchronous loading.


Only one minus: Miroslav does not quite correctly apply Observable.create(...) . But I wrote about this in the comments on the article , so pay attention necessarily.


And now let's see how the code for the scope of the object will look like (with the “correct” RxJava):


Example with scope
 @Module public class AppModule { @Provides @Singleton // or custom scope for "local" singletons HeavyExternalLibrary provideHeavyExternalLibrary() { HeavyExternalLibrary heavyExternalLibrary = new HeavyExternalLibrary(); heavyExternalLibrary.init(); //This method takes about 500ms return heavyExternalLibrary; } @Provides @Singleton // or custom scope for "local" singletons Observable<HeavyExternalLibrary> provideHeavyExternalLibraryObservable( final Lazy<HeavyExternalLibrary> heavyExternalLibraryLazy) { return Observable.fromCallable(heavyExternalLibraryLazy::get) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); } } public class MainActivity extends AppCompatActivity { @Inject Observable<HeavyExternalLibrary> heavyExternalLibraryObservable; //This will be injected asynchronously HeavyExternalLibrary heavyExternalLibrary; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MyApplication.getInstance().getAppComponent().inject(this); setContentView(R.layout.activity_main); // init HeavyExternalLibrary in background thread! heavyExternalLibraryObservable.subscribe( heavyExternalLibrary1 -> heavyExternalLibrary = heavyExternalLibrary1, throwable -> {} ); } } 

Notice the @Singleton and Lazy interface in the AppModule . Lazy just guarantees that the heavy object will be initialized when we request, and then cached.


And how should we be if we want to receive a new copy of this "heavy" object every time? Then it is worth changing AppModule :


Unscope example
 @Module public class AppModule { @Provides // No scope! HeavyExternalLibrary provideHeavyExternalLibrary() { HeavyExternalLibrary heavyExternalLibrary = new HeavyExternalLibrary(); heavyExternalLibrary.init(); //This method takes about 500ms return heavyExternalLibrary; } @Provides @Singleton // or custom scope for "local" singletons Observable<HeavyExternalLibrary> provideHeavyExternalLibraryObservable( final Provider<HeavyExternalLibrary> heavyExternalLibraryLazy) { return Observable.fromCallable(heavyExternalLibraryLazy::get) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); } } public class MainActivity extends AppCompatActivity { @Inject Observable<HeavyExternalLibrary> heavyExternalLibraryObservable; //This will be injected asynchronously HeavyExternalLibrary heavyExternalLibrary; HeavyExternalLibrary heavyExternalLibraryCopy; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MyApplication.getInstance().getAppComponent().inject(this); setContentView(R.layout.activity_main); // init HeavyExternalLibrary and heavyExternalLibraryCopy in background thread! heavyExternalLibraryObservable.subscribe( heavyExternalLibrary1 -> heavyExternalLibrary = heavyExternalLibrary1, throwable -> {} ); heavyExternalLibraryObservable.subscribe( heavyExternalLibrary1 -> heavyExternalLibraryCopy = heavyExternalLibrary1, throwable -> {} ); } } 

For the provideHeavyExternalLibrary() method, we have removed the scope , and in the provideHeavyExternalLibraryObservable(final Provider<HeavyExternalLibrary> heavyExternalLibraryLazy) use the Provider instead of Lazy . Thus, the heavyExternalLibrary and heavyExternalLibraryCopy in MainActivity are different objects.


And you can even make the whole process of initializing the dependency tree to the background. You ask how? Very easy. First, look at how it was:


SplashActivity from the article of Miroslav
 public class SplashActivity extends BaseActivity { @Inject SplashActivityPresenter presenter; @Inject AnalyticsManager analyticsManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setupActivityComponent(); } @Override protected void setupActivityComponent() { final SplashActivityComponent splashActivityComponent = GithubClientApplication.get(SplashActivity.this) .getAppComponent() .plus(new SplashActivityModule(SplashActivity.this)); splashActivityComponent.inject(SplashActivity.this); } } 

And now let's take a look at the updated void setupActivityComponent() method (with my RxJava edits):


void setupActivityComponent ()
 @Override protected void setupActivityComponent() { Completable.fromAction(() -> { final SplashActivityComponent splashActivityComponent = GithubClientApplication.get(SplashActivity.this) .getAppComponent() .plus(new SplashActivityModule(SplashActivity.this)); splashActivityComponent.inject(SplashActivity.this); }) .doOnCompleted(() -> { //Here is the moment when injection is done. analyticsManager.logScreenView(getClass().getName()); presenter.callAnyMethod(); }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(() -> {}, throwable -> {}); } 

Measurements


In the last section, we talked about performance at the start of the application. However, we know that if the question concerns performance and speed, we must measure! To rely on intuition and the feeling "it seems to have become faster" is impossible. And with this Miroslav will help us again in this and this articles. So that we can do everything without him, I have no idea at all.


New interesting features


Dagger has new interesting features that promise us to make life easier. But understanding how everything works and what gives us all this was not an easy task. Well, let's start!


@Reusable scope


An interesting annotation. It allows you to save memory, but at the same time it is in fact not limited to any scope , which makes it very convenient to reuse dependencies in any components. That is, it is something between the scope and unscope .


At the docks they write a very important point, which somehow doesn’t strike the eye from the first time: " For each component that uses the @Reusable dependency, this dependency is cached separately ." And my addition: " Unlike scope annotations, where the object is cached at creation and its instance is used by child and dependent components ."


And now just an example to understand everything:


Long clarification example

Our main ingredient.


 @Component(modules = {AppModule.class, UtilsModule.class}) @Singleton public interface AppComponent { FirstComponent.Builder firstComponentBuilder(); SecondComponent.Builder secondComponentBuilder(); } 

AppComponent has two Subcomponent . Drew attention to this design - FirstComponent.Builder ? About her, we will later.
Now look at the UtilsModule .


 @Module public class UtilsModule { @Provides @NonNull @Reusable public NumberUtils provideNumberUtils() { return new NumberUtils(); } @Provides @NonNull public StringUtils provideStringUtils() { return new StringUtils(); } } 

NumberUtils with @Reusable , and StringUtils left unscoped .
Next we have two Subcomponents .


 @FirstScope @Subcomponent(modules = FirstModule.class) public interface FirstComponent { @Subcomponent.Builder interface Builder { FirstComponent.Builder firstModule(FirstModule firstModule); FirstComponent build(); } void inject(MainActivity mainActivity); } @SecondScope @Subcomponent(modules = {SecondModule.class}) public interface SecondComponent { @Subcomponent.Builder interface Builder { SecondComponent.Builder secondModule(SecondModule secondModule); SecondComponent build(); } void inject(SecondActivity secondActivity); void inject(ThirdActivity thirdActivity); } 

As we can see, FirstComponent injects only into MainActivity , and SecondComponent into SecondActivity and ThirdActivity .
Let's see the code.


 public class MainActivity extends AppCompatActivity { @Inject NumberUtils numberUtils; @Inject StringUtils stringUtils; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyApplication.getInstance().getFirstComponent() .inject(this); // other... } } public class SecondActivity extends AppCompatActivity { @Inject NumberUtils numberUtils; @Inject StringUtils stringUtils; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); MyApplication.getInstance().getSecondComponent() .inject(this); // other... } } public class ThirdActivity extends AppCompatActivity { @Inject NumberUtils numberUtils; @Inject StringUtils stringUtils; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_third); MyApplication.getInstance().getSecondComponent() .inject(this); // other... } } 

Briefly about navigation. From MainActivity we get to SecondActivity , and then to ThirdActivity . And now the question. When we will be on the third screen, how many NumberUtils and StringUtils will be created?


Since StringUtils is unscoped , three instances will be created, that is, with each injection, a new object is created. This we know.


But NumberUtils objects will be two - one for FirstComponent , and the other for SecondComponent . And here I will again bring the main idea about @Reusable from the documentation: " For each component that uses the @Reusable dependency, this dependency is cached separately! ", Unlike the scope annotation, where the object is cached at creation and its instance is used by child and dependent components .


But Google users themselves warn that if you need a unique object, which can also be mutable, then use only scoped annotations.


I will also give a link to the question about comparing @Singleton and @Reusable with SO.


@ Subcomponent.Builder


A feature that makes the code more beautiful. Previously, to create @Subcomponent we had to write something like this:


As it was
 @Component(modules = {AppModule.class, UtilsModule.class}) @Singleton public interface AppComponent { FirstComponent plusFirstComponent(FirstModule firstModule, SpecialModule specialModule); } @FirstScope @Subcomponent(modules = {FirstModule.class, SpecialModule.class}) public interface FirstComponent { void inject(MainActivity mainActivity); } 

Creating FirstComponent :


 appComponent .plusFirstComponent(new FirstModule(), new SpecialModule()); 

I did not like in this approach that the parent component was loaded with unnecessary knowledge about the modules that the child subcomponents use. Well, plus the transfer of a large number of arguments does not look very nice, because for this there is a Builder pattern. Now it has become more beautiful:


As it became
 @Component(modules = {AppModule.class, UtilsModule.class}) @Singleton public interface AppComponent { FirstComponent.Builder firstComponentBuilder(); } @FirstScope @Subcomponent(modules = {FirstModule.class, SpecialModule.class}) public interface FirstComponent { @Subcomponent.Builder interface Builder { FirstComponent.Builder firstModule(FirstModule firstModule); FirstComponent.Builder specialModule(SpecialModule specialModule); FirstComponent build(); } void inject(MainActivity mainActivity); } 

Creating a FirstComponent now looks like this:


 appComponent .firstComponentBuilder() .firstModule(new FirstModule()) .specialModule(new SpecialModule()) .build(); 

Another thing =)


static


Now we have the opportunity to do this:


 @Provides static User currentUser(AuthManager authManager) { return authManager.currentUser(); } 

That is, we can make the methods responsible for providing dependencies in the modules static. At first I didn’t quite understand, but why is it really necessary? And it turns out, the request for such a feature existed for a long time, and there are situations when it is profitable.


SO asked a good question on this topic, they say, and what is the difference @Singleton and @Provide static . To understand this difference well, you need to read the answer to the question, experimenting in parallel and looking at the generated code.


So, we have an introductory. We have three variants of the same method in the module:


 @Provides User currentUser(AuthManager authManager) { return authManager.currentUser(); } @Provides @Singleton User currentUser(AuthManager authManager) { return authManager.currentUser(); } @Provides static User currentUser(AuthManager authManager) { return authManager.currentUser(); } 

In this case, authManager.currentUser() at different points in time can give different instances.
The logical question is: how do these methods differ?


In the first case, we have a classic unscope . Each request will be given a new instance of authManager.currentUser() (more precisely, a new reference to currentUser ).


In the second case, the first request will be cached with a link to currentUser , and with each new request this link will be given. That is, if the currentUser changed in AuthManager , then the old reference to the invalid instance will be given.


The third case is more interesting. This method is similar in behavior to unscope , that is, each request will be given a new link. This is the first difference from @Singleton , which caches objects. Thus, placing the object's initialization in the @Provide static method is not entirely appropriate.


But then @Provide static different from unscope ? Suppose we have such a module:


 @Module public class AuthModule { @Provides User currentUser(AuthManager authManager) { return authManager.currentUser(); } } 

AuthManager comes from another module as a Singleton . Now let's quickly take a look at the generated AuthModule_CurrentUserFactory code (in the studio, just put the cursor on currentUser and press Ctrl + B):


Unscope
 @Generated( value = "dagger.internal.codegen.ComponentProcessor", comments = "https://google.imtqy.com/dagger" ) public final class AuthModule_CurrentUserFactory implements Factory<User> { private final AuthModule module; private final Provider<AuthManager> authManagerProvider; public AuthModule_CurrentUserFactory( AuthModule module, Provider<AuthManager> authManagerProvider) { assert module != null; this.module = module; assert authManagerProvider != null; this.authManagerProvider = authManagerProvider; } @Override public User get() { return Preconditions.checkNotNull( module.currentUser(authManagerProvider.get()), "Cannot return null from a non-@Nullable @Provides method"); } public static Factory<User> create(AuthModule module, Provider<AuthManager> authManagerProvider) { return new AuthModule_CurrentUserFactory(module, authManagerProvider); } /** Proxies {@link AuthModule#currentUser(AuthManager)}. */ public static User proxyCurrentUser(AuthModule instance, AuthManager authManager) { return instance.currentUser(authManager); } } 

And if you add static to currentUser :


 @Module public class AuthModule { @Provides static User currentUser(AuthManager authManager) { return authManager.currentUser(); } } 

Then we get:


static
 @Generated( value = "dagger.internal.codegen.ComponentProcessor", comments = "https://google.imtqy.com/dagger" ) public final class AuthModule_CurrentUserFactory implements Factory<User> { private final Provider<AuthManager> authManagerProvider; public AuthModule_CurrentUserFactory(Provider<AuthManager> authManagerProvider) { assert authManagerProvider != null; this.authManagerProvider = authManagerProvider; } @Override public User get() { return Preconditions.checkNotNull( AuthModule.currentUser(authManagerProvider.get()), "Cannot return null from a non-@Nullable @Provides method"); } public static Factory<User> create(Provider<AuthManager> authManagerProvider) { return new AuthModule_CurrentUserFactory(authManagerProvider); } /** Proxies {@link AuthModule#currentUser(AuthManager)}. */ public static User proxyCurrentUser(AuthManager authManager) { return AuthModule.currentUser(authManager); } } 

Note that in the static version there is no AuthModule . Thus, the static method is jerked by the component directly, bypassing the module . And if there is only one static method in the module, the module instance is not even created.


Savings and minus unnecessary challenges. Actually we have a performance gain. They also write that calling a static method is 15-20% faster than calling a similar non-static method. If I'm wrong, iamironz will correct me. Oh, he knows for sure, and if necessary, he will.


@Binds + inject constructor


Mega-convenient bundle, which significantly reduces the boilerplate-code. At the dawn of studying Dagger, I did not understand why we needed designer injections. What and where it comes from. And then @Binds appeared. But everything is actually quite simple. Thank you for helping Vladimir Tagakov and this article .


Consider a typical situation. There is a Presenter interface and its implementation:


 public interface IFirstPresenter { void foo(); } public class FirstPresenter implements IFirstPresenter { public FirstPresenter() {} @Override public void foo() {} } 

We, as white people, will provide the whole thing in the module and inject the Presenter interface into activations:


 @Module public class FirstModule { @Provides @FirstScope public IFirstPresenter provideFirstPresenter() { return new FirstPresenter(); } } public class MainActivity extends AppCompatActivity { @Inject IFirstPresenter firstPresenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyApplication.getInstance().getFirstComponent() .inject(this); // others } } 

Suppose that our FirstPresenter needs helpers to whom it delegates some of the work. To do this, you need to create two more methods in the module, which will provide new classes, then change the FirstPresenter constructor, and FirstPresenter update the corresponding method in the module.


The module will be:


 @Module public class FirstModule { @Provides @FirstScope public HelperClass1 provideHelperClass1() { return new HelperClass1(); } @Provides @FirstScope public HelperClass2 provideHelperClass2() { return new HelperClass2(); } @Provides @FirstScope public IFirstPresenter provideFirstPresenter( HelperClass1 helperClass1, HelperClass2 helperClass2) { return new FirstPresenter(helperClass1, helperClass2); } } 

And so every time, if you need to add some class and "share" it with others. "" . - , ? , .


-, , ( HelperClass1 HelperClass2 ), . :


 @FirstScope public class HelperClass1 { @Inject public HelperClass1() { } } @FirstScope public class HelperClass2{ @Inject public HelperClass2() { } } 

, @FirstScope , , .


HelperClass1 HelperClass2 :


 @Module public class FirstModule { @Provides @FirstScope public IFirstPresenter provideFirstPresenter( HelperClass1 helperClass1, HelperClass2 helperClass2) { return new FirstPresenter(helperClass1, helperClass2); } } 

? @Binds :


 @Module public abstract class FirstModule { @FirstScope @Binds public abstract IFirstPresenter provideFirstPresenter(FirstPresenter firstPresenter); } 

FirstPresenter :


 @FirstScope public class FirstPresenter implements IFirstPresenter { private HelperClass1 helperClass1; private HelperClass2 helperClass2; @Inject public FirstPresenter(HelperClass1 helperClass1, HelperClass2 helperClass2) { this.helperClass1 = helperClass1; this.helperClass2 = helperClass2; } @Override public void foo() {} } 

? FirstModule , provideFirstPresenter . provideFirstPresenter @Provide , @Binds . , !
FirstPresenter scope — @FirstScope , , . @Inject . , !


Mujahit .
, FirstModule FirstComponent , AppComponent . FirstComponent :


 appComponent .firstComponentBuilder() .firstModule(new FirstModule()) .specialModule(new SpecialModule()) .build(); 

FirstModule , ? , , , :


 appComponent .firstComponentBuilder() .build(); 

, .


, , :


 @Module public interface FirstModule { @FirstScope @Binds IFirstPresenter provideFirstPresenter(FirstPresenter firstPresenter); } 

. "" :


 @Module public abstract class FirstModule { @FirstScope @Binds public abstract IFirstPresenter provideFirstPresenter(FirstPresenter firstPresenter); @FirstScope @Provides public HelperClass3 provideHelperClass3() { // <- Incorrect! return new HelperClass3(); } @FirstScope @Provides public static HelperClass3 provideHelperClass3() { // <- Ok! return new HelperClass3(); } } 


:


  1. Muitibindings . "" ( Set Map ). ("plugin architecture"). . Muitibindings . . .


  2. Releasable references . . , . .
    ( Releasable references ) , .


  3. . , Unit- . , UI . Artem_zin . . - , . . ( ) . , , , Application . ?


  4. @BindsOptionalOf . Optional Java 8 Guava, . , .


  5. @BindsInstance . , dagger 2.8 . , - . , AppComponent Context . . .

Well that's all! . - , ! Fix it. Dagger2 , .


, . . , AndroidDevPodcast , . Follow the news!


')

Source: https://habr.com/ru/post/320676/


All Articles