I. Code source▲
Le code source de cet article est disponible sur Github : https://github.com/InsertKoinIO/getting-started-koin-android
Koin est maintenant en version 1.0.0, vous pouvez utiliser ce numéro pour les variables $koin_version Gradle utilisées ci-dessous. Vous pouvez également vérifier la dernière version disponible du projet sur le site officiel ou Github.
II. Premiers pas▲
Dans un premier temps, nous allons déclarer quelques composants simples.
II-A. Gradle - koin-android▲
Pour intégrer Koin à votre projet, il suffit d'ajouter la dépendance Gradle ci-dessous :
2.
3.
4.
5.
6.
7.
8.
// Add Jcenter to your repositories if needed
repositories {
jcenter()
}
dependencies {
// Koin for Android
implementation 'org.koin:koin-android:$koin_version'
}
II-B. Premiers composants▲
Nous allons partir sur un cas très simple, inspiré d'une architecture MVP : récupérer une donnée (notre message "Hello Koin") via un Repository et l'intégrer à une vue Android via un Presenter.
Nous allons mettre en œuvre deux composants :
- HelloRepository qui va nous fournir un message via la fonction giveHello(). Nous utiliserons son implémentation HelloRepositoryImpl ;
- MySimplePresenter, un composant qui va nous aider à récupérer notre message et qui sera utilisé par une vue Android.
2.
3.
4.
5.
6.
7.
interface HelloRepository {
fun giveHello(): String
}
class HelloRepositoryImpl() : HelloRepository {
override fun giveHello() = "Hello Koin"
}
2.
3.
4.
class MySimplePresenter(val repo: HelloRepository) {
fun sayHello() = "${repo.giveHello()} from $this"
}
À noter que nous utilisons le constructeur de MySimplePresenter afin d'utiliser le composant HelloRepository
II-C. Déclaration de composants avec Koin▲
Nous allons utiliser ici le DSL Kotlin de Koin afin de déclarer nos composants décrits ci-dessus.
Dans Koin nous déclarons nos composants via des définitions ou dans des modules. Une définition peut être de type single ou factory :
- single permet de déclarer un singleton, une instance unique dans le conteneur Koin ;
- factory permet de créer une nouvelle instance à chaque appel de la définition.
Chaque définition est déclarée avec une expression qui indique comment créer l'instance en question. Pour déclarer un singleton de HelloRepositoryImpl, je vais utiliser la fonction single :
single { HelloRepositoryImpl() }
Koin aura donc une définition correspondant au type HelloRepositoryImpl en singleton. Par contre, si je veux résoudre cette définition par le type HelloRepository, il va falloir que je le précise via le DSL (Koin ne fait pas d'introspection pour deviner l’héritage d'un type) :
single<HelloRepository> { HelloRepositoryImpl() }
Pour notre « presenter » MySimplePresenter, je vais devoir déclarer une factory afin d'avoir une nouvelle instance à chaque fois que ma vue sera créée. Nous utilisons ici la fonction factory
factory { MySimplePresenter() }
La classe possède aussi un constructeur, dans lequel je vais devoir récupérer mon instance de HelloRepository. À chaque fois que j'ai besoin d'injecter une instance dans un constructeur, je vais utiliser la fonction get() :
factory { MySimplePresenter(get()) }
Pour finir, nous pourrons déclarer nos définitions dans un module avec la fonction module :
2.
3.
4.
5.
6.
7.
8.
val appModule = module {
// single instance of HelloRepository
single<HelloRepository> { HelloRepositoryImpl() }
// Simple Presenter Factory
factory { MySimplePresenter(get()) }
}
II-D. Démarrons avec Koin !▲
Voilà, nos déclarations sont prêtes. Nous allons pouvoir les exécuter dans le conteneur Koin. Pour lancer Koin avec notre module, il suffit d'appeler la fonction startKoin() de la manière suivante (ici depuis une classe Application Android) :
2.
3.
4.
5.
6.
7.
class MyApplication : Application(){
override fun onCreate() {
super.onCreate()
// Start Koin
startKoin(this, listOf(appModule))
}
}
II-E. Intégration avec Android▲
Koin met à disposition une API simple pour permettre d'aller chercher les instances déclarées dans Koin.
Pour récupérer une instance de Koin dans un composant Android :
- by inject(), un « lazy delegate » pour récupérer l'instance uniquement quand elle est utilisée ;
- get() récupère directement l'instance.
Tous les composants Android (Activity, Fragment, Service…) sont munis des extensions Koin pour pouvoir utiliser l'API Koin ci-dessus.
Écrivons l'Activity MySimpleActivity afin de récupérer une instance de MySimplePresenter :
2.
3.
4.
5.
6.
7.
8.
9.
10.
class MySimpleActivity : AppCompatActivity() {
// Lazy injected MySimplePresenter
val firstPresenter: MySimplePresenter by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//...
}
}
Nous déclarons notre Presenter via la propriété firstPresenter :
val firstPresenter: MySimplePresenter by inject()
C'est tout ! :)
III. Tester facilement avec Koin▲
Koin permet également d'écrire des tests qui utilisent les instances du conteneur Koin.
III-A. Gradle - koin-test▲
Il suffit d'ajouter la dépendance Gradle ci-dessous :
2.
3.
4.
5.
6.
7.
8.
// Add Jcenter to your repositories if needed
repositories {
jcenter()
}
dependencies {
// Koin for Android
testImplementation 'org.koin:koin-test:$koin_version'
}
III-B. Tests unitaires avec Junit▲
Le projet koin-test apporte quelques fonctionnalités intéressantes pour les tests unitaires, basés sur JUnit. Dans un premier temps, il faut utiliser l'interface de marquage KoinTest afin de débloquer les fonctions suivantes :
- get() & by inject() pour récupérer les instances depuis Koin ;
- checkModules() pour vérifier les modules Koin (vérifie que chaque instance peut être résolue) ;
- declareMock() pour déclarer un mock Mockito à la volée.
Nous pouvons écrire les tests suivants :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
class MySimplePresenterTest : KoinTest {
val presenter: MySimplePresenter by inject()
@Before
fun before() {
startKoin(listOf(appModule))
}
@After
fun after() {
stopKoin()
}
@Test
fun `say hello`() {
// reuse the lazy injected presenter
assertTrue(presenter.sayHello().contains("Hello Koin"))
}
@Test
fun `say hello with mock`() {
declareMock<HelloRepository>()
// retrieve the HelloRepository mock
val mock = get<HelloRepository>()
// retrieve actual presenter (injected with mock)
val presenter = get<MySimplePresenter>()
presenter.sayHello()
verify(mock, times(1)).giveHello()
}
}
III-C. Vérification des modules▲
La résolution des dépendances dans Koin est dynamique. Par conséquent, le graphe des dépendances est découvert à la volée. Pour résoudre cette problématique et éviter de découvrir des problèmes de dépendances au lancement de l'application, il est possible de vérifier l'intégralité des dépendances avec la fonction checkModules() :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
class CheckModulesTest : KoinTest {
@Test
fun checkAllModules() {
checkModules(listOf(appModule))
}
@After
fun close() {
stopKoin()
}
}
IV. Koin, prêt pour les ViewModel▲
Dernier point pour clore cette longue introduction à l'injection de dépendances Android avec Koin : la possibilité d'utiliser les ViewModel (Architecture Components) avec Koin.
IV-A. Gradle - koin-android-viewmodel▲
Ci-dessous la dépendance Gradle nécessaire :
2.
3.
4.
5.
6.
7.
8.
9.
// Add Jcenter to your repositories if needed
repositories {
jcenter()
}
dependencies {
// Koin for Android - Scope feature
// include koin-android-scope & koin-android
implementation 'org.koin:koin-android-viewmodel:$koin_version'
}
IV-B. Notre classe ViewModel - MyViewModel▲
Écrivons une classe ViewModel qui va utiliser notre composant HelloRepository. Il suffit d'utiliser l'injection par constructeur pour récupérer notre dépendance depuis Koin :
2.
3.
4.
class MyViewModel(val repo : HelloRepository) : ViewModel() {
fun sayHello() = "${repo.giveHello()} from $this"
}
IV-C. Déclaration dans Koin▲
Le projet koin-android-viewmodel met à disposition un nouveau mot clé dans le DSL : viewModel.
Celui-ci permet de déclarer une instance de ViewModel, qui sera câblée avec le lifecycle du composant qui l'appelle.
2.
3.
4.
5.
6.
7.
8.
val appModule = module {
// single instance of HelloRepository
single<HelloRepository> { HelloRepositoryImpl() }
// MyViewModel ViewModel
viewModel { MyViewModel(get()) }
}
IV-D. Intégration avec Android▲
En activant le projet koin-android-viewmodel, nous débloquons les extensions suivantes :
- by viewModel() ou getViewModel() pour injecter une instance de type ViewModel ;
- by sharedViewModel() ou getSharedViewModel() pour utiliser/partager l'instance de ViewModel de l'activité parente.
Ci-dessous, la récupération de notre MyViewModel en une ligne :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
class MyViewModelActivity : AppCompatActivity() {
// Lazy Inject ViewModel
val myViewModel: MyViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_simple)
//...
}
}
V. Conclusion▲
Si le projet Koin vous plaît, n'hésitez pas à aller voir la documentation sur https://insert-koin.io.
Vous pouvez suivre les dernières infos du projet sur Twitter @insert-koin_io ou sur Medium.
VI. Remerciements▲
Cet article a été publié avec l'aimable autorisation de Arnaud Giuliani.
Nous tenons à remercier escartefigue et f-leb pour sa relecture orthographique attentive de cet article et Mickael Baron pour la mise au gabarit.



