Android Developer Route 2023 - Part 4

Android roadmap 2023
Android Developer Route 2023 - Part 1

Android Developer Route 2023 - Part 2

Android Developer Route 2023 - Part 3

Android Developer Route 2023 - Part 4

Android Developer Route 2023 - Part 4

In a previous blog post, we discussed the fundamentals of modern Android development, including Fragments, App Navigation, Architecture Components, and the Android Jetpack library.
In part 4, we will learn the following parts of Android:

  1. Design Patterns Design Patterns
  2. Architecture Architecture
  3. Asynchronous
  4. network network
  5. Image Loading image loading
  6. Local Storage local storage

Design Patterns

Design Patterns
A software design pattern is a reusable solution to a recurring, common software problem in software engineering. Design patterns can be categorized according to the types of problems they solve, such as creational patterns, behavioral patterns, and concurrency patterns.

In Android development, some design patterns are often used to solve common problems on the Android platform and manage the life cycle of resources, such as Dependency Injection and Observer patterns.

You've probably used creational patterns such as Builder pattern, Factory method pattern, and Singleton pattern at least once to create instances in Android development.

In this section, you'll explore three major patterns that influence overall architectural design in modern Android development.

dependency injection

Dependency injection is one of the most popular patterns in modern Android development, which moves the responsibility of creating objects outside of classes.

By shifting the obligation to create objects, classes do not need to depend on each other. Thus, you can design loosely coupled dependencies between classes.

If you use dependency injection correctly, you can benefit from the following advantages:

  • Reduce boilerplate code.
  • Loose coupling between classes so you can easily write unit tests.
  • Take advantage of the reusability of classes.
  • Improve code maintainability.

You can implement dependency injection manually following the Manual Dependency Injection guide, but the following efficient solutions are highly recommended: Hilt, Dagger, and Koin (strict service locator pattern).

  • Hilt: Hilt is a compile-time dependency injection tool for Android that runs on top of Dagger. Compared to the Dagger-Android library, Hilt generates standard Android Dagger components and reduces the amount of boilerplate code required. Additionally, Google offers compatibility with ViewModel, Jetpack Compose, Navigation, and WorkManager, which at the time of writing is a highly recommended dependency injection tool for modern Android development. For more information, check out Dependency Injection in the Hilt documentation.
  • Dagger: Dagger is also a compile-time dependency injection tool, based on javax.inject annotations and (JSR 330). You can use Dagger to configure dependency injection for your Android project, but this will require a lot of additional setup costs, because Dagger is not an Android dependency library. Highly recommend using Hilt or Dagger-Android to drastically reduce setup costs.
  • Koin: Koin is also a popular dependency injection (service locator pattern strictly speaking) tool for Kotlin projects, it is easy to use and cheap to set up. For more information, check out insert-koin.io.

More content reference for dependency injection: https://developer.android.com/training/dependency-injection

Observer pattern

The Observer pattern is a behavioral design pattern that allows you to build subscription mechanisms to automatically notify observers of any state changes.

The Observer pattern is also one of the most commonly used patterns in Android development, used to build a loosely coupled architecture between components. It can also be used to overcome Android platform limitations, such as communication between independent Android components.

You can implement your own subscription functionality and use them easily with the following libraries: RxKotlin, Kotlin Flows and LiveData.

  • LiveData: LiveData is a lifecycle aware, thread safe and data holder observer pattern. LiveData observers are bound to Android Lifecycles, so you don't need to manually unsubscribe your observers, they won't subscribe to data emissions when the lifecycle is not active. Thus, it will prevent unpredictable and hard-to-identify memory leaks. It also provides useful operators through the LiveData-ktx library and supports data binding and Room compatibility. However, modern Android development prefers Kotlin's Flows over LiveData because of the widespread adoption of coroutines. If you're interested in migrating to Flow, check out Flow from LiveData to Kotlin.

  • Kotlin Flows: Flows are an asynchronous solution, which are cold flows, similar to sequences using Coroutines. They are asynchronous and non-blocking solutions supported at the language level in Kotlin. Flows also provides useful operators such as Transform operator, Flattening operators and flowOn operator. You can leverage StateFlow and SharedFlow in Android to implement a state holder observable flow and send values ​​to multiple consumers.

  • RxKotlin (RxJava): RxKotlin originated from ReactiveX and is a combination of observer mode, iterator mode and functional programming. RxKotlin provides many useful operators that allow you to write asynchronous and event-based programs using observable sequences. Also, you can use these operators to easily solve concurrency issues such as low-level threading, synchronization, and thread safety, and there are many useful solutions for Android, such as RxAndroid. However, RxKotlin contains many operators which might be too complex for beginners. Kotlin Flows or LiveData are easier to use, especially if you don't need to use a lot of complex operations in your project.

