Skip to content

Commit 5fa8e1f

Browse files
Merge tag '3.5.4'
2 parents d4582c5 + f995b1c commit 5fa8e1f

74 files changed

Lines changed: 2505 additions & 180 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
---
2+
title: Android - Annotations
3+
---
4+
5+
> This tutorial lets you write an Android application and use Koin dependency injection to retrieve your components.
6+
> You need around __10/15 min__ to do the tutorial.
7+
8+
## Get the code
9+
10+
:::info
11+
[The source code is available at on Github](https://github.com/InsertKoinIO/koin-getting-started/tree/main/android)
12+
:::
13+
14+
## Gradle Setup
15+
16+
Let's configure the KSP Plugin like this:
17+
18+
```groovy
19+
apply plugin: 'com.google.devtools.ksp'
20+
21+
android {
22+
sourceSets {
23+
main.java.srcDirs += 'src/main/kotlin'
24+
test.java.srcDirs += 'src/test/kotlin'
25+
}
26+
// For KSP
27+
applicationVariants.configureEach { variant ->
28+
kotlin.sourceSets {
29+
getByName(name) {
30+
kotlin.srcDir("build/generated/ksp/${variant.name}/kotlin")
31+
}
32+
}
33+
}
34+
}
35+
```
36+
37+
Add the Koin Android dependency like below:
38+
39+
```groovy
40+
dependencies {
41+
// Koin
42+
implementation "io.insert-koin:koin-android:$koin_version"
43+
implementation "io.insert-koin:koin-annotations:$koin_ksp_version"
44+
ksp "io.insert-koin:koin-ksp-compiler:$koin_ksp_version"
45+
}
46+
```
47+
48+
49+
50+
## Application Overview
51+
52+
The idea of the application is to manage a list of users, and display it in our `MainActivity` class with a Presenter or a ViewModel:
53+
54+
> Users -> UserRepository -> (Presenter or ViewModel) -> MainActivity
55+
56+
## The "User" Data
57+
58+
We will manage a collection of Users. Here is the data class:
59+
60+
```kotlin
61+
data class User(val name : String)
62+
```
63+
64+
We create a "Repository" component to manage the list of users (add users or find one by name). Here below, the `UserRepository` interface and its implementation:
65+
66+
```kotlin
67+
interface UserRepository {
68+
fun findUser(name : String): User?
69+
fun addUsers(users : List<User>)
70+
}
71+
72+
class UserRepositoryImpl : UserRepository {
73+
74+
private val _users = arrayListOf<User>()
75+
76+
override fun findUser(name: String): User? {
77+
return _users.firstOrNull { it.name == name }
78+
}
79+
80+
override fun addUsers(users : List<User>) {
81+
_users.addAll(users)
82+
}
83+
}
84+
```
85+
86+
## The Koin module
87+
88+
Let's declare a `AppModule` module class like below.
89+
90+
```kotlin
91+
@Module
92+
@ComponentScan("org.koin.sample")
93+
class AppModule
94+
```
95+
96+
* We use the `@Module` to declare our class as Koin module
97+
* The `@ComponentScan("org.koin.sample")` allow to scann any Koin definition in `"org.koin.sample"`package
98+
99+
Let's simply add `@Single` on `UserRepositoryImpl` class to declare it as singleton:
100+
101+
```kotlin
102+
@Single
103+
class UserRepositoryImpl : UserRepository {
104+
// ...
105+
}
106+
```
107+
108+
## Displaying User with Presenter
109+
110+
Let's write a presenter component to display a user:
111+
112+
```kotlin
113+
class UserPresenter(private val repository: UserRepository) {
114+
115+
fun sayHello(name : String) : String{
116+
val foundUser = repository.findUser(name)
117+
return foundUser?.let { "Hello '$it' from $this" } ?: "User '$name' not found!"
118+
}
119+
}
120+
```
121+
122+
> UserRepository is referenced in UserPresenter`s constructor
123+
124+
We declare `UserPresenter` in our Koin module. We declare it as a `factory` definition with the `@Factory` annotation, to not keep any instance in memory (avoid any leak with Android lifecycle):
125+
126+
```kotlin
127+
@Factory
128+
class UserPresenter(private val repository: UserRepository) {
129+
// ...
130+
}
131+
```
132+
133+
## Injecting Dependencies in Android
134+
135+
The `UserPresenter` component will be created, resolving the `UserRepository` instance with it. To get it into our Activity, let's inject it with the `by inject()` delegate function:
136+
137+
```kotlin
138+
class MainActivity : AppCompatActivity() {
139+
140+
private val presenter: UserPresenter by inject()
141+
142+
override fun onCreate(savedInstanceState: Bundle?) {
143+
super.onCreate(savedInstanceState)
144+
145+
//...
146+
}
147+
}
148+
```
149+
150+
That's it, your app is ready.
151+
152+
:::info
153+
The `by inject()` function allows us to retrieve Koin instances, in Android components runtime (Activity, fragment, Service...)
154+
:::
155+
156+
## Start Koin
157+
158+
We need to start Koin with our Android application. Just call the `startKoin()` function in the application's main entry point, our `MainApplication` class:
159+
160+
```kotlin
161+
// generated
162+
import org.koin.ksp.generated.*
163+
164+
class MainApplication : Application(){
165+
override fun onCreate() {
166+
super.onCreate()
167+
168+
startKoin{
169+
androidLogger()
170+
androidContext(this@MainApplication)
171+
modules(AppModule().module)
172+
}
173+
}
174+
}
175+
```
176+
177+
The Koin module is generated from `AppModule` with the `.module` extension: Just use the `AppModule().module` expression to get the Koin module from the annotations.
178+
179+
:::info
180+
The `import org.koin.ksp.generated.*` import is required to allow to use generated Koin module content
181+
:::
182+
183+
## Displaying User with ViewModel
184+
185+
Let's write a ViewModel component to display a user:
186+
187+
```kotlin
188+
@KoinViewModel
189+
class UserViewModel(private val repository: UserRepository) : ViewModel() {
190+
191+
fun sayHello(name : String) : String{
192+
val foundUser = repository.findUser(name)
193+
return foundUser?.let { "Hello '$it' from $this" } ?: "User '$name' not found!"
194+
}
195+
}
196+
```
197+
198+
> UserRepository is referenced in UserViewModel`s constructor
199+
200+
The `UserViewModel` is tagged with `@KoinViewModel` annotation to declare the Koin ViewModel definition, to not keep any instance in memory (avoid any leak with Android lifecycle).
201+
202+
203+
## Injecting ViewModel in Android
204+
205+
The `UserViewModel` component will be created, resolving the `UserRepository` instance with it. To get it into our Activity, let's inject it with the `by viewModel()` delegate function:
206+
207+
```kotlin
208+
class MainActivity : AppCompatActivity() {
209+
210+
private val viewModel: UserViewModel by viewModel()
211+
212+
override fun onCreate(savedInstanceState: Bundle?) {
213+
super.onCreate(savedInstanceState)
214+
215+
//...
216+
}
217+
}
218+
```
219+
220+
## Compile Time Checks
221+
222+
Koin Annotations allows to check your Koin configuration at compile time. This is available by jusing the following Gradle option:
223+
224+
```groovy
225+
ksp {
226+
arg("KOIN_CONFIG_CHECK","true")
227+
}
228+
```
229+
230+
## Verifying your App!
231+
232+
We can ensure that our Koin configuration is good before launching our app, by verifying our Koin configuration with a simple JUnit Test.
233+
234+
### Gradle Setup
235+
236+
Add the Koin Android dependency like below:
237+
238+
```groovy
239+
// Add Maven Central to your repositories if needed
240+
repositories {
241+
mavenCentral()
242+
}
243+
244+
dependencies {
245+
246+
// Koin for Tests
247+
testImplementation "io.insert-koin:koin-test-junit4:$koin_version"
248+
}
249+
```
250+
251+
### Checking your modules
252+
253+
The `verify()` function allow to verify the given Koin modules:
254+
255+
```kotlin
256+
class CheckModulesTest : KoinTest {
257+
258+
@Test
259+
fun checkAllModules() {
260+
261+
AppModule().module.verify(
262+
extraTypes = listOf(
263+
SavedStateHandle::class
264+
))
265+
}
266+
}
267+
```
268+
269+
With just a JUnit test, you can ensure your definitions configuration are not missing anything!

0 commit comments

Comments
 (0)