Skip to content

Commit 82a3142

Browse files
authored
Add Dependency Injection first best practice (#2764)
1 parent 1e3bb31 commit 82a3142

File tree

1 file changed

+54
-5
lines changed

1 file changed

+54
-5
lines changed

docs/android/best_practices.md

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ Logging is essential but should be used judiciously. As Jake Wharton says in his
3838

3939
When working with time, date, or duration, avoid using primitive types. Instead, use strong types to prevent unit mix-ups.
4040

41-
### ❌ Don't do this
41+
:::note[Example]
42+
43+
#### ❌ Don't do this
4244

4345
```kotlin
4446
const val THRESHOLD = 600000
@@ -52,7 +54,7 @@ fun main() {
5254
}
5355
```
5456

55-
### ✅ Do this
57+
#### ✅ Do this
5658

5759
```kotlin
5860
val THRESHOLD = Instant.ofEpochSecond(60)
@@ -66,6 +68,8 @@ fun main() {
6668
}
6769
```
6870

71+
:::
72+
6973
:::warning
7074
If you must use primitive types, ensure the variable name includes the unit (e.g., `THRESHOLD_MS` instead of `THRESHOLD`) to reduce ambiguity.
7175
:::
@@ -113,10 +117,55 @@ Naming is hard, but smaller functions make it easier to choose meaningful names.
113117

114118
For more details, see [submit](/docs/android/submit).
115119

116-
## Additional notes
120+
## Dependency injection (DI)
121+
122+
We use Dependency injection (DI) to help write modular, testable, and maintainable code. By using DI, we can decouple the classes from their dependencies, making it easier to swap implementations, write unit tests, and manage complex object graphs. DI also improves code readability and helps enforce the single responsibility principle.
123+
124+
### Use explicit qualifier annotations over `@Named`
125+
126+
When you need to inject multiple implementations of the same type (or primitive types), you must use a qualifier to distinguish between them. While the `@Named` annotation is a common approach, it relies on string identifiers, which can be error-prone and harder to refactor. Using custom qualifier annotations instead of `@Named` offers several advantages:
127+
128+
- **Discoverability**: Custom qualifiers make it easier to find where a specific dependency is used in the codebase.
129+
- **Refactorability**: Renaming a custom annotation is straightforward and safe, while changing a string identifier requires searching for all string usages.
130+
- **Type safety**: Custom annotations are checked at compile time, reducing the risk of typos or mismatches that can occur with strings.
131+
- **Clarity**: Custom qualifiers make the code more self-explanatory and easier to understand.
132+
133+
:::note[Example]
134+
135+
#### ❌ Don't do this
136+
137+
```kotlin
138+
@Inject
139+
@Named("keyChainRepository")
140+
lateinit var keyChainRepository: KeyChainRepository
141+
```
142+
143+
#### ✅ Do this
144+
145+
```kotlin
146+
@Inject
147+
@NamedKeyChain
148+
lateinit var keyChainRepository: KeyChainRepository
149+
```
150+
151+
Define the annotation like this:
152+
153+
```kotlin
154+
package io.homeassistant.companion.android.common.data.keychain
155+
156+
import javax.inject.Qualifier
157+
158+
/**
159+
* Qualifier for the [KeyChainRepository] used to select the key chain.
160+
*/
161+
@Qualifier
162+
@Retention(AnnotationRetention.RUNTIME)
163+
annotation class NamedKeyChain
164+
```
165+
166+
:::
117167

118-
- **Testing**: Write [unit tests](/docs/android/testing/unit_testing) for critical functionality to ensure reliability.
119-
- **Code reviews**: Always review code for adherence to these best practices.
168+
For a real-world example of migrating from `@Named("keyChainRepository")` to `@NamedKeyChain`, see [this pull request](https://github.com/home-assistant/android/pull/5667).
120169

121170
## Fail fast
122171

0 commit comments

Comments
 (0)