คำแนะนำสำหรับสถาปัตยกรรม Android

หน้านี้นำเสนอแนวทางปฏิบัติที่ดีที่สุดและคำแนะนำมากมายเกี่ยวกับสถาปัตยกรรม นําไปใช้เพื่อปรับปรุงคุณภาพ ความทนทาน และความสามารถในการปรับขนาดของแอป และ ช่วยให้ดูแลและทดสอบแอปได้ง่ายขึ้น

แนวทางปฏิบัติแนะนำด้านล่างจัดกลุ่มตามหัวข้อ แต่ละแบบมีลำดับความสำคัญที่ กับทีมนั้นแนะนำอย่างจริงจังเพียงใด รายการลำดับความสำคัญมีดังต่อไปนี้

  • แนะนําอย่างยิ่ง: คุณควรทําตามแนวทางปฏิบัตินี้ เว้นแต่จะเกิดการชนกัน กับแนวทางของคุณเป็นหลัก
  • แนะนำ: วิธีนี้น่าจะช่วยปรับปรุงแอปได้
  • ไม่บังคับ: วิธีนี้ช่วยปรับปรุงแอปได้ในบางสถานการณ์

สถาปัตยกรรมแบบเลเยอร์

สถาปัตยกรรมแบบเลเยอร์ที่แนะนำของเราเพื่อแยกข้อกังวลได้ ทั้งนี้ UI จากโมเดลข้อมูลเป็นไปตาม แหล่งที่มาที่ถูกต้องแห่งเดียว และทำตามหลักการการถ่ายโอนข้อมูลที่ไปในทิศทางเดียวกัน นี่เป็นตัวอย่างที่ดีที่สุด แนวทางปฏิบัติสำหรับสถาปัตยกรรมเลเยอร์

คำแนะนำ คำอธิบาย
ใช้ชั้นข้อมูลที่กำหนดไว้อย่างชัดเจน
แนะนำอย่างยิ่ง
ชั้นข้อมูลจะเปิดเผยข้อมูลแอปพลิเคชันส่วนที่เหลือของแอปและประกอบด้วยตรรกะทางธุรกิจส่วนใหญ่ของแอป
  • คุณควรสร้างที่เก็บแม้ว่าจะมีเพียงแหล่งข้อมูลเดียว
  • ในแอปขนาดเล็ก คุณจะเลือกวางประเภทชั้นข้อมูลในแพ็กเกจหรือโมดูล data ได้
ใช้เลเยอร์ UI ที่กำหนดไว้อย่างชัดเจน
แนะนำอย่างยิ่ง
เลเยอร์ UI จะแสดงข้อมูลแอปพลิเคชันบนหน้าจอและทำหน้าที่เป็นจุดหลักของการโต้ตอบของผู้ใช้
  • ในแอปขนาดเล็ก คุณจะเลือกวางประเภทชั้นข้อมูลในแพ็กเกจหรือโมดูล ui ได้
ดูแนวทางปฏิบัติแนะนำเพิ่มเติมของเลเยอร์ UI ได้ที่นี่
ชั้นข้อมูลควรเปิดเผยข้อมูลแอปพลิเคชันโดยใช้ที่เก็บ
แนะนำอย่างยิ่ง

คอมโพเนนต์ในเลเยอร์ UI เช่น Composables, กิจกรรม หรือ ViewModels ไม่ควรโต้ตอบกับแหล่งข้อมูลโดยตรง ตัวอย่างแหล่งข้อมูลมีดังนี้

  • Databases, DataStore, SharedPreferences, Firebase API
  • ผู้ให้บริการตำแหน่ง GPS
  • ผู้ให้บริการข้อมูลบลูทูธ
  • ผู้ให้บริการสถานะการเชื่อมต่อเครือข่าย
ใช้โครูทีนและโฟลว์
แนะนำอย่างยิ่ง
ใช้โครูทีนและโฟลว์เพื่อสื่อสารระหว่างเลเยอร์

ดูแนวทางปฏิบัติแนะนำเพิ่มเติมเกี่ยวกับโครูทีนได้ที่นี่

ให้ใช้เลเยอร์โดเมน
แนะนำในแอปขนาดใหญ่
ใช้เลเยอร์โดเมนกรณีการใช้งาน หากคุณต้องการนำตรรกะทางธุรกิจที่โต้ตอบกับชั้นข้อมูลใน ViewModel หลายรายการมาใช้ซ้ำ หรือหากต้องการลดความซับซ้อนของตรรกะทางธุรกิจของ ViewModel บางรายการ

เลเยอร์ UI