The Repository Pattern
The Repository Pattern has its origins in Domain-Driven Design, a software approach that reconciles domain and data by providing data abstraction.

The presentation layer uses simple abstractions with interfaces that approximate business logic, such as querying data from a local database and fetching remote data from the network. The actual implementation classes do the heavy lifting and perform domain-related work.

A repository conceptually encapsulates a collection of executable domain functions related to a particular domain and provides more object-oriented aspects to other layers.

Repository mode

In modern Android development, the data layer consists of repositories that are exposed to other layers as a public interface and follow the single source of truth principle.

So other layers can observe domain data as a flow, such as Kotlin's Flow or LiveData, and guarantee the source of truth. For more information on the Repository pattern and the Data Layer, check out App Architecture.

Now let's discuss App Architecture.

Architecture

Architecture
Architecture is a crucial part of modern Android development, determining the overall code complexity and cost of project management.

As the number of project features increases, so does the number of lines of code and code cohesion. Application architecture broadly affects project complexity, scalability and robustness, and makes testing easier. By defining the boundaries between each layer, you can clearly define their responsibilities and separate each responsibilities by modularizing them into specialized roles.

Historically, architectural trends in Android have changed over the past few years, depending on available solutions and best practices.

Seven or eight years ago, Android projects were built using MVC and MVP architectural patterns, but now most projects use MVVM and MVI architectural patterns because of the introduction of useful subscription solutions such as RxKotlin, Kotlin Flows, and LiveData.

In this section, you'll learn about the most popular architectural patterns of recent years: MVVM, MVI, and Clean Architecture.

MVVM

Since Google officially announced Architecture Components, MVVM (Model-View-ViewModel) is one of the most popular architecture designs in modern Android development, such as ViewModel, LiveData and Data Binding.

Historically, the Model-View-ViewModel pattern has been used by WPF developers for over a decade since Microsoft introduced it.

The MVVM pattern consists of View, ViewModel and Model, as shown in the figure below:
MVVM
Each component has different responsibilities in Android development, which are defined as follows:

  • View: Responsible for building the user interface and displaying it on the screen for users to see. View consists of Android components including UI elements like TextView, Button or Jetpack Compose UI. UI elements trigger user events passed to ViewModel and configure UI screens by observing data or UI state from ViewModel. Ideally, View only contains UI logic that represents the screen and user interaction, such as listeners, and does not contain business logic.

  • ViewModel: This is an independent component that does not depend on View, which saves business data or UI state from Model and propagates it to UI elements. Usually, there is a many (one-to-many) relationship between ViewModel and Model, and ViewModel notifies View of data changes as domain data or UI state. In modern Android development, Google recommends using the ViewModel library, which helps developers easily save business data and preserve state across configuration changes. But technically you could say it's different from Microsoft's ViewModel because it doesn't quite resemble what Microsoft intended by design. To make it close to Microsoft's version, you should leverage other solutions such as data binding and subscription mechanism solutions such as RxKotlin, Kotlin Flows and LiveData. Check out Microsoft's "Model-View-ViewModel Pattern" for more details.

  • Model: Encapsulates the application's domain/data model, typically including business logic, complex computational work, and validation logic. Model classes are often used with remote services and local databases, and these libraries encapsulate data access in libraries, such as collections of executable domain functions. Warehouses ensure a single truth for multiple data sources and immutability of overall application data.

If you want to explore open source projects built using the MVVM architecture and the above design patterns, check out Pokedex on GitHub.

[MVVM open source project Pokedex] https://github.com/skydoves/pokedex

gray hair

MVI (Model-View-Intent) is also a popular architecture in modern Android development because Jetpack Compose brings declarative programming into our lives.

The MVI pattern focuses on the single source of truth principle, providing immutable state to other layers, and unidirectionality and immutability of state representing the results of user actions and configuration UI screens.

