Android Storage by Dmitry Melnikov
+ - 0:00:00
Notes for current slide
Notes for next slide

Dependency Injection

Dmitry Melnikov

1 / 41

Dependency Injection

What is a dependency?

Class A => *uses* => Class B
Client => *uses* => Service

Class B is both a client and a service:

Class A => *uses* => Class B => *uses* => Class C
2 / 41

How do clients get references to services?

  • instantiation
  • static method call (factory)
  • static global variable
  • gets references "outside"
  • reflection

First three are active dependencies resolution, the fourth one is a dependency injection (DI).

DI is a providing services to clients from "outside".

3 / 41

Dependency Injection

Usually DI has at least two meanings:

The act of providing (injecting) the required dependencies using one of the fundamental techniques: constructor, method or field injection (DI technique).

Dependency injection architecture pattern (DIAP).

4 / 41

Dependency Injection techniques

class Client(private val service1: Service1) {
private var service2: Service2? = null
fun setService2(service2: Service2) {
this.service2 = service2
}
lateinit var service3: Service3
fun doSmth() {
service1.doSmth1()
service2.doSmth2()
service3.doSmth3()
}
}
5 / 41

Dependency Injection techniques

Constructor injection

Pros:

  • simple
  • constructor signature reflects deps
  • injected fields can be finalized (val)
  • easy to mock services in unit tests

Cons:

  • none
6 / 41

Dependency Injection techniques

Method injection

Pros:

  • method signature reflects deps
  • can happen after construction

Cons:

  • not as explicit as a constructor injection
  • can lead to implicit ordering requirements (temporal coupling)
7 / 41

Dependency Injection techniques

Field injection (property in Kotlin)

Pros:

  • can happen after construction

Cons:

  • all cons of the method injection
  • not evident deps
8 / 41

Dependency Injection techniques

Constructor dependency injection technique looks best by far.

Prefer it to other techniques unless:

  • no service instance at the moment of client instantiation (service locator)
  • instantiation is out of your scope (e.g. Activity)
  • limitations on constructor (e.g. Fragment)
9 / 41

Dependency Injection

What is it?

Most popular answer at the StackOverflow:

Dependency Injection is passing dependency to other objects or framework

It's incomplete. It's just a technique.

10 / 41

Dependency Injection

What is it?

Most popular answer at the StackOverflow:

Dependency Injection is passing dependency to other objects or framework

It's incomplete. It's just a technique.

Some more questions

Should such fields be injected?

val list: List<A> = ArrayList()
10 / 41

Dependency Injection

What is it?

Most popular answer at the StackOverflow:

Dependency Injection is passing dependency to other objects or framework

It's incomplete. It's just a technique.

Some more questions

Should such fields be injected?

val list: List<A> = ArrayList()

Is it enough to pass a ServiceLocator to a constructor?

10 / 41

Dependency Injection

What is it?

Most popular answer at the StackOverflow:

Dependency Injection is passing dependency to other objects or framework

It's incomplete. It's just a technique.

Some more questions

Should such fields be injected?

val list: List<A> = ArrayList()

Is it enough to pass a ServiceLocator to a constructor?

Why DI frameworks exist if DI is just a passing deps into constructors?

10 / 41

Dependency Injection

What is it?

Most popular answer at the StackOverflow:

Dependency Injection is passing dependency to other objects or framework

It's incomplete. It's just a technique.

Some more questions

Should such fields be injected?

val list: List<A> = ArrayList()

Is it enough to pass a ServiceLocator to a constructor?

Why DI frameworks exist if DI is just a passing deps into constructors?

If all clients get services from outside, where all of them are instantiated?

10 / 41

Dependency Injection architecture pattern

Segregates application logic into two sets of classes:

  • Functional set

Contains classes that encapsulate core application functionality

  • Construction set

Contains classes that resolve dependencies and instantiate objects from a functional set.

Each class in the app should be a part only of one of these sets.

Application
+----------------+ +-----------------+
|Construction set| | Functional Set |
| | | |
| Class1.. | <----> | Class1.. |
| ClassN | | ClassN |
+----------------+ +-----------------+

Segregation into these sets is a Separation of concerns.

Concerns of core app's functionality separated from concerns of creating and wiring.

11 / 41

Dependency Injection architecture pattern

DI — separation of concerns at the highest level of abstraction.

It's not about frameworks, annotations, or any other implementation details.