บทบาทของเลเยอร์ UI คือการแสดงข้อมูลแอปพลิเคชันบนหน้าจอ และทำหน้าที่เป็นจุดหลักของการโต้ตอบของผู้ใช้ ตัวอย่างแนวทางปฏิบัติแนะนำมีดังนี้ สำหรับเลเยอร์ UI ดังนี้

คำแนะนำ คำอธิบาย
โปรดทำตาม Unidirectional Data Flow (UDF)
แนะนำอย่างยิ่ง
ทำตามหลักการ Unidirectional Data Flow (UDF) โดย ViewModels แสดงสถานะ UI โดยใช้รูปแบบผู้สังเกตการณ์และรับการดำเนินการจาก UI ผ่านการเรียกใช้เมธอด
ใช้ AAC ViewModels หากแอปของคุณมีประโยชน์ต่อแอปของคุณ
แนะนำอย่างยิ่ง
ใช้ AAC ViewModels เพื่อจัดการตรรกะทางธุรกิจและดึงข้อมูลแอปพลิเคชันเพื่อแสดงสถานะ UI ใน UI (เขียนหรือมุมมอง Android)

ดูแนวทางปฏิบัติแนะนำสำหรับ ViewModel ที่นี่

ดูประโยชน์ของ ViewModels ที่นี่

ใช้การรวบรวมสถานะ UI ที่คำนึงถึงอายุการใช้งาน
แนะนำอย่างยิ่ง
รวบรวมสถานะ UI จาก UI โดยใช้เครื่องมือสร้างโครูทีนที่รับรู้ตลอดอายุการใช้งานที่เหมาะสม ซึ่งได้แก่ repeatOnLifecycle ในระบบมุมมองและ collectAsStateWithLifecycle ใน Jetpack Compose

อ่านเพิ่มเติมเกี่ยวกับ repeatOnLifecycle

อ่านเพิ่มเติมเกี่ยวกับ collectAsStateWithLifecycle

อย่าส่งเหตุการณ์จาก ViewModel ไปยัง UI
แนะนำอย่างยิ่ง
ประมวลผลเหตุการณ์ทันทีใน ViewModel และสร้างการอัปเดตสถานะโดยเป็นผลมาจากการจัดการเหตุการณ์ ดูข้อมูลเพิ่มเติมเกี่ยวกับเหตุการณ์ UI ที่นี่
ใช้แอปพลิเคชันกิจกรรมเดียว
แนะนำ
ใช้ส่วนย่อยการนำทางหรือการเขียนการนำทางเพื่อนำทางระหว่างหน้าจอและลิงก์ในรายละเอียดไปยังแอปของคุณ หากแอปมีมากกว่า 1 หน้าจอ
ใช้ Jetpack Compose
แนะนำ
ใช้ Jetpack Compose เพื่อสร้างแอปใหม่ๆ สำหรับโทรศัพท์ แท็บเล็ต อุปกรณ์แบบพับได้ รวมถึง Wear OS

ข้อมูลโค้ดต่อไปนี้สรุปวิธีรวบรวมสถานะ UI ตามวงจร ลักษณะ:

ยอดดู

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
                }
            }
        }
    }
}

เขียน

@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
}

ViewModel

ViewModels รับผิดชอบในการระบุสถานะ UI และการเข้าถึง ของ Google Analytics แนวทางปฏิบัติแนะนำสำหรับ ViewModels มีดังนี้

คำแนะนำ คำอธิบาย
ViewModels ไม่เกี่ยวข้องกับวงจรชีวิตของ Android
แนะนำอย่างยิ่ง
ViewModels ไม่ควรอ้างอิงถึงประเภทใดๆ ที่เกี่ยวข้องกับวงจร อย่าส่ง Activity, Fragment, Context หรือ Resources เป็นทรัพยากร Dependency หากมีบางอย่างต้องมี Context ใน ViewModel คุณควรประเมินอย่างเคร่งครัดว่าข้อมูลนั้นอยู่ในเลเยอร์ที่ถูกต้องหรือไม่
ใช้โครูทีนและโฟลว์
แนะนำอย่างยิ่ง

ViewModel โต้ตอบกับเลเยอร์ข้อมูลหรือโดเมนโดยใช้สิ่งต่อไปนี้

  • โฟลว์ Kotlin สำหรับรับข้อมูลแอปพลิเคชัน
  • ฟังก์ชัน suspend เพื่อดำเนินการต่างๆ โดยใช้ viewModelScope
ใช้ ViewModels ที่ระดับหน้าจอ
แนะนำอย่างยิ่ง