gray hair

The MVI architecture is a state management mechanism designed based on other architectures such as MVP or MVVM, which means that the MVI architecture can run on the Presenter or ViewModel according to your design.

Unlike MVVM and MVP, each component of MVI is defined slightly differently:

  • Intent: Intent is the definition of interfaces and functions that handle user actions (UI events, such as button click events). These functions convert UI events into Model's interface and pass the result to Model for operation. As the name suggests, we can say that we intentionally execute Model functions.

  • Model: In MVI, the definition of Model is completely different from MVP and MVVM. In MVI, a Model is a functional mechanism that takes output from an Intent and manipulates it into UI state that can be rendered in a View. UI state is immutable and derived from business logic, which follows a single source of truth and unidirectional data flow.

  • View: A View in MVI has the same responsibilities as MVP and MVVM, representing screens and user interactions such as listeners, and does not contain business logic. One of the biggest differences from other pattern implementations is that MVI ensures unidirectional data flow, so the View renders UI elements based on the UI state from the Model.

If you're interested in learning more about MVI, Hannes Dorfmann's book Reactive Application Development Using Model-View-Intent will help you better grasp the MVI architecture.

If you want to explore an open source project built using the MVI architecture and the above design pattern, check out WhatsApp Clone Compose on GitHub.

Clean Architecture

Clean Architecture was introduced by Robert C. Martin (Uncle Bob) in one of his Clean books, "Clean Architecture: A Craftsman's Guide to Software Structure and Design", where he proposed some object-oriented programming (OOP) paradigms for design method to build a strong and clear application structure.

Clean Architecture has been used extensively in modern Android development since the introduction of dependency injection solutions like Dagger and multi-module project environments. Also, the theory can be used with other architectures such as MVP, MVVM, and MVI.

This architecture brings the following advantages: isolation of modules, increased reusability, improved scalability, and ease of writing unit test cases. However, if you are developing a small project that does not require complex business logic, this architecture may be overly complex, so you should investigate whether this architecture is advantageous for your project.

Before diving into the theory of clean architecture, we discuss the SOLID design principles that Uncle Bob introduced in his series of clean books. Robert proposes the following five software design principles that allow you to build projects that are understandable, flexible, and maintainable:

  • Single Responsibility: Each software component, such as a class or module, should have only one reason to change; this means that designing unrelated functionality in the same component, class or module makes the purpose of the code difficult to understand and unclear.

  • Open-Closed Principle: You should be able to extend the functionality of a component (openness) without breaking its point and without modifying the usage (closedness).

  • Liskov substitution: Extending classes must replace parent classes. This means that the parent class must have a minimal interface that serves the purpose of brevity, and the subclass must implement every abstraction of the parent class.

  • Interface Segregation: As you can guess by the name, it is an atomic-oriented principle that can be associated with Single Responsibility and Liskov Substitution Principle. It is better to create many smaller interfaces rather than one huge interface to prevent functional bloat and violate the Liskov substitution principle.

  • Dependency Inversion: Classes and modules must depend on abstractions, not concrete implementations, to achieve one-way and linearizable dependencies. Additionally, this ensures component purity, meaning each component is responsible for its dedicated role. Don't confuse it with the Dependency Injection design pattern.

Clean Architecture basically follows the above SOLID design principles. Uncle Bob describes the clean architecture as shown in the following figure:

Clean Architecture@Uncle Bob
The center is the purest range, without dependencies on other layers. Each layer must expose abstractions for use by external layers that have internal circular dependencies. As you may have noticed, this is a maximal combination of SOLID design principles.

Each layer has its own single responsibility and follows the dependency inversion principle between them. Now let's look at the responsibilities of each layer:

  • Entity: A set of business rules and objects that encapsulate an application. This layer also follows the highest-level rules and exposes abstractions for easy consumption by other layers. In Google's official architecture guidelines, you can think of the entity layer as the data layer.
  • Use Cases: Use cases contain the definition of application business rules, such as user actions that will trigger functionality in the entity layer. This layer depends only on the entity layer and exposes abstractions to external layers to perform application-specific business logic.
  • Presenter (Interface Adapter): This layer implements all exposed interfaces, which are definitions of application business rules from the use case layer, and communicates with the UI layer. In MVVM, the ViewModel belongs here.
  • UI (framework and drivers): The UI layer represents how the UI is rendered, including Activities, Fragments, and all UI elements, such as Android widgets used on Android screens.

