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.