อย่าใช้ ViewModels ใน UI ที่ใช้ซ้ำได้ คุณควรใช้ ViewModels ใน

  • Composable ระดับหน้าจอ
  • กิจกรรม/ส่วนย่อยในมุมมอง
  • ปลายทางหรือกราฟเมื่อใช้การนำทางของ Jetpack
ใช้คลาสผู้ถือสถานะปกติในคอมโพเนนต์ UI ที่นำมาใช้ใหม่ได้
แนะนำอย่างยิ่ง
ใช้คลาสผู้ถือสถานะปกติเพื่อจัดการความซับซ้อนในคอมโพเนนต์ UI ที่นำมาใช้ใหม่ได้ การทำเช่นนี้จะทำให้สามารถเคลื่อนย้ายและควบคุมสถานะได้จากภายนอก
อย่าใช้ AndroidViewModel
แนะนำ
ใช้ชั้นเรียน ViewModel ไม่ใช่ AndroidViewModel ไม่ควรใช้คลาส Application ใน ViewModel แต่ให้ย้ายทรัพยากร Dependency ไปยัง UI หรือชั้นข้อมูลแทน
แสดงสถานะ UI
แนะนำ
ViewModels ควรแสดงข้อมูลไปยัง UI ผ่านพร็อพเพอร์ตี้เดียวที่ชื่อ uiState หาก UI แสดงข้อมูลที่ไม่เกี่ยวข้องกันหลายรายการ VM จะแสดงพร็อพเพอร์ตี้สถานะ UI ได้หลายรายการ
  • คุณควรทำให้ uiState เป็น StateFlow
  • คุณควรสร้าง uiState โดยใช้โอเปอเรเตอร์ stateIn พร้อมกับนโยบาย WhileSubscribed(5000) (ตัวอย่าง) หากข้อมูลมาในรูปแบบสตรีมข้อมูลจากเลเยอร์อื่นๆ ในลำดับชั้น
  • สำหรับกรณีที่เรียบง่ายขึ้นซึ่งไม่มีสตรีมข้อมูลมาจากชั้นข้อมูล คุณจะใช้ MutableStateFlow ซึ่งเปิดเผยเป็น StateFlow ที่เปลี่ยนแปลงไม่ได้ (ตัวอย่าง) ได้
  • โดยคุณสามารถเลือกให้ ${Screen}UiState เป็นคลาสข้อมูลที่อาจมีข้อมูล ข้อผิดพลาด และสัญญาณการโหลด คลาสนี้อาจเป็นคลาสที่ปิดผนึกได้หากรัฐต่างๆ เป็นเอกสิทธิ์เฉพาะ

ข้อมูลโค้ดต่อไปนี้สรุปวิธีแสดงสถานะ 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
            )

    // ...
}

วงจร

ต่อไปนี้เป็นแนวทางปฏิบัติที่ดีที่สุดบางส่วนในการใช้งานระบบ Android วงจร:

คำแนะนำ คำอธิบาย
อย่าลบล้างเมธอดอายุการใช้งานใน "กิจกรรม" หรือ "ส่วนย่อย"
แนะนำอย่างยิ่ง
อย่าลบล้างวิธีใช้อายุการใช้งาน เช่น onResume ในกิจกรรมหรือ Fragment โปรดใช้ LifecycleObserver แทน หากแอปต้องทำงานเมื่อวงจรถึง Lifecycle.State ที่กำหนด ให้ใช้ repeatOnLifecycle API

ข้อมูลโค้ดต่อไปนี้สรุปวิธีดำเนินการ สถานะของวงจร:

ยอดดู

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) {
                // ...
            }
        }
    }
}

เขียน

@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)
        }
    }
}

จัดการทรัพยากร Dependency

มีแนวทางปฏิบัติแนะนำมากมายที่คุณควรปฏิบัติตามเมื่อจัดการทรัพยากร Dependency ระหว่างคอมโพเนนต์

คำแนะนำ คำอธิบาย
ใช้การแทรก Dependency
แนะนำอย่างยิ่ง
ใช้แนวทางปฏิบัติแนะนำเกี่ยวกับ Dependency Injection โดยหลักๆ แล้วจะใช้การแทรกเครื่องมือสร้าง เมื่อเป็นไปได้
กำหนดขอบเขตไปยังคอมโพเนนต์เมื่อจำเป็น
แนะนำอย่างยิ่ง
กำหนดขอบเขตเป็นคอนเทนเนอร์ทรัพยากร Dependency เมื่อประเภทมีข้อมูลที่เปลี่ยนแปลงได้ซึ่งจำเป็นต้องแชร์ หรือประเภทการเริ่มต้นมีต้นทุนสูงและใช้กันอย่างแพร่หลายในแอป
ใช้ Hilt
แนะนำ
ใช้ Hilt หรือการแทรกทรัพยากร Dependency ด้วยตนเองในแอปแบบง่ายๆ ใช้ Hilt หากโครงการของคุณซับซ้อนพอ ตัวอย่างเช่น หากคุณมี
  • หลายหน้าจอที่มี ViewModels—การผสานรวม
  • การใช้งาน WorkManager - การผสานรวม
  • การใช้การนำทางขั้นสูง เช่น ViewModels ที่กำหนดขอบเขตเป็นกราฟการนำทาง ซึ่งก็คือการผสานการทำงาน

