Skip to content

Commit 894a37a

Browse files
TimoPtrfrenckCopilot
authored
Clarify string usage and sealed modifier (#2768)
Co-authored-by: Franck Nijhof <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 445a664 commit 894a37a

File tree

1 file changed

+94
-1
lines changed

1 file changed

+94
-1
lines changed

docs/android/best_practices.md

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,95 @@ Tie your coroutines to an Android lifecycle (e.g., `viewModelScope` or `lifecycl
9393

9494
For more details on race conditions, see [Race Condition](https://en.wikipedia.org/wiki/Race_condition#In_software).
9595

96+
## Use strong types instead of strings for logic
97+
98+
Use strings for storing and displaying text, not for controlling logic or behavior in your code. Relying on strings for logic-such as passing a string to determine a destination or behavior-can introduce errors like typos and make it harder to track or refactor your code. Instead, use strong types, such as a `sealed` class or, if necessary, an `enum`, to represent these concepts. Reserve strings for raw values from third-party sources or for UI display. If you must use strings, define them as `const val` (following our [codestyle](/docs/android/codestyle#avoid-magic-numbers-and-strings)) or wrap them in a strong type, such as an [inline value class](https://kotlinlang.org/docs/inline-classes.html).
99+
100+
:::note[Example]
101+
102+
#### ❌ Avoid this pattern
103+
104+
```kotlin
105+
fun newInstance(destination: String): Intent {
106+
// Logic based on string value
107+
return Intent().apply {
108+
putExtra("destination", destination)
109+
}
110+
}
111+
```
112+
113+
#### ✅ Prefer this approach
114+
115+
```kotlin
116+
private const val DESTINATION_KEY = "destination"
117+
118+
@Parcelize
119+
sealed interface Destination : Parcelable {
120+
data object General : Destination
121+
data object Notifications : Destination
122+
data object Privacy : Destination
123+
}
124+
125+
fun newInstance(destination: Destination): Intent {
126+
return Intent().apply {
127+
putExtra(DESTINATION_KEY, destination)
128+
}
129+
}
130+
131+
fun onIntent(intent: Intent) {
132+
val destination = IntentCompat.getParcelableExtra(intent, DESTINATION_KEY, Destination::class.java)
133+
when (destination) {
134+
Destination.General -> // Handle General
135+
Destination.Notifications -> // Handle Notifications
136+
Destination.Privacy -> // Handle Privacy
137+
null -> // Handle missing destination
138+
}
139+
}
140+
```
141+
142+
:::
143+
144+
Using strong types for destinations helps prevent errors, improves code navigation, and makes refactoring more reliable. When you use `sealed` classes with `when`, the compiler can catch missing cases, and your IDE can quickly locate all usages of a specific destination, making updates and maintenance easier.
145+
146+
### Why sealed classes are better than enums
147+
148+
Sealed classes provide more flexibility and safety than enums. With sealed classes, you can define subclasses with their own properties, allowing you to pass additional data as needed for each type. This makes your APIs more expressive and adaptable.
149+
150+
For example, if the `Notifications` destination needs a `title` parameter, define it like this:
151+
152+
```kotlin
153+
154+
private const val DESTINATION_KEY = "destination"
155+
156+
@Parcelize
157+
sealed interface Destination : Parcelable {
158+
data object General : Destination
159+
data class Notifications(val title: String) : Destination
160+
data object Privacy : Destination
161+
}
162+
163+
fun onIntent(intent: Intent) {
164+
val destination = IntentCompat.getParcelableExtra(intent, DESTINATION_KEY, Destination::class.java)
165+
when (destination) {
166+
Destination.General -> // Handle General
167+
is Destination.Notifications -> {
168+
val title = destination.title
169+
// Handle Notifications with title
170+
}
171+
Destination.Privacy -> // Handle Privacy
172+
null -> // Handle missing destination
173+
}
174+
}
175+
```
176+
177+
:::note
178+
When you use `when` with a sealed class, avoid adding an `else` branch. This ensures that if you add a new case, the compiler will require you to handle it, making your code safer and easier to maintain.
179+
:::
180+
181+
By using sealed classes, you can safely add new destination types with their own required fields, and the compiler will enforce handling all cases. This approach makes your code more robust, maintainable, and less error-prone than using enums or strings for logic control.
182+
183+
Read more about sealed modifier on the [Kotlin documentation](https://kotlinlang.org/docs/sealed-classes.html).
184+
96185
## Code organization
97186

98187
### Keep your classes small
@@ -173,7 +262,11 @@ The further you progress in development, the more difficult it becomes to debug
173262

174263
### Leverage Kotlin compiler
175264

176-
The Kotlin compiler can help you catch issues early. For example, using the `when` operator with sealed classes/interfaces ensures that all cases are handled.
265+
The Kotlin compiler can help you catch issues early. For example, using the `when` operator with `sealed` classes/interfaces ensures that all cases are handled.
266+
267+
:::note
268+
Favor [composition over inheritance](https://en.wikipedia.org/wiki/Composition_over_inheritance) when designing your classes. Composition leads to more flexible, maintainable, and testable code by allowing you to build complex behavior from simpler, reusable components, rather than relying on rigid class hierarchies.
269+
:::
177270

178271
**Example:**
179272

0 commit comments

Comments
 (0)