If you want to learn more about this concept and use it in your Android projects, you can refer to the following resources:

《The Clean Architecture》https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

《Architecting Android…The clean way?》https://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/

《Clean Architecture Tutorial for Android: Getting Started》https://www.raywenderlich.com/3595916-clean-architecture-tutorial-for-android-getting-started

Asynchronous

Asynchronous and Concurrency
Asynchronous
In Android, the system creates a main thread (also known as the UI thread), which is responsible for handling all UI-related work of the application. The main thread is responsible for rendering UI elements, dispatching events to the appropriate UI, and all interactions between Android UI Kit components.

Therefore, if you want to perform I/O or computationally intensive work (such as network requests and querying databases), you should handle them in another thread (the so-called worker thread). This ensures that the main thread is dedicated to rendering the screen and handling user interaction.

Having said that, writing a highly multi-threaded program is difficult to maintain and debug, and there are other considerations such as avoiding race conditions and managing resources. Fortunately, there are solutions to perform computationally intensive business work without blocking the main thread, and they make it possible not to manually process each thread one by one.

Let us now see how to execute business logic using the following solution.

RxJava/RxKotlin

One of the advantages of using RxJava is that it can easily control multi-threading issues like executing business logic in background thread and getting results in UI thread.

RxJava provides a thread pool called Schedulers, and this thread pool contains the following different types of threads: io, computation, single or you can create a completely new thread.

You can manipulate thread behavior by specifying Schedulers using the SubscribeOn and ObserveOn operators, which define which thread should do the work and which thread should consume the results.

If you want to know more about RxJava and multi-threading, you can refer to "Multi-Threading Like a Boss in Android With RxJava 2" by Aritra Roy.

https://github.com/ReactiveX/RxJava

Coroutines

Coroutines are an excellent concurrency solution for executing code asynchronously at the language level.

https://kotlinlang.org/docs/coroutines-overview.html

Unlike threads, coroutines are pure user-level language abstractions, so they are not directly bound to operating system resources, and each coroutine object is allocated on the JVM heap. This means that coroutines are controlled by the user, consume fewer resources, and are less expensive to context switch than threads.

According to the Android documentation, coroutines are lightweight, so you can run multiple coroutines on a single thread, and since they work based on scope, they cause fewer memory leaks. Google also supports the integration and compatibility of many Jetpack libraries, such as ViewModelScope and LifecycleScope, if you choose coroutines as a concurrency solution, you will get many advantages.

https://developer.android.com/kotlin/coroutines

If you want to learn more about coroutines for Android, check out Kotlin Coroutines on Android.

https://developer.android.com/kotlin/coroutines

Network

network arch components

Network communication is an essential part of modern applications. However, building your own networking solution requires significant resources such as connection pooling, response caching, HTTP (Hypertext Transfer Protocol) specific features, interceptors, and support for asynchronous calls.

7-8 years ago, Android developers used HttpURLConnection or Apache's HttpClient to perform HTTP requests. However, these libraries require a lot of boilerplate code and do not support Android platform compatibility, such as connectivity features, security support, and DNS (Domain Name System) resolution.

In this section, we'll explore the most popular HTTP libraries for Android: OkHttp and Retrofit.

OkHttp

OkHttp is an HTTP client developed by Square, built for the JVM and Android, and uses the in-house modern I/O library Okio. It works efficiently by default, helping you quickly build HTTP clients. It has its own recovery system when there is a problem with the network (such as a connection problem), so you don't need to deal with it manually. This library provides modern TLS (Transport Layer Security) functionality, Android's security support, caches and interceptors.

One of the most powerful features in OkHttp are interceptors, a powerful mechanism that allows you to log, monitor, modify, rewrite and retry calls.

Interceptor@OkHttp
You can easily transform all network requests according to your needs, such as adding an access token in the header (such as Bearer authentication) or adding Gzip compression to the request body.

https://github.com/square/okhttp

Retrofit

Retrofit is a type-safe HTTP client for Android and the JVM, also developed by Square. Retrofit provides an abstraction layer on top of OkHttp that allows you to easily and concisely define request specifications without having to deal with the underlying implementation.