12 / 41

DI techniques vs DIAP

Different levels of abstraction (class vs application)

13 / 41

DI techniques vs DIAP

Different levels of abstraction (class vs application)

DI techniques - class level Single Responsibility Principle

13 / 41

DI techniques vs DIAP

Different levels of abstraction (class vs application)

DI techniques - class level Single Responsibility Principle

DIAP - application level Separation of Concerns

13 / 41

DI techniques vs DIAP

Different levels of abstraction (class vs application)

DI techniques - class level Single Responsibility Principle

DIAP - application level Separation of Concerns

DIAP implementations use DI techniques under the hood

13 / 41

DI techniques vs DIAP

Different levels of abstraction (class vs application)

DI techniques - class level Single Responsibility Principle

DIAP - application level Separation of Concerns

DIAP implementations use DI techniques under the hood

It's not enough to employ DI techniques to build DIAP

13 / 41

Dependency Injection sample

Myths:

  • small applications don't need DI
  • it doesn't worth investments
  • DIAP is about frameworks

Pure Dependency Injection aka Manual Dependency Injection aka Vanilla Dependency Injection aka Poor Man's Dependency Injection

Service Locator ≠ DIAP

14 / 41

Dependency Injection sample

git clone git@github.com:melnikovdv/android-arch-2.git
git checkout 2d28dd5
15 / 41

Pure DI

Application
+-----------------------------+ +-----------------+
| Construction set | | Functional Set |
| | | |
| CompostitionRoot | <----> | Class1 |
| ActivityCompositionRoot | | ... |
| PresentationCompositionRoot | | ClassN |
| MvpViewFactory | | |
| Injector | | |
| ... | | |
+-----------------------------+ +-----------------+

Single entry point class for all dependencies

Dependency graph

Responsible for creating services and wiring them together

16 / 41

Law of Demeter

Principle of the least knowledge

Minimize class dependencies

Use of Law of Demeter makes fewer dependencies and more readable and maintainable code.

17 / 41

Objects vs Data Structures

Objects expose behavior.

Data structures expose data.

It's ok to create data structures in-place, but not ok to create objects.

Data structures are Kotlin data classes and Java records.

18 / 41

Dependency Injection benefits

Non-repetitive definition and exposure of the entire object graph.

You can keep your classes small and focused, but still easily compose them into arbitrary long chains to achieve complex functionality.

It enables Single Responsibility Principle and Reusability.

Testability: easier to write unit tests (test doubles, mocks).

Context isolation (for Android, activity leaks)

19 / 41

Additional Dependency Injection benefits

BlogItemMvpFragment dependency on BlogItemMvpViewImpl was eliminated in favor to BlogItemMvpView contract.

Sample profit: A/B testing with different ViewImpls. We can change View without changing Activity code.

It's OCP: an ability to modify functionality of a unit without changing its code.

20 / 41

Injector

Create Injector class with CompositionRoot to inject fields, not this.somethin manually

21 / 41

Injector

Create Injector class with CompositionRoot to inject fields, not this.somethin manually

Annotations and reflection sample to inject automatically

git clone git@github.com:melnikovdv/android-arch-2.git
git checkout 33e7216

Cons

Hardcoded in Injector which is bad

Performance could be bad either (every time inspecting hundreds of fields inherited from the Fragment is quite expensive)

21 / 41

Pure Dependency Injection cons

Pure DI is error-prone and forces you to write lots of code

Can be fixed with a DI framework

22 / 41

Dependency Injection Framework

External library

Provides a specific template for Construction Set implementation

Provides set of pre-defined conventions

23 / 41

Dagger 2

Dagger 2 is the most mature DI framework for now

History

Dagger 1 by Square, deprecated (reflection based)

Dagger 2 by Google

dagger.android package, deprecated

Dagger Hilt

Cons

Dagger is considered the most complex DI framework in Android world

24 / 41

Dependency Injection with Dagger 2

Uses Code generation

Cons

Poor docs

Lack of best practices

Too many features

Pros

No boilerplate code

Easier to refactor

img

25 / 41

Dependency Injection with Dagger 2 sample

git clone git@github.com:melnikovdv/android-arch-2.git
git checkout dagger2
26 / 41

Dependency Injection with Dagger 2

Components and modules (commit 43f27f4)

Components are interfaces annotated with @Component

