Modern Android Development in 2023
According to StatCounter, the future of Android development is promising, where the Android operating system had a market share of 72% in Feb 2023. To gain more user engagement and satisfaction, you would need to adopt the latest approaches for developing better Android apps. In this blog, we will focus on those design & architecture approaches for developing modern native Android apps in 2023.
Our focus will be on two key areas for Android app development:
When it comes to developing Android apps we can take many advantages by carefully choosing development libraries and the appropriate architecture. Below is the list that most of the developer community is adapting to build high-quality, scalable, and testable Android apps:
- Kotlin-first approach
- MVVM (Model-View-ViewModel) architecture
- Android Jetpack libraries
- Jetpack compose
- Dependency injection
- Kotlin coroutine & flows
The Kotlin language has a lot of benefits on top of Java, which makes it worth using instead of Java:
- Null safety: Kotlin has a type system that helps prevent null pointer exceptions. We can define Nullable and Non-nullable types, the compiler enforces null safety by requiring the programmer to handle the null case explicitly.
- Interoperability with Java: Kotlin can easily be embedded in existing Java-based Android apps which helps in slowly adopting Kotlin.
- Functional programming features: Kotlin has functional programming features like higher-order functions, lambda functions, and extension functions.
- Conciseness: Kotlin code is more concise with features like no semicolon, type inference, and data classes to reduce boilerplate code.
There are multiple architectures available to build Android apps such as MVC (Model-View-Controller), MVP (Model-View-Presenter), MVVM (Model-View-ViewModel), and MVI (Model-View-Intent), but Google recommends MVVM with clean architecture.
Clean architecture is based on the principle of separating concerns into distinct layers like presentation, use cases, domain, and data with clear boundaries and dependencies between them. The key principle of clean architecture is that the outer layers depend on the inner layers, but the inner layers are independent of the outer layers. For example, the presentation layer depends on the use cases, but the use cases are independent of the presentation layer.
This separation of concerns makes the code more modular and easier to maintain. There are multiple benefits of adopting it:
- Separation of concern: MVVM separates the UI logic from the business logic and data access logic. This makes the code easier to maintain, test, and modify.
- Testability: Due to having separate layers for different concerns, it's easy to unit test different layers in isolation.
- Scalability: It's very easy to add new or updated functionality due to different modules without affecting the existing ones.
- Reusability: ViewModels and use cases from MVVM can be reused across different features, which saves development time and reduces the code to be written.
Android Jetpack Libraries
The Google Android team has provided a great suite of Jetpack libraries that help in developing better apps. Each library solves specific problems that developers face in development.
- Navigation: The Navigation component helps developers implement navigation between different screens in an app. It provides a visual editor for creating navigation graphs, which can be used to define the flow of an app.
- LiveData: LiveData is a lifecycle-aware observable data holder class that is used to represent data that can be observed and updated by UI components. When bound with UI, It can update the UI automatically when the data changes.
- ViewModel: ViewModel is a class that is responsible for preparing and managing data for the UI. It survives configuration changes, such as screen rotations, and provides a clean separation between UI components and the data.
- Room: Room is a persistence library that provides an abstraction layer over SQLite. It allows developers to define their database schema using annotations and provides compile-time checks to catch errors early.
- WorkManager: WorkManager is a library that provides a simple API for scheduling background tasks. It can handle tasks that need to run at a specific time or tasks that need to run periodically.
- Paging: Paging is a library that helps developers load and display large data sets efficiently. It loads data in chunks, or pages, and provides APIs for loading more data when needed.
- Data Binding: Data Binding is a library that provides a declarative way to bind UI components to data sources in an app. It reduces boilerplate code and improves the readability of the code.
Jetpack Compose is a modern UI toolkit for building native Android apps, developed by Google. It allows developers to build user interfaces using a declarative programming model, where the UI components are described in code using a Kotlin-based DSL (Domain Specific Language).
Compose is designed to simplify UI development by reducing the amount of boilerplate code required to create and update views. With Compose, developers can easily create complex UIs with less code than traditional Android XML layouts with a more intuitive and flexible approach.
Below are some benefits to achieve by using compose:
- Declarative UI: with Jetpack Compose, you can define your UI components in a declarative way, which means that you simply describe what you want your UI to look like, and the framework takes care of updating the UI automatically when needed.
- Improved performance: compose uses a new rendering engine that is designed to be more efficient than the traditional Android UI framework. This means that your app will be faster and more responsive, even on older devices.
- Increased productivity: compose is easy to use and reduces the amount of boilerplate code required to create UIs. This means that you can build UIs faster, with fewer bugs and less code.
- Interoperability with existing code: compose is designed to work seamlessly with existing Android view-based code, so you can start using it in your project right away, without having to rewrite your entire app.
Dependency Injection (DI) helps in managing dependencies between different components of an application. In DI, dependencies are injected into a component at runtime, rather than being created or managed by the component itself. This approach allows for better modularity, testability, and scalability of an app, as it makes it easier to manage and replace dependencies as needed. Dagger and Koin are the two most popular libraries used in Android for managing dependencies.
Below are some DI benefits:
- Modularity: DI helps to create modular code by allowing developers to define dependencies in a separate module, rather than hard-coding dependencies into a component.
- Testability: by injecting dependencies in the constructor, DI makes it easier to test individual components in isolation, without the need to create complex testing environments.
- Performance: DI generates code at compile time, which helps to reduce the overhead associated with dependency injection at runtime, leading to improved performance.
Kotlin Coroutine & Flows
Kotlin Coroutines and Flows are two powerful features introduced in Kotlin to support asynchronous programming. While both are designed to handle asynchronous tasks, they have different use cases and features.
- Kotlin Coroutines: Kotlin Coroutines are a lightweight concurrency framework that allows developers to write asynchronous code synchronously. Coroutines make it easier to write code that can suspend and resume execution at specific points without blocking the thread.
- Kotlin Flows: Kotlin Flows are a reactive streams framework that allows developers to handle streams of data in a reactive and non-blocking manner. Flows are designed to handle asynchronous operations that produce a stream of data, such as sensor data or user input.
Stats show that users like apps that have fewer bugs. Testing helps in finding early bugs, improving code quality, and facilitating code refactoring. There are different measures that developers are taking in 2023 to test the apps:
- Unit testing
- UI/Integration testing
- Testing on multiple device capabilities
Unit testing is a testing technique that tests individual units or components of an application in isolation like a Class or a method. JUnit is a popular open-source testing framework that is widely used for unit testing in Android development. It provides a set of annotations and assertions that make it easy to write and run tests.
UI/Integration testing is a testing technique that tests individual or multiple components interaction with Espresso, it is a popular open-source testing framework that is used for UI testing in Android development. It provides a set of APIs that allow developers to simulate user interactions with the application, which makes it easier to write automated UI tests.
Testing on multiple devices
To ensure the app quality, there are several ways that developers can follow to test the Android application on multiple OS versions to verify the behavior of the application before moving to production.
- Emulators: developers can create multiple different emulators based on different OS versions and screen sizes instead of maintaining the pool of physical devices to test applications.
- Physical Devices: physical devices can be used to test apps across different OS versions and screen sizes.
- Automated Testing Services: Firebase Test Lab or BrowserStack allow developers to test their app on multiple devices and OS versions automatically. These portals can provide crash reports and screenshots so you can spot errors before your users see them.
- Beta Testers: Google Play Beta Testing allows developers to distribute their app to a group of testers who are using different devices and OS versions. This allows developers to get feedback on the app's compatibility and performance on different devices.
In this article, I briefly talked about best practices to follow in developing modern scalable Android apps in 2023. I hope it was useful. Happy coding.