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

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

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

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

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

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

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

ตรวจสอบว่าคอมโพเนนต์ในเลเยอร์ UI เช่น Composable หรือ ViewModel ไม่โต้ตอบกับแหล่งข้อมูลโดยตรง ตัวอย่างแหล่งข้อมูล ได้แก่

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

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

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

เลเยอร์ UI

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

คำแนะนำ คำอธิบาย
ทำตามการไหลของข้อมูลแบบทางเดียว (UDF) ปฏิบัติตามหลักการการไหลของข้อมูลแบบทิศทางเดียว (UDF) ซึ่ง ViewModel จะแสดงสถานะ UI โดยใช้รูปแบบ Observer และรับการดำเนินการจาก UI ผ่านการเรียกใช้เมธอด
ใช้ AAC ViewModels หากประโยชน์ของ ViewModel นั้นๆ เหมาะกับแอปของคุณ ใช้ AAC ViewModels เพื่อจัดการตรรกะทางธุรกิจ และดึงข้อมูลแอปพลิเคชันเพื่อแสดงสถานะ UI ต่อ UI

ดูข้อมูลเพิ่มเติมเกี่ยวกับแนวทางปฏิบัติแนะนำของ ViewModel ได้ที่คำแนะนำด้านสถาปัตยกรรม

ดูข้อมูลเพิ่มเติมเกี่ยวกับประโยชน์ของ ViewModel ได้ที่ViewModel ในฐานะที่เก็บสถานะของตรรกะทางธุรกิจ

ใช้การรวบรวมสถานะ UI ที่รับรู้ถึงวงจร รวบรวมสถานะ UI จาก UI โดยใช้ตัวสร้างโครูทีนที่รับรู้ถึงวงจรที่เหมาะสม collectAsStateWithLifecycle

ดูข้อมูลเพิ่มเติมเกี่ยวกับ collectAsStateWithLifecycle

อย่าส่งเหตุการณ์จาก ViewModel ไปยัง UI ประมวลผลเหตุการณ์ใน ViewModel ทันทีและทําให้เกิดการอัปเดตสถานะด้วยผลลัพธ์ของการจัดการเหตุการณ์ ดูข้อมูลเพิ่มเติมเกี่ยวกับเหตุการณ์ UI ได้ที่จัดการเหตุการณ์ ViewModel
ใช้แอปพลิเคชันแบบกิจกรรมเดียว ใช้การนำทาง 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 ให้ประเมินอย่างรอบคอบว่าอยู่ในเลเยอร์ที่ถูกต้องหรือไม่
ใช้โครูทีนและโฟลว์

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

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

อย่าใช้ ViewModel ในชิ้นส่วน UI ที่นำกลับมาใช้ใหม่ได้ คุณควรใช้ ViewModel ในส่วนต่อไปนี้

  • Composable ระดับหน้าจอ
  • กิจกรรม/Fragment ใน View
  • ปลายทางหรือกราฟเมื่อใช้ Jetpack Navigation
ใช้คลาสตัวเก็บสถานะธรรมดาในคอมโพเนนต์ UI ที่นำกลับมาใช้ใหม่ได้ ใช้คลาสตัวเก็บสถานะธรรมดาเพื่อจัดการความซับซ้อนในคอมโพเนนต์ UI ที่นำกลับมาใช้ใหม่ได้ เมื่อทำเช่นนี้ คุณจะยกสถานะและควบคุมภายนอกได้
อย่าใช้ AndroidViewModel ใช้คลาส ViewModel ไม่ใช่ AndroidViewModel อย่าใช้คลาส Application ใน ViewModel แต่ให้ย้ายการอ้างอิงไปยัง UI หรือชั้นข้อมูลแทน
แสดงสถานะ UI ทำให้ ViewModel แสดงข้อมูลต่อ 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
            )

    // ...
}

วงจร

