Recommendations for Android architecture | Android Developers
Note:You should treat the recommendations in the document as recommendations and not strict requirements. Adapt them to your app as needed .
The best practices below are grouped by topic. Each has a priority that reflects how strongly the team recommends it. The list of priorities is as follows :
- Strongly recommended: You should implement this practice unless it clashes
fundamentally with your approach. - Recommended: This practice is likely to improve your app.
- Optional: This practice can improve your app in certain circumstances.
Note:
In order to understand these recommendations, you should be familiar with the Architecture guidance
Mục Chính
Layered architecture
Our recommended layered architecture favors separation of concerns. It drives UI from data models, complies with the single source of truth principle, and follows unidirectional data flow principles. Here are some best practices for layered architecture :
UI layer
The role of the UI layer is to display the application data on the màn hình hiển thị and serve as the primary point of user interaction. Here are some best practices for the UI layer :
The following snippet outlines how to collect the UI state in a lifecycle-aware manner :Views
class MyFragment : Fragment() { private val viewModel: MyViewModel by viewModel() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState.collect { // Process item } } } } }
Compose
@Composable fun MyScreen( viewModel: MyViewModel = viewModel() ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() }
ViewModel
ViewModels are responsible for providing the UI state and access to the data layer. Here are some best practices for ViewModels :
Recommendation Description ViewModels should be agnostic of the Android lifecycle. Strongly recommended
ViewModels shouldn’t hold a reference to any Lifecycle-related type. Don’t pass Activity, Fragment, Context
orResources
as a dependency.
If something needs aContext
in the ViewModel, you should strongly evaluate if that is in the right layer.Use coroutines and flows. Strongly recommended
The ViewModel interacts with the data or domain layers using :
- Kotlin flows for receiving application data,
suspend
functions to perform actions usingviewModelScope
.Use ViewModels at screen level. Strongly recommended
Do not use ViewModels in reusable pieces of UI. You should use ViewModels in :
- Screen-level composables,
- Activities/Fragments in Views,
- Destinations or graphs when using Jetpack Navigation.
Use plain state holder classes in reusable UI components. Strongly recommended
Use plain state holder classes for handling complexity in reusable UI components. By doing this, the state can be hoisted and controlled externally. Do not use AndroidViewModel
.Recommended
Use the ViewModel
class, notAndroidViewModel
. TheApplication
class shouldn’t be used in the ViewModel. Instead, move the dependency to the UI or the data layer.Expose a UI state. Recommended
ViewModels should expose data to the UI through a single property called uiState
. If the UI shows multiple, unrelated pieces of data, the VM can expose multiple UI state properties.
- You should make
uiState
aStateFlow
.- You should create the
uiState
using thestateIn
operator with theWhileSubscribed(5000)
policy (example) if the data comes as a stream of data from other layers of the hierarchy.- For simpler cases with no streams of data coming from the data layer, it’s acceptable to use a
MutableStateFlow
exposed as an immutableStateFlow
(example).- You can choose to have the
${Screen}UiState
as a data class that can contain data, errors and loading signals. This class could also be a sealed class if the different states are exclusive.The following snippet outlines how to expose UI state from a ViewModel :
@HiltViewModel class BookmarksViewModel @Inject constructor( newsRepository: NewsRepository ) : ViewModel() { val feedState: StateFlow
= newsRepository .getNewsResourcesStream() .mapToFeedState(savedNewsResourcesState) .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = NewsFeedUiState.Loading ) // ... } Lifecycle
The following are some best practices for working with the Android lifecycle :
Recommendation Description Do not override lifecycle methods in Activities or Fragments. Strongly recommended
Do not override lifecycle methods such as onResume
in Activities or Fragments. UseLifecycleObserver
instead. If the app needs to perform work when the lifecycle reaches a certainLifecycle.State
, use therepeatOnLifecycle
API.The following snippet outlines how to perform operations given a certain
Lifecycle state:Views
class MyFragment: Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onResume(owner: LifecycleOwner) { // ... } override fun onPause(owner: LifecycleOwner) { // ... } } } }
Compose
@Composable fun MyApp() { val lifecycleOwner = LocalLifecycleOwner.current DisposableEffect(lifecycleOwner, ...) { val lifecycleObserver = object : DefaultLifecycleObserver { override fun onStop(owner: LifecycleOwner) { // ... } } lifecycleOwner.lifecycle.addObserver(lifecycleObserver) onDispose { lifecycleOwner.lifecycle.removeObserver(lifecycleObserver) } } }
Handle dependencies
There are several best practices you should observe when managing dependencies between components :
Recommendation Description Use dependency injection. Strongly recommended
Use dependency injection best practices, mainly constructor injection when possible. Scope to a component when necessary. Strongly recommended
Scope to a dependency container when the type contains mutable data that needs to be shared or the type is expensive to initialize and is widely used in the app. Use Hilt. Recommended
Use Hilt or manual dependency injection in simple apps. Use Hilt if your project is complex enough. For example, if you have:
- Multiple screens with ViewModels—integration
- WorkManager usage—integration
- Advance usage of Navigation, such as ViewModels scoped to the nav graph—integration.
Testing
The following are some best practices for testing :
Recommendation Description Know what to test. Strongly recommended
Unless the project is roughly as simple as a hello world app, you should test it, at minimum with :
- Unit test ViewModels, including Flows.
- Unit test data layer entities. That is, repositories and data sources.
- UI navigation tests that are useful as regression tests in CI.
Prefer fakes to mocks. Strongly recommended
Read more in the Use test doubles in Android documentation. Test StateFlows. Strongly recommended
When testing StateFlow
:
- Assert on the
value
property whenever possible- You should create a
collectJob
if usingWhileSubscribed
For more information, check the What to test in Android DAC guide .
Models
You should observe these best practices when developing models in your apps :
Recommendation Description Create a model per layer in complex apps. Recommended
In complex apps, create new models in different layers or components when it makes sense. Consider the following examples :
- A remote data source can map the model that it receives through the network to a simpler class with just the data the app needs
- Repositories can map DAO models to simpler data classes with just the information the UI layer needs.
- ViewModel can include data layer models in
UiState
classes.Naming conventions
When naming your codebase, you should be aware of the following best practices :
Recommendation Description Naming methods. Optional
Methods should be a verb phrase. For example, makePayment()
.Naming properties. Optional
Properties should be a noun phrase. For example, inProgressTopicSelection
.Naming streams of data. Optional
When a class exposes a Flow stream, LiveData, or any other stream, the naming convention is get{model}Stream()
. For example,getAuthorStream(): Flow
If the function returns a list of models the model name should be in the plural:getAuthorsStream(): Flow
>
Naming interfaces implementations. Optional
Names for the implementations of interfaces should be meaningful. Have Default
as the prefix if a better name cannot be found. For example, for aNewsRepository
interface, you could have anOfflineFirstNewsRepository
, orInMemoryNewsRepository
. If you can find no good name, then useDefaultNewsRepository
.
Fake implementations should be prefixed withFake
, as inFakeAuthorsRepository
.
Source: https://thomaygiat.com
Category : Ứng Dụng
Máy Giặt Electrolux Lỗi E51 Làm Tăng Nguy Cơ Hỏng Nặng
Mục ChínhMáy Giặt Electrolux Lỗi E51 Làm Tăng Nguy Cơ Hỏng NặngNguyên Nhân Máy Giặt Electrolux Báo Lỗi E511. Động Cơ Hỏng2. Mạch Điều Khiển…
Hậu quả từ lỗi H-29 tủ lạnh Sharp Side by Side
Mục ChínhHậu quả từ lỗi H-29 tủ lạnh Sharp Side by SideMã Lỗi H-29 Tủ Lạnh Sharp Là Gì?Tầm Quan Trọng Của Việc Khắc Phục…
Hỏi đáp giấy dán tường chống ẩm mốc
Mục ChínhGiải Mã 25+ Hỏi Đáp Giấy Dán Tường Chống Ẩm MốcChống ẩm mốc cùng giấy dán tường1. Nguyên nhân gây ẩm mốc trong không…
Máy Giặt Electrolux Lỗi E-45 Kiểm Tra Ngay!
Mục ChínhMáy Giặt Electrolux Lỗi E-45 Kiểm Tra Ngay!Định Nghĩa Mã Lỗi E-45 Máy Giặt ElectroluxNguyên nhân lỗi E-45 máy giặt Electrolux1. Cảm biến cửa…
Hướng dẫn sửa Tủ lạnh Sharp lỗi H-28 chi tiết và an toàn
Mục ChínhHướng dẫn sửa Tủ lạnh Sharp lỗi H-28 chi tiết và an toànLỗi H-28 Trên Tủ Lạnh Sharp Là Gì?Dấu Hiệu Nhận Biết Lỗi…
Máy giặt Electrolux gặp lỗi E-44 điều bạn nên làm
Mục ChínhMáy giặt Electrolux gặp lỗi E-44 điều bạn nên làmĐịnh nghĩa mã lỗi E-44 máy giặt Electrolux5 Nguyên nhân gây ra mã lỗi E-44…