You can also build the required HTTP request by supporting Retrofit annotations, including URL, header actions, request method and body. Also, by attaching a pluggable Converter.Factory, you can easily serialize all JSON responses without writing boilerplate code.

You can also manipulate network responses by attaching a CallAdapter, which allows you to handle raw responses and model the response type as desired. For more information, check out Modeling Retrofit Responses With Sealed Classes and Coroutines.

If you want to know more about Retrofit, please refer to Retrofit's official page.

https://square.github.io/retrofit/

Image Loading

Image Loading
Image loading is also an important part of modern application development, for example when loading user profiles or other content from the web.

You could implement your own image loading system, but it would require a lot of functionality behind the scenes for downloading images, resizing, caching, rendering and memory management, and more.

In this section, we'll explore popular Android image libraries.

Glide

Glide is one of the most popular image libraries developed by bumptech and has been around for a long time. It is used by many global products and open source projects, including Google's official open source projects.

It provides useful features such as animated GIF support, placeholders, transforms, caching, and resource reuse.

For more information, check out Glide's official documentation.

https://bumptech.github.io/glide/

Coil

Coil was created by Colin White and has grown in popularity since 2019.

It's written entirely in Kotlin, and the exposed API is Kotlin-friendly. One notable point is that Coil is lighter than other alternatives because it uses other libraries already widely used in the Android project, such as OkHttp and Coroutines.

Coil also supports Jetpack Compose, which provides useful features such as transitions, animated GIF support, SVG support, and video frame support.

For more information, check out Coil's official guide.

https://coil-kt.github.io/coil/

Fresco

Fresco is also one of the popular image loading libraries, as has Glide for a long time. It was developed by Meta.

Unlike other libraries, Fresco focuses on using memory efficiently, especially for devices below Android version 4.x. However, recent projects require at least a minimum SDK version of 21 to 23, and the API is very complex. Unless you're building a memory-sensitive application, choose Glide or Coil.

For more information, check out Fresco's official guide.

https://frescolib.org/

Landscapist

Landscapist is a Jetpack Compose image loading library developed by Jaewoong (skydoves), which uses Glide, Coil and Fresco to fetch and display network or local images.

Since Jetpack Compose completely changed the UI rendering mechanism from the original XML-based way, Landscapeist was developed to support image loading using popular image loading libraries in a generic way.

Landscapeist supports tracking image states, composing custom implementations, and animations such as circular reveals and fades. A recent version also introduces a new concept called ImagePlugin that makes attaching and implementing image loading behaviors easier and faster.

For more information, visit Landscapeist's GitHub page.

https://github.com/skydoves/landscapist

Local Storage

Local Storage
Local storage is another commonly used solution in Android. If you need to persist user input or remote resources to the user's local device, you should save and restore them in local storage.

In this section, we'll explore Jetpack's main local storage solutions: Room and DataStore.

Room

Room is an Android Jetpack library provided by Google that provides an abstraction layer over SQLite that simplifies querying and accessing databases without writing complex SQL statements.

It is based on annotation processors (which also support Kotlin symbol processing), so implementations such as querying and inserting columns will be generated at compile time.

One of the biggest advantages of using this library is that developers don't need to learn SQL queries because the abstraction layer is very clean and easy to understand. It also provides useful features such as coroutines, RxJava compatibility, automatic migration strategies, and type converters.

To learn more about Room, see Using Room to save data training in a local database.

DataStore

DataStore, another Android Jetpack library provided by Google, is a data storage solution that lets you store key-value pairs in local storage. This library is another solution for SharedPreferences.

DataStore also supports high compatibility with other libraries, such as Coroutines and Flow, stores data in an asynchronous manner, and supports RxJava. It also supports the use of protocol buffers to store typed objects.

To learn more about DataStore, check Google's official documentation.

https://developer.android.com/topic/libraries/architecture/datastore

in conclusion

That's part four of the 2022 Android development roadmap. This part covers design patterns, architecture, async, networking, image loading, and local storage, which are all necessary components of modern Android development.

《 2022 Android Developer Roadmap》https://github.com/skydoves/android-developer-roadmap

Guess you like

Origin blog.csdn.net/u011897062/article/details/130685075