ทำตามแนวทางปฏิบัติแนะนำสำหรับการทำงานกับวงจร กิจกรรม

คำแนะนำ คำอธิบาย
ใช้เอฟเฟกต์ที่รับรู้ถึงวงจรใน Composable แทนการลบล้างโค้ดเรียกกลับของวงจร Activity

อย่าลบล้างActivityเมธอดวงจร เช่น onResume เพื่อเรียกใช้ฟังก์ชันที่เกี่ยวข้องกับ UI แต่ให้ใช้ LifecycleEffects ของ Compose หรือขอบเขตโครูทีนที่รับรู้ถึงวงจรของแอปแทน

  • ใช้ LifecycleStartEffect เพื่อทำงานแบบซิงโครนัสเมื่อกิจกรรมเริ่มต้นและสิ้นสุด
  • ใช้ LifecycleResumeEffect เพื่อทำงานพร้อมกันเมื่อกิจกรรมของคุณกลับมาทำงานต่อและหยุดชั่วคราว
  • ใช้ repeatOnLifecycle เพื่อทำงานแบบไม่พร้อมกันเพื่อตอบสนองต่อเหตุการณ์วงจร
  • รวบรวมข้อมูลแบบไม่พร้อมกันจากโฟลว์โดยใช้ collectAsStateWithLifecycle

ข้อมูลโค้ดต่อไปนี้แสดงวิธีดำเนินการเมื่อมีสถานะ Lifecycle ที่เฉพาะเจาะจง

  @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 โดยหลักๆ คือการแทรกเครื่องมือสร้างเมื่อเป็นไปได้
กำหนดขอบเขตไปยังคอมโพเนนต์เมื่อจำเป็น กำหนดขอบเขตเป็นคอนเทนเนอร์การอ้างอิงเมื่อประเภทมีข้อมูลที่เปลี่ยนแปลงได้ซึ่งต้องแชร์ หรือประเภทมีค่าใช้จ่ายสูงในการเริ่มต้นและใช้กันอย่างแพร่หลายในแอป
ใช้ Hilt ใช้ Hilt หรือการแทรกทรัพยากร Dependency ด้วยตนเองในแอปที่เรียบง่าย ใช้ Hilt หากโปรเจ็กต์มีความซับซ้อนมากพอ เช่น หากมีสิ่งต่อไปนี้
  • หลายหน้าจอที่มี ViewModel
  • ใช้ WorkManager
  • มี ViewModel ที่กำหนดขอบเขตไว้กับ Back Stack ของการนำทาง

การทดสอบ

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

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

ทดสอบโปรเจ็กต์ เว้นแต่ว่าโปรเจ็กต์จะเป็นแอปง่ายๆ อย่าง "hello world" โดยควรมีข้อมูลต่อไปนี้เป็นอย่างน้อย

  • Unit Test สำหรับ ViewModel รวมถึง Flow
  • การทดสอบหน่วยสำหรับเอนทิตีของ Data Layer ซึ่งก็คือที่เก็บและแหล่งข้อมูล
  • การทดสอบการนำทาง UI ที่มีประโยชน์เป็นการทดสอบถดถอยใน CI
ใช้ Fake แทน Mock ดูข้อมูลเพิ่มเติมเกี่ยวกับการใช้ข้อมูลจำลองได้ที่ใช้การทดสอบแบบคู่ใน Android
ทดสอบ StateFlow เมื่อทดสอบ StateFlow ให้ทำดังนี้

ดูข้อมูลเพิ่มเติมได้ที่สิ่งที่ควรทดสอบใน Android และทดสอบเลย์เอาต์ Compose

โมเดล

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

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

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

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

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

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

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

แหล่งข้อมูลเพิ่มเติม

ดูข้อมูลเพิ่มเติมเกี่ยวกับสถาปัตยกรรมของ Android ได้ที่แหล่งข้อมูลเพิ่มเติมต่อไปนี้

เอกสาร

ดูเนื้อหา