หน้านี้แสดงแนวทางปฏิบัติแนะนำและคำแนะนำเกี่ยวกับสถาปัตยกรรมหลายรายการ นำแนวทางเหล่านี้ไปใช้เพื่อปรับปรุงคุณภาพ ความเสถียร และความสามารถในการปรับขนาดของแอป นอกจากนี้ ยังช่วยให้การดูแลรักษาและทดสอบแอปง่ายขึ้นด้วย
แนวทางปฏิบัติแนะนำด้านล่างจัดกลุ่มตามหัวข้อ โดยแต่ละแนวทางจะมีลำดับความสำคัญที่แสดงถึงความสำคัญของคำแนะนำ ซึ่งลำดับความสำคัญมีดังนี้
- ขอแนะนำอย่างยิ่ง: ใช้แนวทางปฏิบัตินี้ เว้นแต่จะขัดแย้งกับแนวทางของคุณโดยสิ้นเชิง
- ขอแนะนำ: แนวทางปฏิบัตินี้มีแนวโน้มที่จะปรับปรุงแอปของคุณ
- ไม่บังคับ: แนวทางปฏิบัตินี้สามารถปรับปรุงแอปของคุณได้ในบางกรณี
สถาปัตยกรรมแบบแบ่งเลเยอร์
สถาปัตยกรรมแบบแบ่งเลเยอร์ที่เราแนะนำให้ใช้จะเน้นการแยกความกังวลออกจากกัน โดยขับเคลื่อน UI จากโมเดลข้อมูล เป็นไปตามหลักการแหล่งข้อมูลเดียวที่เชื่อถือได้ และเป็นไปตามหลักการไหลของข้อมูลแบบทิศทางเดียว แนวทางปฏิบัติแนะนำสำหรับสถาปัตยกรรมแบบแบ่งเลเยอร์มีดังนี้
| คำแนะนำ | คำอธิบาย |
|---|---|
| ใช้ชั้นข้อมูลที่กำหนดไว้อย่างชัดเจน
ขอแนะนำอย่างยิ่ง |
ชั้นข้อมูลจะแสดงข้อมูลแอปพลิเคชันให้ส่วนอื่นๆ ของแอป และมีตรรกะทางธุรกิจส่วนใหญ่ของแอป
|
| ใช้เลเยอร์ UI ที่กำหนดไว้อย่างชัดเจน
ขอแนะนำอย่างยิ่ง |
เลเยอร์ UI จะแสดงข้อมูลแอปพลิเคชันบนหน้าจอและเป็นจุดหลักที่ผู้ใช้โต้ตอบ Jetpack Compose เป็นชุดเครื่องมือที่ทันสมัยซึ่งเราแนะนำให้ใช้ในการสร้าง UI ของแอป
|
| แสดงข้อมูลแอปพลิเคชันจากชั้นข้อมูลโดยใช้ที่เก็บ
ขอแนะนำอย่างยิ่ง |
ตรวจสอบว่าคอมโพเนนต์ในเลเยอร์ UI เช่น Composable หรือ ViewModel ไม่โต้ตอบกับแหล่งข้อมูลโดยตรง ตัวอย่างแหล่งข้อมูล ได้แก่
|
| ใช้ Coroutine และ Flow
ขอแนะนำอย่างยิ่ง |
ใช้ Coroutine และ Flow เพื่อสื่อสารระหว่างเลเยอร์
ดูข้อมูลเพิ่มเติมเกี่ยวกับแนวทางปฏิบัติแนะนำสำหรับ Coroutine ได้ที่ แนวทางปฏิบัติแนะนำสำหรับ Coroutine ใน Android |
| ใช้เลเยอร์โดเมน
ขอแนะนำในแอปขนาดใหญ่ |
ใช้เลเยอร์โดเมนที่มี Use Case หากคุณต้องการนำตรรกะทางธุรกิจที่โต้ตอบกับชั้นข้อมูลกลับมาใช้ซ้ำใน ViewModel หลายรายการ หรือต้องการลดความซับซ้อนของตรรกะทางธุรกิจของ ViewModel ที่เฉพาะเจาะจง |
เลเยอร์ UI
บทบาทของเลเยอร์ UI คือการแสดงข้อมูลแอปพลิเคชันบนหน้าจอ และเป็นจุดหลักของการโต้ตอบของผู้ใช้ แนวทางปฏิบัติแนะนำสำหรับเลเยอร์ UI มีดังนี้
| คำแนะนำ | คำอธิบาย |
|---|---|
| ทำตามการไหลของข้อมูลแบบทิศทางเดียว (UDF)
ขอแนะนำอย่างยิ่ง |
ทำตามหลักการไหลของข้อมูลแบบทิศทางเดียว (UDF) ซึ่ง ViewModel จะแสดงสถานะ UI โดยใช้รูปแบบ Observer และรับการดำเนินการจาก UI ผ่านการเรียกใช้เมธอด |
| ใช้ AAC ViewModels หากประโยชน์ของ ViewModel เหล่านี้มีผลกับแอปของคุณ
ขอแนะนำอย่างยิ่ง |
ใช้ AAC ViewModel เพื่อ จัดการตรรกะทางธุรกิจ และดึงข้อมูลแอปพลิเคชันเพื่อแสดงสถานะ UI ใน UI
ดูข้อมูลเพิ่มเติมเกี่ยวกับแนวทางปฏิบัติแนะนำสำหรับ ViewModel ได้ที่ คำแนะนำเกี่ยวกับสถาปัตยกรรม ดูข้อมูลเพิ่มเติมเกี่ยวกับประโยชน์ของ ViewModel ได้ที่ ViewModel ในฐานะตัวเก็บสถานะตรรกะทางธุรกิจ |
| ใช้การรวบรวมสถานะ UI ที่รับรู้ถึงวงจร
ขอแนะนำอย่างยิ่ง |
รวบรวมสถานะ UI จาก UI โดยใช้ตัวสร้างโครูทีนที่เหมาะสมซึ่งรับรู้ถึงวงจร collectAsStateWithLifecycle
อ่านเพิ่มเติมเกี่ยวกับ |
| อย่าส่งเหตุการณ์จาก ViewModel ไปยัง UI
ขอแนะนำอย่างยิ่ง |
ประมวลผลเหตุการณ์ทันทีใน ViewModel และทำให้เกิดการอัปเดตสถานะด้วยผลลัพธ์ของการจัดการเหตุการณ์ ดูข้อมูลเพิ่มเติมเกี่ยวกับเหตุการณ์ UI ได้ที่ จัดการเหตุการณ์ ViewModel |
| ใช้แอปพลิเคชันแบบกิจกรรมเดียว
ขอแนะนำอย่างยิ่ง |
ใช้ Navigation 3 เพื่อไปยังส่วนต่างๆ ระหว่างหน้าจอและ Deep Link ไปยังแอป หากแอปมีหน้าจอมากกว่า 1 หน้าจอ |
| ใช้ Jetpack Compose
ขอแนะนำอย่างยิ่ง |
ใช้ Jetpack Compose เพื่อสร้างแอปใหม่สำหรับโทรศัพท์ แท็บเล็ต อุปกรณ์พับได้ และ Wear OS |
ข้อมูลโค้ดต่อไปนี้แสดงวิธีรวบรวมสถานะ UI ในลักษณะที่รับรู้ถึงวงจร
@Composable
fun MyScreen(
viewModel: MyViewModel = viewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
}
ViewModel
ViewModel มีหน้าที่รับผิดชอบในการแสดงสถานะ UI และเข้าถึงชั้นข้อมูล แนวทางปฏิบัติแนะนำสำหรับ ViewModel มีดังนี้
| คำแนะนำ | คำอธิบาย |
|---|---|
| เก็บ ViewModel ไว้โดยไม่ขึ้นอยู่กับวงจรของ Android
ขอแนะนำอย่างยิ่ง |
ใน ViewModel อย่าเก็บข้อมูลอ้างอิงถึงประเภทที่เกี่ยวข้องกับวงจร อย่าส่ง Activity, Context หรือ Resources เป็นทรัพยากร Dependency
หากมีบางอย่างที่ต้องใช้ Context ใน ViewModel ให้ประเมินอย่างรอบคอบว่าสิ่งนั้นอยู่ในเลเยอร์ที่ถูกต้องหรือไม่ |
| ใช้ Coroutine และ Flow
ขอแนะนำอย่างยิ่ง |
ViewModel จะโต้ตอบกับเลเยอร์ข้อมูลหรือเลเยอร์โดเมนโดยใช้สิ่งต่อไปนี้
|
| ใช้ ViewModel ที่ระดับหน้าจอ
ขอแนะนำอย่างยิ่ง |
อย่าใช้ ViewModel ในส่วน UI ที่นำกลับมาใช้ใหม่ได้ คุณควรใช้ ViewModel ในส่วนต่อไปนี้
สำหรับ Composable ที่ซับซ้อนมากขึ้น หรือ Composable ที่มีลักษณะการทำงานแบบไดนามิกตามสถานะ ให้ใช้ |
| ใช้คลาสตัวเก็บสถานะธรรมดาในคอมโพเนนต์ UI ที่นำกลับมาใช้ใหม่ได้
ขอแนะนำอย่างยิ่ง |
ใช้ คลาสตัวเก็บสถานะธรรมดา เพื่อจัดการความซับซ้อนในคอมโพเนนต์ UI ที่นำกลับมาใช้ใหม่ได้ เมื่อทำเช่นนี้ คุณจะย้ายสถานะขึ้นและควบคุมสถานะจากภายนอกได้ |
อย่าใช้ AndroidViewModel
ขอแนะนำ |
ใช้คลาส ViewModel ไม่ใช่ AndroidViewModel อย่าใช้คลาส Application ใน ViewModel แต่ให้ย้ายทรัพยากร Dependency ไปยัง UI หรือชั้นข้อมูลแทน |
| แสดงสถานะ UI
ขอแนะนำ |
กำหนดให้ ViewModel แสดงข้อมูลใน UI ผ่านพร็อพเพอร์ตี้เดียวที่ชื่อว่า uiState หาก UI แสดงข้อมูลหลายส่วนที่ไม่เกี่ยวข้องกัน VM จะแสดงพร็อพเพอร์ตี้สถานะ UI หลายรายการได้
|
ข้อมูลโค้ดต่อไปนี้แสดงวิธีแสดงสถานะ UI จาก ViewModel
@HiltViewModel
class BookmarksViewModel @Inject constructor(
newsRepository: NewsRepository
) : ViewModel() {
val feedState: StateFlow<NewsFeedUiState> =
newsRepository
.getNewsResourcesStream()
.mapToFeedState(savedNewsResourcesState)
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = NewsFeedUiState.Loading
)
// ...
}
วงจร
ทำตามแนวทางปฏิบัติแนะนำสำหรับการทำงานกับวงจรของกิจกรรม
| คำแนะนำ | คำอธิบาย |
|---|---|
ใช้เอฟเฟกต์ที่รับรู้ถึงวงจรใน Composable แทนการลบล้างการเรียกกลับวงจรของ Activity
ขอแนะนำอย่างยิ่ง |
อย่าลบล้างเมธอดวงจรของ
|
ข้อมูลโค้ดต่อไปนี้แสดงวิธีดำเนินการตามสถานะวงจรที่กำหนด
@Composable
fun LocationChangedEffect(
locationManager: LocationManager,
onLocationChanged: (Location) -> Unit
) {
val currentOnLocationChanged by rememberUpdatedState(onLocationChanged)
LifecycleStartEffect(locationManager) {
val listener = LocationListener { newLocation ->
currentOnLocationChanged(newLocation)
}
try {
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
1000L,
1f,
listener,
)
} catch (e: SecurityException) {
// TODO: Handle missing permissions
}
onStopOrDispose {
locationManager.removeUpdates(listener)
}
}
}
จัดการทรัพยากร Dependency
ทำตามแนวทางปฏิบัติแนะนำเมื่อจัดการทรัพยากร Dependency ระหว่างคอมโพเนนต์
| คำแนะนำ | คำอธิบาย |
|---|---|
| ใช้การแทรกทรัพยากร Dependency
ขอแนะนำอย่างยิ่ง |
ใช้แนวทางปฏิบัติแนะนำสำหรับการแทรกทรัพยากร Dependency โดยหลักๆ คือการแทรกตัวสร้างเมื่อเป็นไปได้ |
| กำหนดขอบเขตเป็นคอมโพเนนต์เมื่อจำเป็น
ขอแนะนำอย่างยิ่ง |
กำหนดขอบเขตเป็นคอนเทนเนอร์ทรัพยากร Dependencyเมื่อประเภทมีข้อมูลที่เปลี่ยนแปลงได้ซึ่งต้องแชร์ หรือประเภทเริ่มต้นใช้งานได้ยากและใช้กันอย่างแพร่หลายในแอป |
| ใช้ Hilt
ขอแนะนำ |
ใช้ Hilt หรือ การแทรกทรัพยากร Dependency ด้วยตนเอง ในแอปง่ายๆ ใช้ Hilt หากโปรเจ็กต์มีความซับซ้อนมากพอ เช่น หากมีสิ่งต่อไปนี้:
|
การทดสอบ
แนวทางปฏิบัติแนะนำสำหรับการทดสอบมีดังนี้
| คำแนะนำ | คำอธิบาย |
|---|---|
| ทราบสิ่งที่จะทดสอบ
ขอแนะนำอย่างยิ่ง |
ทดสอบโปรเจ็กต์ เว้นแต่โปรเจ็กต์จะง่ายมาก เช่น แอป "Hello World" โดยให้รวมสิ่งต่อไปนี้เป็นอย่างน้อย
|
| ใช้ Fake แทน Mock
ขอแนะนำอย่างยิ่ง |
ดูข้อมูลเพิ่มเติมเกี่ยวกับการใช้ Fake ได้ที่ ใช้ Test Double ใน Android |
| ทดสอบ StateFlow
ขอแนะนำอย่างยิ่ง |
เมื่อทดสอบ StateFlow ให้ทำดังนี้
|
ดูข้อมูลเพิ่มเติมได้ที่ สิ่งที่จะทดสอบใน Android และ ทดสอบเลย์เอาต์ Compose
โมเดล
ปฏิบัติตามแนวทางปฏิบัติแนะนำต่อไปนี้เมื่อพัฒนาโมเดลในแอป
| คำแนะนำ | คำอธิบาย |
|---|---|
| สร้างโมเดลต่อเลเยอร์ในแอปที่ซับซ้อน
ขอแนะนำ |
ในแอปที่ซับซ้อน ให้สร้างโมเดลใหม่ในเลเยอร์หรือคอมโพเนนต์ต่างๆ เมื่อเหมาะสม ลองดูตัวอย่างต่อไปนี้
|
รูปแบบการตั้งชื่อ
เมื่อตั้งชื่อฐานของโค้ด คุณควรทราบแนวทางปฏิบัติแนะนำต่อไปนี้
| คำแนะนำ | คำอธิบาย |
|---|---|
| การตั้งชื่อเมธอด
ไม่บังคับ |
ใช้วลีที่เป็นคำกริยาเพื่อตั้งชื่อเมธอด เช่น makePayment() |
| การตั้งชื่อพร็อพเพอร์ตี้
ไม่บังคับ |
ใช้วลีที่เป็นคำนามเพื่อตั้งชื่อพร็อพเพอร์ตี้ เช่น inProgressTopicSelection |
| การตั้งชื่อสตรีมข้อมูล
ไม่บังคับ |
เมื่อคลาสแสดงสตรีม Flow หรือสตรีมอื่นๆ รูปแบบการตั้งชื่อจะเป็น get{model}Stream เช่น getAuthorStream(): Flow<Author>
หากฟังก์ชันแสดงรายการโมเดล ให้ใช้ชื่อโมเดลพหูพจน์: getAuthorsStream(): Flow<List<Author>> |
| การตั้งชื่อการติดตั้งใช้งานอินเทอร์เฟซ
ไม่บังคับ |
ใช้ชื่อที่มีความหมายสำหรับการติดตั้งใช้งานอินเทอร์เฟซ ใช้ Default เป็นคำนำหน้าหากไม่พบชื่อที่ดีกว่า เช่น สำหรับอินเทอร์เฟซ NewsRepository คุณอาจมี OfflineFirstNewsRepository หรือ InMemoryNewsRepository หากไม่พบชื่อที่ดี ให้ใช้ DefaultNewsRepository
ใส่คำนำหน้า Fake ในการติดตั้งใช้งาน Fake เช่น FakeAuthorsRepository |
แหล่งข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับสถาปัตยกรรม Android ได้ที่แหล่งข้อมูลเพิ่มเติมต่อไปนี้