Modules are classes annotated with @Module

Methods in modules with @Provides provide services

Provided services can be used as method arguments in other provider methods

Scopes are annotations annoteted with @Scope

Components that provide scoped services must be scoped

All clients get the same instance of a scoped service from the same instancce of a Component

Void methods with single argument defined on components generate injectors for the type of the argument

Client's non-private non-final properties (fields) annotated with @Inject designate injection targets

27 / 41

Dependency Injection with Dagger 2

Components dependencies (commit 43b8947)

Component inter-dependencies are specified as part of @Component annotation

Component B that depens on Component A has implicit access to all services exposed by Component A Services from A can be injected by B Serivces from A can be consumed inside modules of B

28 / 41

Dependency Injection with Dagger 2

Subcomponents (commit eaac8a7)

Subcomponents specified by @Subcomponent annotation

Parent componennt exposes factory method which returns Subcomponent

The argument of the factory method are Subcomponent's modules

Subcomponents get access to all services provided by parent (provided, not just exposed)

29 / 41

Dependency Injection with Dagger 2

Multi modules (commit 86dc54f)

Components can use more than one module

Modules of a single Component share the same object graph

Dagger automatically instantiates modules with no-arguments constructors

30 / 41

Dependency Injection with Dagger 2

Automatic injects (commit aca2e5b)

Dagger can automatically discover services having a public constructor annotated with @Inject annotation

Automatically discovered services can be scoped

@Binds annotation helps to resolve dependency types between interfaces and its implementations

31 / 41

Dependency Injection with Dagger 2

Static providers (commit a957b6d)

Dagger generates more performant code for static providers in Modules (use companion object or top-level object in Kotlin)

@Component.Builder (or @Subcomponent.Builder) designates inner builder interface for Component

@BindsInstance allows for injection of "bootstrapping dependencies" directly into Component builders

32 / 41

Dependency Injection with Dagger 2

Qualifiers (commit dc8351e)

Qualifiers are annotations classes annotated with @Qualifier

Qualifiers are part of the type (@Q1 Service and @Q2 Service are different types)

You can use the standard @Named(String) qualifier

33 / 41

Dependency Injection with Dagger 2

Providers (commit bb494cf)

Provider wrappers are "windows" into Dagger's object graph and allow you to retrieve a single type of services

Providers are basically "extensions" of composition roots

You use Providers when you need to perform "late injection" (factories)

34 / 41

Dependency Injection with Dagger 2

Assisted inject

class MyDataService @AssistedInject constructor(
dataFetcher: DataFetcher,
@Assisted config: Config
) {}
@AssistedFactory
interface MyDataServiceFactory {
fun create(config: Config): MyDataService
}
class MyApp {
@Inject lateinit var serviceFactory: MyDataServiceFactory;
fun setupService(config: Config): MyDataService {
val service = serviceFactory.create(config)
...
return service
}
}
35 / 41

When you integrated Dagger 2

image

36 / 41

Who is that guy?

img

37 / 41

WWJD

image

38 / 41

Hilt

https://developer.android.com/training/dependency-injection/hilt-android

git clone git@github.com:melnikovdv/android-arch-2.git
git checkout dagger2
  • Application (by using @HiltAndroidApp)
  • ViewModel (by using @HiltViewModel)
  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver
39 / 41

Hilt

img/hilt-hierarchy.svg

40 / 41

Hilt

Android component Default bindings
-----------------------------------------------------------------
SingletonComponent Application
ActivityRetainedComponent Application
ViewModelComponent SavedStateHandle
ActivityComponent Application, Activity
FragmentComponent Application, Activity, Fragment
ViewComponent Application, Activity, View
ViewWithFragmentComponent Application, Activity, Fragment, View
ServiceComponent Application, Service
41 / 41

Dependency Injection

What is a dependency?

Class A => *uses* => Class B
Client => *uses* => Service

Class B is both a client and a service:

Class A => *uses* => Class B => *uses* => Class C
2 / 41
Paused

Help

Keyboard shortcuts

, , Pg Up, k Go to previous slide
, , Pg Dn, Space, j Go to next slide
Home Go to first slide
End Go to last slide
Number + Return Go to specific slide
b / m / f Toggle blackout / mirrored / fullscreen mode
c Clone slideshow
p Toggle presenter mode
t Restart the presentation timer
?, h Toggle this help
Esc Back to slideshow