--- title: "Android best practices" sidebar_label: "Best Practices" --- ## General principles In general, we should follow standard development principles such as: - **SOLID**: Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion. Learn more with [Kotlin SOLID Principles Examples](https://medium.com/huawei-developers/kotlin-solid-principles-tutorial-examples-192bf8c049dd) - **KISS**: Keep It Simple, Stupid. - **DRY**: Don't Repeat Yourself - **Community guidelines**: Follow the practices showcased in the [NowInAndroid](https://github.com/android/nowinandroid) repository. ## Documentation Documentation in the code should bring value and evolve with the codebase. Keep the following in mind: - **Stay up-to-date**: Documentation must be updated as the code changes. - **Balance comments**: Avoid over-commenting, but don’t forget to comment where necessary. - **Future-proof**: Ask yourself, *"Will I understand what I did in 6 months?"* :::info Documentation should help, not hinder. ::: ## Logging Logging is essential but should be used judiciously. As Jake Wharton says in his [Timber](https://github.com/JakeWharton/timber) library: > Every time you log in production, a puppy dies. - Avoid excessive logging in production. - Use structured and meaningful log messages. - Leverage tools like Timber to manage logging effectively. ## Time and duration When working with time, date, or duration, avoid using primitive types. Instead, use strong types to prevent unit mix-ups. ### ❌ Don't do this ```kotlin const val THRESHOLD = 600000 fun main() { val now = System.currentTimeMillis() if (now > THRESHOLD) { // Do something } } ``` ### ✅ Do this ```kotlin val THRESHOLD = Instant.ofEpochSecond(60) fun main() { val now = Instant.now() if (now > THRESHOLD) { // Do something } } ``` :::warning If you must use primitive types, ensure the variable name includes the unit (e.g., `THRESHOLD_MS` instead of `THRESHOLD`) to reduce ambiguity. ::: - Apply the same logic to dates, durations, and timestamps. - For APIs that use `long` for timestamps (e.g., milliseconds vs. seconds), convert the values to a strong type as soon as possible to minimize exposure to untyped units. ## Concurrency Concurrency is powerful but requires careful handling to avoid issues like memory leaks and race conditions. ### Coroutine scope Tie your coroutines to an Android lifecycle (e.g., `viewModelScope` or `lifecycleScope`) to prevent memory leaks. ### Concurrent access - Ensure that any references accessed outside of a coroutine are thread-safe. - If a reference is not safe, either make it safe or don't use it. - Debugging concurrency issues (e.g., race conditions) can be extremely challenging, so design carefully. For more details on race conditions, see [Race Condition](https://en.wikipedia.org/wiki/Race_condition#In_software). ## Code organization ### Keep your classes small - Large classes often have too many responsibilities, making them harder to review, test, and maintain. - Aim for small classes with proper separation of concerns and abstraction. ### Keep your functions small and meaningful - Functions should be small and focused on a single responsibility. - A function's name should clearly describe what it does. If it’s hard to name, the function likely does too much. - Well-named, small functions reduce the need for documentation and make the code self-explanatory. :::note Naming is hard, but smaller functions make it easier to choose meaningful names. ::: ## Keep your PRs small - **Why?** Smaller PRs are easier to review, reduce delays, and minimize frustration. - **How?** Break down large changes into smaller, logical chunks. For more details, see [submit](/docs/android/submit). ## Additional notes - **Testing**: Write [unit tests](/docs/android/testing/unit_testing) for critical functionality to ensure reliability. - **Code reviews**: Always review code for adherence to these best practices.