การทดสอบ

แนวทางปฏิบัติแนะนำสำหรับการทดสอบมีดังนี้

คำแนะนำ คำอธิบาย
รู้ว่าต้องทดสอบอะไร
แนะนำอย่างยิ่ง

คุณควรทดสอบโปรเจ็กต์ด้วยรายการต่อไปนี้เป็นอย่างน้อย เว้นแต่โปรเจ็กต์จะเรียบง่ายเหมือนแอป Hello World แล้ว

  • การทดสอบหน่วย ViewModels รวมถึง Flows
  • เอนทิตีชั้นข้อมูลการทดสอบหน่วย ซึ่งก็คือ ที่เก็บและแหล่งข้อมูล
  • การทดสอบการนำทางใน UI ที่เป็นประโยชน์ในฐานะการทดสอบการเกิดปัญหาซ้ำใน CI
ชอบปลอมเพื่อล้อเลียน
แนะนำอย่างยิ่ง
อ่านเพิ่มเติมได้ในใช้การทดสอบแบบทวีคูณในเอกสาร Android
ทดสอบ StateFlows
แนะนำอย่างยิ่ง
เมื่อทดสอบ StateFlow:

สำหรับข้อมูลเพิ่มเติม โปรดดูสิ่งที่ต้องทดสอบในคู่มือ DAC ของ Android

รุ่น

คุณควรสังเกตแนวทางปฏิบัติแนะนำต่อไปนี้เมื่อพัฒนาโมเดลในแอป

คำแนะนำ คำอธิบาย
สร้างโมเดลต่อเลเยอร์ในแอปที่ซับซ้อน
แนะนำ

ในแอปที่ซับซ้อน ให้สร้างโมเดลใหม่ในเลเยอร์หรือคอมโพเนนต์ต่างๆ ตามความเหมาะสม ลองดูตัวอย่างต่อไปนี้

  • แหล่งข้อมูลระยะไกลสามารถแมปโมเดลที่ได้รับผ่านเครือข่ายกับคลาสที่ง่ายขึ้นด้วยข้อมูลที่แอปต้องการเท่านั้น
  • ที่เก็บสามารถแมปโมเดล DAO กับคลาสข้อมูลที่ง่ายขึ้นโดยมีข้อมูลที่เลเยอร์ UI ต้องการเท่านั้น
  • ViewModel สามารถรวมโมเดลชั้นข้อมูลในคลาส UiState

รูปแบบการตั้งชื่อ

เมื่อตั้งชื่อฐานของโค้ด คุณควรทำตามแนวทางปฏิบัติแนะนำต่อไปนี้

คำแนะนำ คำอธิบาย
วิธีตั้งชื่อ
ไม่บังคับ
วิธีการควรเป็นวลีกริยา เช่น makePayment()
การตั้งชื่อพร็อพเพอร์ตี้
ไม่บังคับ
คุณสมบัติควรเป็นคำนาม เช่น inProgressTopicSelection
การตั้งชื่อสตรีมข้อมูล
ไม่บังคับ
เมื่อคลาสแสดงสตรีมโฟลว์, LiveData หรือสตรีมอื่นๆ รูปแบบการตั้งชื่อคือ get{model}Stream() เช่น getAuthorStream(): Flow<Author> หากฟังก์ชันแสดงผลรายการโมเดล ชื่อโมเดลควรอยู่ในรูปพหูพจน์: getAuthorsStream(): Flow<List<Author>>
การใช้งานอินเทอร์เฟซการตั้งชื่อ
ไม่บังคับ
ชื่อสำหรับการใช้อินเทอร์เฟซควรมีความหมาย มี Default เป็นคำนำหน้าหากไม่พบชื่อที่ดีกว่า ตัวอย่างเช่น สำหรับอินเทอร์เฟซ NewsRepository คุณอาจมี OfflineFirstNewsRepository หรือ InMemoryNewsRepository หากไม่เห็นชื่อที่เป็นประโยชน์ ให้ใช้ DefaultNewsRepository การติดตั้งใช้งานปลอมควรขึ้นต้นด้วย Fake เช่น FakeAuthorsRepository