Best Practices
## Learning Objectives
- Learn Kotlin coding conventions
- Understand idiomatic Kotlin
- Work with Java interoperability
- Apply best practices in Android development
## Coding Conventions
### Naming
```kotlin
// Classes: PascalCase
class UserAccount
// Functions: camelCase
fun calculateTotal()
// Properties: camelCase
val userName = "Alice"
// Constants: SCREAMING_SNAKE_CASE
const val MAX_RETRY_COUNT = 3
// Packages: lowercase
package com.example.myapp
```
### Indentation and Formatting
```kotlin
// 4 spaces for indent
fun main() {
if (condition) {
doSomething()
}
}
// Chained calls
val result = items
.filter { it > 0 }
.map { it * 2 }
.firstOrNull()
```
### Documentation
```kotlin
/**
* Calculates the sum of two integers.
*
* @param a First integer
* @param b Second integer
* @return The sum of a and b
*/
fun sum(a: Int, b: Int): Int = a + b
```
## Idiomatic Kotlin
### Nullable Types
```kotlin
// Bad
if (name != null) {
println(name.length)
} else {
println(0)
}
// Good
println(name?.length ?: 0)
```
### String Templates
```kotlin
// Bad
println("User: " + user.name + ", Age: " + user.age)
// Good
println("User: ${user.name}, Age: ${user.age}")
```
### Default Values
```kotlin
// Bad
fun createUser(
name: String,
age: Int,
email: String,
phone: String
) {
// ...
}
createUser("Alice", 30, "alice@example.com", "")
// Good
fun createUser(
name: String,
age: Int = 18,
email: String = "unknown@example.com",
phone: String = ""
) {
// ...
}
createUser("Alice")
```
### Data Classes
```kotlin
// Bad
class User {
val name: String
val email: String
constructor(name: String, email: String) {
this.name = name
this.email = email
}
fun equals(other: User) = name == other.name && email == other.email
fun hashCode() = name.hashCode() * 31 + email.hashCode()
fun toString() = "User(name=$name, email=$email)"
}
// Good
data class User(val name: String, val email: String)
```
### Collection Operations
```kotlin
// Bad
val result = mutableListOf()
for (item in items) {
if (item.isNotEmpty()) {
result.add(item.uppercase())
}
}
// Good
val result = items
.filter { it.isNotEmpty() }
.map { it.uppercase() }
```
### apply and with
```kotlin
// Bad
val person = Person()
person.name = "Alice"
person.age = 30
person.email = "alice@example.com"
// Good
val person = Person().apply {
name = "Alice"
age = 30
email = "alice@example.com"
}
// with for transforming
val info = with(person) {
"$name is $age years old"
}
```
## Java Interoperability
### Calling Java from Kotlin
```kotlin
// Java method returning null
val javaObject = JavaClass()
javaObject.nullableMethod?.let { /* safe call */ }
// Java method with @Nullable annotation
val result: String? = javaObject.method()
```
### Calling Kotlin from Java
```kotlin
// Kotlin top-level function
// Java calls: FunctionsKt.repeat("Hello", 3)
// Kotlin object
object MySingleton {
fun doSomething() {}
}
// Java calls: MySingleton.doSomething()
// Kotlin companion object
class MyClass {
companion object {
fun create() = MyClass()
}
}
// Java calls: MyClass.create()
```
### Annotations for Java
```kotlin
// @JvmField exposes property as field
class User {
@JvmField
val id: Int = 0
}
// @JvmName specifies Java method name
@JvmName("getUsersByRole")
fun getUsers(role: String) { }
// @JvmStatic for companion object methods
class Utils {
companion object {
@JvmStatic
fun helper() {}
}
}
// @Throws for checked exceptions
@Throws(IOException::class)
fun readFile(path: String): String { }
```
## Android Development
### View Binding
```kotlin
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.button.setOnClickListener {
binding.textView.text = "Clicked!"
}
}
}
```
### Coroutines with ViewModel
```kotlin
class MyViewModel : ViewModel() {
private val _data = MutableLiveData()
val data: LiveData = _data
private val _scope = ViewModelScope(Dispatchers.Main)
fun loadData() {
_scope.launch {
val result = withContext(Dispatchers.IO) {
api.fetchData()
}
_data.value = result
}
}
}
```
### Extension Functions for Views
```kotlin
fun View.visible() {
visibility = View.VISIBLE
}
fun View.gone() {
visibility = View.GONE
}
fun View.onClick(action: () -> Unit) {
setOnClickListener { action() }
}
// Usage
binding.button.visible()
binding.progressBar.gone()
binding.button.onClick { doSomething() }
```
### Kotlin Android Extensions
```kotlin
// build.gradle
// apply plugin: 'kotlin-android-extensions'
// In Activity
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
textView.text = "Hello!"
}
}
}
```
## Performance Tips
### Avoid Creating Unnecessary Objects
```kotlin
// Bad - creates String each iteration
val result = items.joinToString("") { it.value }
// Good - reuse separator
val result = items.joinToString("") { it.value }
```
### Use inline for Small Functions
```kotlin
inline fun List.first(predicate: (T) -> Boolean): T? {
for (element in this) {
if (predicate(element)) return element
}
return null
}
```
### Sequence for Large Collections
```kotlin
// When processing large collections
val result = largeList
.asSequence()
.map { expensiveOperation(it) }
.filter { it > 0 }
.take(10)
.toList()
```
## Error Handling
### Use Result Type
```kotlin
fun divide(a: Int, b: Int): Result {
return if (b == 0) {
Result.failure(IllegalArgumentException("Cannot divide by zero"))
} else {
Result.success(a / b)
}
}
fun main() {
val result = divide(10, 2)
result.fold(
onSuccess = { println("Result: $it") },
onFailure = { println("Error: ${it.message}") }
)
}
```
### runCatching
```kotlin
fun main() {
val result = runCatching {
"hello".toInt()
}
println(result.isSuccess) // false
println(result.exceptionOrNull()) // NumberFormatException
}
```
## Testing
```kotlin
// Using JUnit 5 with Kotlin
class CalculatorTest {
private val calculator = Calculator()
@Test
fun `adds two numbers`() {
assertEquals(5, calculator.add(2, 3))
}
@Test
fun `divides two numbers`() {
assertThrows {
calculator.divide(1, 0)
}
}
}
```
## Summary
- Follow Kotlin naming conventions and formatting
- Use idiomatic Kotlin patterns (elvis, let, apply)
- Data classes reduce boilerplate
- Chain collection operations instead of loops
- Use @Jvm annotations for Java interop
- Android development benefits from extensions and coroutines
- Use sequences for large collection processing
- Result types and runCatching for error handling
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →