Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Unconventional Android - Dependency Injection w...

Unconventional Android - Dependency Injection with Dagger

Richard Cirerol

March 18, 2017
Tweet

More Decks by Richard Cirerol

Other Decks in Programming

Transcript

  1. Agenda 4 Introductions 4 What is Unconventional Android? 4 Dependency

    Injection in Java and Android 4 Dagger 2 Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 2
  2. Richard Cirerol Android Core Team Lead BAMTech Unconventional Android -

    Dependency Injection. ©2017 Richard Cirerol 5
  3. Android development that follows conventional software development practices. Not (necessarily)

    the Google sample way. Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 11
  4. Android development that follows conventional software development practices. Not (necessarily)

    the Google sample way. Therefore, unconventional. Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 12
  5. Electronic Program Guide A schedule of events 4 Activity (View)

    4 Presenter 4 Service (Model) Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 13
  6. Activity class ScoreboardActivity: AppCompatActivity { override fun onCreate(saveInstanceState: Bundle){ val

    eventService = Retrofit.Builder() //...build up service .build().create<EventService>() val schedule = api.getSchedule() setupRecyclerView(schedule) } } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 14
  7. We'll just use MVP (Model-View-Presenter) to make things a little

    cleaner... Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 15
  8. Activity class ScoreboardActivity: AppCompatActivity { lateinit var presenter: ScoreboardPresenter; override

    fun onCreate(saveInstanceState: Bundle){ val service = EventServiceBuilder.get() presenter = ScoreboardPresenter(service) } override fun onResume() { presenter.getSchedule().subscribe(subscriber) } } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 16
  9. Presenter class ScoreboardPresenter(val service: EventService) { fun getSchedule(): Single<List<Event>> {

    return service.getEvents(today) } } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 17
  10. Service interface EventService { @GET fun getEvents(start: DateTime, end: DateTime?):

    Single<List<Event>> @GET fun getEvent(id:Long): Single<Event> } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 18
  11. Effects of High Coupling1 A change in one module usually

    forces a ripple effect of changes in other modules. 1 https://en.wikipedia.org/wiki/Coupling_(computer_programming) Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 21
  12. Cohesion2 4 the degree to which the elements of a

    module belong together 2 https://en.wikipedia.org/wiki/Cohesion_(computer_science) Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 22
  13. High Cohesion2 Traits: 4 robust 4 reliable 4 reusable 4

    understandable 2 https://en.wikipedia.org/wiki/Cohesion_(computer_science) Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 23
  14. Low Cohesion2 Traits: 4 difficult to maintain 4 difficult to

    test 4 difficult to reuse 4 difficult to understand 2 https://en.wikipedia.org/wiki/Cohesion_(computer_science) Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 24
  15. Activity class ScoreboardActivity: AppCompatActivity { override fun onCreate(saveInstanceState: Bundle){ val

    service = EventServiceBuilder.get() presenter = ScoreboardPresenter(service) } } class GameActivity: AppCompatActivity { override fun onCreate(saveInstanceState: Bundle){ val service = EventServiceBuilder.get() presenter = GamePresenter(service) } } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 26
  16. @Test fun testEventService(){ val service = EventServiceBuilder.get() // <- Why

    is this bad? //... } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 27
  17. Activity class ScoreboardActivity: AppCompatActivity { override fun onCreate(saveInstanceState: Bundle){ val

    service = EventServiceBuilder.get(BuildConfig.ENVIRONMENT) presenter = ScoreboardPresenter(service) } } class GameActivity: AppCompatActivity { override fun onCreate(saveInstanceState: Bundle){ val service = EventServiceBuilder.get(BuildConfig.ENVIRONMENT) presenter GamePresenter(service) } } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 29
  18. Problem #3 (testability, reusability) The presenters are highly coupled to

    EventService Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 30
  19. Service class RestEventService : EventService { //... } class WebSocketEventService

    : EventService { //... } class InMemoryEventService : EventService { //... class TestEventService : EventService { //... } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 31
  20. Problem #4 (reliability, maintainability) The presenter may change its dependencies

    Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 32
  21. class ScoreboardPresenter( val eventService: EventService, val statsService: StatsService) { fun

    getSchedule(): Single<List<Event>> { return service.getEvents(today) .combineWith(statsService.getStats()) } } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 33
  22. // ScoreboardActivity val eventService = EventServiceBuilder.get() val statsService = StatsServiceBuilder.get()

    presenter = ScoreboardPresenter(eventService, statsService) //... presenter.getSchedule().subscribe(subscriber) Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 34
  23. Inversion of Control 4 Reverses the creation of dependencies 4

    Encourages: 4 High cohesion 4 Low coupling Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 38
  24. Inversion of Control Two ways to invert dependencies... Service Locator

    vs Dependency Injection3 3 https://martinfowler.com/articles/injection.html#ServiceLocatorVsDependencyInjection Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 39
  25. Service Locator A class asks a locator for its dependencies.

    Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 40
  26. Service Locator class ScoreboardActivity: AppCompatActivity { lateinit var presenter: ScoreboardPresenter;

    fun onCreate(saveInstanceState: Bundle) { presenter = ServiceLocator.get<ScoreboardPresenter>() } } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 41
  27. Service Locator Singleton Registry: - Dependencies often stored in a

    map - The ScoreboardPresenter will be provided by the ServiceLocator - The ScoreboardPresenter should have its dependencies provided before being returned by the ServiceLocator Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 42
  28. Service Locator class ScoreboardActivity: AppCompatActivity { lateinit var presenter: ScoreboardPresenter;

    lateinit var prefs: SharedPreferences; fun onCreate(saveInstanceState: Bundle) { presenter = ServiceLocator.get<ScoreboardPresenter>() prefs = ServiceLocator.get<SharedPreferences>() } } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 43
  29. Dependency Injection class ScoreboardActivity: AppCompatActivity { lateinit var presenter: ScoreboardPresenter;

    @Inject set fun onCreate(saveInstanceState: Bundle) { val injector = ScheduleInjector.create() injector.inject(this) } } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 45
  30. Dependency Injection class ScoreboardActivity: AppCompatActivity { lateinit var presenter: ScoreboardPresenter;

    @Inject set lateinit var prefs: SharedPreferences; @Inject set fun onCreate(saveInstanceState: Bundle) { val injector = ScheduleInjector.create() injector.inject(this) } } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 46
  31. Dependency Injection 4 The target class declares its dependencies with

    the @Inject annotation 4 The injector builds a graph of the dependencies - often called an object graph or dependency graph. 4 The injector provides the target class with the fully constructed dependencies when inject(target) is called. Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 47
  32. JSR-330 4 The change that promoted dependency injection in Java.

    4 Released in October 2009. Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 49
  33. JSR-330 We propose to maximize reusability, testability and maintainability of

    Java code by standardizing an extensible dependency injection API. -- Google and SpringSource Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 50
  34. JSR-330 4 @Inject: marks a requested dependency 4 @Scope: meta-annotation

    for dependency lifecycle 4 @Singleton: application-wide @Scope 4 @Qualifier: meta-annotation for dependency differentiation 4 @Named: string-based @Qualifier 4 Provider<T> Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 51
  35. Constructor Injection //java public class ScoreboardPresenter { private EventService service;

    @Inject public ScoreboardPresenter(EventService service){ this.service = service; } } //kotlin class ScoreboardPresenter @Inject constructor(val service: EventService) Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 53
  36. Constructor Injection 4 Preferred injection type 4 Always in a

    valid state 4 All constructor dependencies must be available at creation Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 54
  37. Field Injection //java public class ScoreboardActivity implements AppCompatActivity { @Inject

    ScoreboardPresenter presenter; @Override public void onCreate(Bundle savedInstanceState) { Injector.inject(this); } } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 55
  38. Field Injection //kotlin class ScoreboardActivity: AppCompatActivity() { lateinit var presenter:

    ScoreboardPresenter @Inject set override fun onCreate(savedInstanceState: Bundle) { Injector.inject(this) } } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 56
  39. Field Injection 4 Good for classes where you are not

    responsible for construction 4 May be in invalid state after construction 4 Sometimes called Interface Injection Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 57
  40. Method Injection //kotlin @Inject fun setDependency(presenter: Presenter) { this.presenter =

    presenter } @Inject fun register(publisher: Publisher) { publisher.registerListener(this) } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 58
  41. Method Injection 4 Similar to field injection, but allows for

    side effects 4 If used to assign members, class may be in an invalid state 4 Also called Setter Injection Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 59
  42. Dagger 2 A Dependency Injection Framework for Java and Android

    Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 60
  43. DAG-ger D irected A cyclic G raph Unconventional Android -

    Dependency Injection. ©2017 Richard Cirerol 64
  44. Dagger 2 4 A DAG of dependencies 4 Does not

    use reflection to get dependencies 4 Instead, generates the dependency graph at compile- time via annotation processing and code generation Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 68
  45. Adding Dagger 2 to a Java-based Android Project // ./build.gradle

    dependencies { // ... classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' } // ./app/build.gradle apply plugin: 'com.android.application' apply plugin: 'com.neenbedankt.android-apt' dependencies { compile "com.google.dagger:dagger:$daggerVersion" apt "com.google.dagger:dagger-compiler:$daggerVersion" } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 69
  46. Adding Dagger 2 to a Kotlin-based Android Project // ./build.gradle

    dependencies { // ... // add kotlin plugin classpath } // ./app/build.gradle apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' dependencies { // add kotlin stdlib compile "com.google.dagger:dagger:$daggerVersion" kapt "com.google.dagger:dagger-compiler:$daggerVersion" } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 70
  47. Dagger 2 Annotations 4 @Module: (class, abstract class) marks a

    dependency container 4 @Provides: (method) marks a constructed dependency 4 @Binds: (abstract method) marks a constructed dependency Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 71
  48. Dagger 2 Annotations (continued) @Component: (interface) 4 uses modules (dependency

    composition) 4 defines injection targets 4 defines provisioning methods 4 can depend on other components Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 72
  49. Dagger 2 Annotations (continued) @Subcomponent: (interface) 4 uses modules (dependency

    composition) 4 defines injection targets 4 created by parent component Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 73
  50. Providing Dependencies There are two ways to provide dependencies in

    Dagger: - Annotating Classes - Via a Module-annotated class Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 74
  51. Annotated Classes @Singleton class RestEventService: EventService // Declares the class

    as a dependency @Inject constructor() Dependencies annotated with @Singleton will always return the same instance. Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 75
  52. Annotated Classes The @Scope annotation (from JSR-330) @Scope @Retention(AnnotationRetention.RUNTIME) annotation

    class ActivityScope A meta-annotation to define a custom scope. Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 76
  53. Annotated Classes Applying a custom scope @ActivityScope class ScoreboardPresenter @Inject

    constructor(val service: EventService) Classes annotated with a custom scope are "singletons" within the scope. Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 77
  54. Annotated Classes Transient Scope class ScoreboardItemPresenter @Inject constructor() Classes not

    annotated with a scope return a new instance for each call. Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 78
  55. Dagger Modules 4 Provide constructed dependencies 4 Can be composed

    (include other modules) Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 79
  56. @Module(includes = arrayOf(PreferencesModule::class)) class ApplicationModule @Module class PreferencesModule(private val context:Context)

    { @Provides @Singleton fun sharedPreferences(): SharedPreferences { return PreferenceManager.getDefaultSharedPreferences(context) } } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 80
  57. Module @Provides Marks a module method for binding by the

    compiler. Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 81
  58. Module Modules cannot mix scopes 4 All scope annotations must

    match 4 Methods with no scope are allowed Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 82
  59. @Module class ScoreboardActivityModule { @Provides fun dependency1(): Dependency1 { return

    Dependency1.get() } @ActivityScope @Provides fun dependency2(): Dependency2 { return Dependency2.get() } // !!! Cannot mix scopes in a module @Singleton @Provides fun dependency3(): Dependency3 { return Dependency3.get() } } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 83
  60. Binding Interfaces The interface... interface PersistentStore { fun save(key: String,

    data: String) fun load(key: String) : String? fun delete(key: String) } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 85
  61. Binding Interfaces An implementation... @Singleton class DefaultPersistentStore @Inject constructor(val prefs:SharedPreferences)

    : PersistentStore { override fun save(key: String, data: String) { prefs.edit().putString(key, data).commit() } override fun load(key: String): String? { return prefs.getString(key, null) } override fun delete(key: String) { prefs.edit().remove(key).commit() } } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 86
  62. Binding Interfaces Don't do this... @Module(includes = arrayOf(PreferencesModule::class)) class ApplicationModule

    { @Provides @Singleton fun storage(prefs: SharedPreferences): PersistentStore { return DefaultPersistentStore(prefs) } } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 87
  63. Binding Interfaces For interface dependencies: 4 Use an abstract class

    4 Use @Binds instead of @Provides Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 88
  64. Binding Interfaces @Module(includes = arrayOf(PreferencesModule::class)) abstract class ApplicationModule { @Binds

    @Singleton abstract fun storage(store: DefaultPersistentStore): PersistentStore @Binds @Singleton @Named("REST") abstract fun restEventService(service: RestEventService): EventService @Binds @Singleton @Named("WS") abstract fun wsEventService(service: WsEventService): EventService } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 89
  65. Module Rules 4 Only include dependencies that must be configured,

    created, or built 4 Do not mix scopes in modules 4 Use an abstract class with @Binds to define the interface/implementation couplets Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 90
  66. Component @Singleton @Component(modules = arrayOf(ApplicationModule::class)) interface ApplicationComponent { // Members

    injection fun inject(application: MobileApplication) } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 93
  67. Component A scoped @Component means dependencies must: 4 Be annotated

    with the same scope annotation, or... 4 Be transient (no scope annotation) Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 94
  68. Component 4 Concrete implementation created at compile time 4 The

    generated class is prefixed with Dagger 4 The concrete component is configured via a builder 4 Each declared (and nested) module is configured via the builder Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 95
  69. class MobileApplication: Application() { @set:Inject lateinit var storage: PersistentStore @set:Inject

    lateinit var prefs: SharedPreferences companion object { lateinit internal var component: ApplicationComponent } override fun onCreate(){ component = DaggerApplicationComponent.builder() //.applicationModule(ApplicationModule()) .preferencesModule(PreferencesModule(this)) .build() component.inject(this) } } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 96
  70. Component component.inject(this) Assigns all the target's dependencies from the "directed

    acyclic graph". Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 97
  71. Component An annotated interface that: 4 Defines injection targets 4

    Provisions methods to other components Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 98
  72. Provisioned Dependencies (Exports) @Singleton @Component(modules = arrayOf(ApplicationModule::class)) interface ApplicationComponent{ //

    Members injection fun inject(application: MobileApplication) // Provisioning methods (exports) // Exposes dependencies to other components fun prefs(): SharedPreferences } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 99
  73. class ScoreboardActivity: AppCompatActivity() { @set:Inject lateinit var presenter: ScoreboardPresenter @set:Inject

    lateinit var prefs: SharedPreferences var component: ScoreboardActivityComponent? = null override fun onCreate(savedInstanceState: Bundle) { component = DaggerScoreboardActivityComponent.builder() .applicationComponent(MobileApplication.component) .build() component?.inject(this) } override fun onDestroy() { component = null } } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 100
  74. Component A Component can depend on other components. @ActivityScope @Component(dependencies=arrayOf(ApplicationComponent::class))

    interface ScoreboardActivityComponent { fun inject(activity: ScoreboardActivity) } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 101
  75. Provisioning Method A Component can expose dependencies to other components.

    // interface ApplicationComponent fun prefs(): SharedPreferences Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 102
  76. //class ScoreboardActivity @set:Inject lateinit var presenter: ScoreboardPresenter @set:Inject lateinit var

    prefs: SharedPreferences Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 103
  77. class ScoreboardPresenter @Inject constructor(val service: EventService) EventService is annotated with

    @Singleton in the ApplicationModule. Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 104
  78. Dagger 2 Component Inheritance 4 Via @Component annotation parameter 4

    @Subcomponent Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 106
  79. Component Inheritance @ActivityScope @Component(dependencies = arrayOf(ApplicationComponent.class), modules = arrayOf(ScoreboardActivityModule.class) interface

    ScoreboardActivityComponent { fun inject(activity: ScoreboardActivity) } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 107
  80. Subcomponent Inheritance @ActivityScope @Subcomponent(modules = arrayOf(ScoreboardActivityModule.class) interface ScoreboardActivitySubcomponent { fun

    inject(activity: ScoreboardActivity) } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 108
  81. Subcomponent Inheritance @Singleton @Component(modules = arrayOf(ApplicationModule.class)) interface ScoreboardActivityComponent { fun

    inject(application: MobileApplication) fun scoreboardSubcomponent(module: ScoreboardActivityModule) : ScoreboardActivitySubcomponent } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 109
  82. // ScoreboardActivity fun onCreate(savedInstanceState: Bundle) { val subcomponent = MobileApplication.component()

    .scoreboardSubcomponent(ScoreboardActivityModule(this)) subcomponent.inject(this) } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 110
  83. // ScoreboardActivity @Inject lateinit var presenter: ScoreboardPresenter @Inject lateinit var

    prefs: SharedPreferences fun onCreate(savedInstanceState: Bundle) { val subcomponent = MobileApplication.component() .scoreboardSubcomponent() // <- service locator subcomponent.inject(this) // <- dependency injection } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 111
  84. Interesting Uses for Subcomponent Activities with common - Views -

    Behaviors Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 112
  85. @ActivityScope @Subcomponent interface CommonSubcomponent { fun inject(eventsView: EventsView) fun inject(eventItemView:

    EventItemView) } interface CommonInjector { fun getCommonSubcomponent(): CommonSubcomponent } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 113
  86. @ActivityScope @Component(dependencies = arrayOf(MobileComponent::class)) interface ScoreboardActivityComponent : CommonInjector @ActivityScope @Component(dependencies

    = arrayOf(MobileComponent::class)) interface SearchActivityComponent : CommonInjector Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 114
  87. class ScoreboardActivity : AppCompatActivity, CommonInjector { override fun onCreate(savedInstanceState: Bundle)

    { component = ScoreboardActivityComponent.create() } override fun getCommonSubcomponent(): CommonSubcomponent { return component.getCommonSubcomponent() } } // repeat for SearchActivity (Base class?) Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 115
  88. // class EventItemView : RelativeLayout @Inject lateinit var presenter: EventItemPresenter

    override fun onViewAttachedToWindow(){ (context as CommonInjector).getCommonSubcomponent().inject(this) } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 116
  89. // class EventItemView : RelativeLayout @Inject lateinit var presenter: EventItemPresenter

    @Inject lateinit var eventsPresenter: EventsPresenter override fun onViewAttachedToWindow(){ (context as CommonInjector).getCommonSubcomponent().inject(this) } Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 117
  90. Review 4 Dependency Injection 4 Service Locator 4 JSR-330 annotations

    4 Dagger 2 annotations 4 Using Dagger 2 in Android Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 119
  91. Thanks! Richard Cirerol @codeprogression We're hiring! Android Engineer, Client Team

    (http://grnh.se/imfhs71) Android Engineer, Core Team (http://grnh.se/617dof1) Unconventional Android - Dependency Injection. ©2017 Richard Cirerol 120