เจ้าของสถานะและสถานะ UI

คู่มือเลเยอร์ UI พูดถึงโฟลว์ข้อมูลแบบทิศทางเดียว (UDF) ในฐานะวิธีการ การผลิตและจัดการสถานะ UI สำหรับเลเยอร์ UI

วันที่ ข้อมูลจากชั้นข้อมูลไปยัง UI จะเป็นทิศทางเดียว
รูปที่ 1: โฟลว์ข้อมูลแบบทิศทางเดียว

และยังเน้นย้ำถึงประโยชน์ของการมอบสิทธิ์เข้าถึงการจัดการ UDF ให้แก่ชั้นเรียนพิเศษอีกด้วย ที่เรียกว่าเจ้าของสถานะ คุณสามารถใช้ผู้ถือรัฐผ่าน ViewModel หรือชั้นเรียนธรรมดา เอกสารนี้เจาะลึกสถานะ และบทบาทที่ผู้ใช้เล่นในเลเยอร์ UI

เมื่ออ่านจบเอกสารนี้ คุณควรเข้าใจวิธีจัดการ สถานะของแอปพลิเคชันในเลเยอร์ UI ซึ่งก็คือไปป์ไลน์การสร้างสถานะ UI คุณ ควรจะเข้าใจและทราบสิ่งต่อไปนี้

  • ทำความเข้าใจประเภทสถานะ UI ที่มีอยู่ในเลเยอร์ UI
  • ทำความเข้าใจประเภทของตรรกะที่ดำเนินการกับสถานะ UI เหล่านั้นในเลเยอร์ UI
  • รู้วิธีเลือกการใช้งานผู้ถือรัฐที่เหมาะสม เช่น ViewModel หรือชั้นเรียนธรรมดา

องค์ประกอบของไปป์ไลน์การสร้างสถานะ UI

สถานะ UI และตรรกะที่สร้างขึ้นจะเป็นตัวกำหนดเลเยอร์ UI

สถานะ UI

สถานะ UI คือพร็อพเพอร์ตี้ที่อธิบาย UI UI มี 2 ประเภท รัฐ:

  • สถานะ UI ของหน้าจอคือสิ่งที่คุณต้องแสดงบนหน้าจอ ตัวอย่างเช่น คลาส NewsUiState สามารถมีบทความข่าวและข้อมูลอื่นๆ ที่จำเป็นได้ เพื่อแสดงผล UI สถานะนี้มักจะเชื่อมต่อกับเลเยอร์อื่นๆ ของ ลำดับชั้นเนื่องจากมีข้อมูลแอป
  • สถานะองค์ประกอบ UI หมายถึงพร็อพเพอร์ตี้ที่มีอยู่ในองค์ประกอบ UI ที่ ส่งผลต่อวิธีแสดงผล องค์ประกอบ UI อาจแสดงหรือซ่อนไว้ และอาจ จะมีแบบอักษร ขนาดแบบอักษร หรือสีแบบอักษรที่เฉพาะเจาะจง ใน Android View มุมมอง จัดการสถานะนี้ด้วยตัวเอง เนื่องจากเป็นการเก็บสถานะโดยธรรมชาติ แก้ไขหรือค้นหาสถานะ ตัวอย่างได้แก่ get และ เมธอด set ของคลาส TextView สำหรับข้อความ ใน Jetpack Compose สถานะนี้อยู่นอก Composable และคุณยังยกขึ้นได้ด้วย ออกจากพื้นที่ใกล้เคียงของ Composable กับการเรียกใช้ Composable หรือตัวยึดสถานะ ตัวอย่างของกรณีนี้คือ ScaffoldState สำหรับ Scaffold Composable

เชิงตรรกะ

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

วันที่ Logic สร้างสถานะ UI
รูปที่ 2: ตรรกะในฐานะผู้สร้างสถานะ UI

ตรรกะในแอปพลิเคชันอาจเป็นตรรกะทางธุรกิจหรือตรรกะ UI ดังนี้

  • ตรรกะทางธุรกิจคือการนำข้อกำหนดผลิตภัณฑ์มาใช้สำหรับแอป ตัวอย่างเช่น การบุ๊กมาร์กบทความในแอปโปรแกรมอ่านข่าวเมื่อผู้ใช้ แตะปุ่ม ตรรกะในการบันทึกบุ๊กมาร์กไปยังไฟล์หรือฐานข้อมูลนี้ ซึ่งปกติวางไว้ในโดเมนหรือชั้นข้อมูล เจ้าของรัฐมักจะ จะมอบสิทธิ์ตรรกะนี้ให้กับเลเยอร์เหล่านั้นโดยเรียกใช้เมธอดที่เลเยอร์เหล่านั้นแสดง
  • ตรรกะ UI เกี่ยวข้องกับวิธีแสดงสถานะ UI บนหน้าจอ สำหรับ ตัวอย่างเช่น การรับคำแนะนำแถบการค้นหาที่ถูกต้องเมื่อผู้ใช้เลือก หมวดหมู่ การเลื่อนไปยังรายการใดโดยเฉพาะในรายการ หรือตรรกะการนำทาง ไปยังหน้าจอที่ต้องการเมื่อผู้ใช้คลิกปุ่ม

วงจรของ Android รวมถึงประเภทสถานะและตรรกะของ UI

เลเยอร์ UI มี 2 ส่วน ได้แก่ ส่วนหนึ่งเป็นอิสระจากกันและอีกส่วนเป็นอิสระจาก UI ใหม่ การแยกนี้จะกําหนดแหล่งข้อมูลที่พร้อมใช้งานสําหรับแต่ละส่วน ดังนั้นจึงต้องใช้สถานะและตรรกะของ UI หลายประเภท

  • ไม่ขึ้นอยู่กับวงจรการใช้งาน UI: เลเยอร์ UI ส่วนนี้เกี่ยวข้องกับข้อมูล การสร้างเลเยอร์ของแอป (เลเยอร์ข้อมูลหรือโดเมน) และกำหนดโดยธุรกิจ วงจร การเปลี่ยนแปลงการกำหนดค่า และการดำเนินการใหม่ Activity รายการใน UI อาจส่งผลกระทบหากไปป์ไลน์การสร้างสถานะ UI ทำงานอยู่ แต่จะไม่ส่งผลต่อ ความถูกต้องของข้อมูลที่สร้าง
  • ขึ้นอยู่กับวงจรการใช้งาน UI: ส่วนนี้ของเลเยอร์ UI จะดีลกับตรรกะ UI และ ได้รับอิทธิพลโดยตรงจากการเปลี่ยนแปลงวงจรหรือการกำหนดค่า การเปลี่ยนแปลงเหล่านี้ มีผลโดยตรงต่อความถูกต้องของแหล่งข้อมูลที่อ่านภายในแหล่งข้อมูล และ สถานะของผลลัพธ์จะเปลี่ยนได้ก็ต่อเมื่อวงจรทำงานเท่านั้น ตัวอย่างของ ซึ่งรวมถึงสิทธิ์รันไทม์และการรับทรัพยากรที่ขึ้นอยู่กับการกำหนดค่า เช่น สตริงที่แปลแล้ว

ข้อมูลข้างต้นสามารถสรุปได้ด้วยตารางด้านล่าง

ไม่ขึ้นอยู่กับวงจรการใช้งาน UI อิงตามวงจรการใช้งาน UI
ตรรกะทางธุรกิจ ตรรกะ UI
สถานะ UI ของหน้าจอ

ไปป์ไลน์การสร้างสถานะ UI

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

ซึ่งหมายความว่าการเรียงสับเปลี่ยนไปป์ไลน์ของเลเยอร์ UI ต่อไปนี้ถูกต้อง

  • สถานะ UI ที่สร้างและจัดการโดย UI เอง ตัวอย่างเช่น ตัวนับแบบใช้ซ้ำได้:

    @Composable
    fun Counter() {
        // The UI state is managed by the UI itself
        var count by remember { mutableStateOf(0) }
        Row {
            Button(onClick = { ++count }) {
                Text(text = "Increment")
            }
            Button(onClick = { --count }) {
                Text(text = "Decrement")
            }
        }
    }
    
  • ตรรกะ UI → UI ตัวอย่างเช่น การแสดงหรือซ่อนปุ่มซึ่งช่วยให้ผู้ใช้ ข้ามไปที่ส่วนบนสุดของรายการ

    @Composable
    fun ContactsList(contacts: List<Contact>) {
        val listState = rememberLazyListState()
        val isAtTopOfList by remember {
            derivedStateOf {
                listState.firstVisibleItemIndex < 3
            }
        }
    
        // Create the LazyColumn with the lazyListState
        ...
    
        // Show or hide the button (UI logic) based on the list scroll position
        AnimatedVisibility(visible = !isAtTopOfList) {
            ScrollToTopButton()
        }
    }
    
  • ตรรกะทางธุรกิจ → UI องค์ประกอบ UI ที่แสดงรูปภาพของผู้ใช้คนปัจจุบันบน บนหน้าจอ

    @Composable
    fun UserProfileScreen(viewModel: UserProfileViewModel = hiltViewModel()) {
        // Read screen UI state from the business logic state holder
        val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    
        // Call on the UserAvatar Composable to display the photo
        UserAvatar(picture = uiState.profilePicture)
    }
    
  • ตรรกะทางธุรกิจ → ตรรกะ UI → UI องค์ประกอบ UI ที่เลื่อนเพื่อแสดง ข้อมูลที่ถูกต้องบนหน้าจอสำหรับสถานะของ UI หนึ่งๆ

    @Composable
    fun ContactsList(viewModel: ContactsViewModel = hiltViewModel()) {
        // Read screen UI state from the business logic state holder
        val uiState by viewModel.uiState.collectAsStateWithLifecycle()
        val contacts = uiState.contacts
        val deepLinkedContact = uiState.deepLinkedContact
    
        val listState = rememberLazyListState()
    
        // Create the LazyColumn with the lazyListState
        ...
    
        // Perform UI logic that depends on information from business logic
        if (deepLinkedContact != null && contacts.isNotEmpty()) {
            LaunchedEffect(listState, deepLinkedContact, contacts) {
                val deepLinkedContactIndex = contacts.indexOf(deepLinkedContact)
                if (deepLinkedContactIndex >= 0) {
                  // Scroll to deep linked item
                  listState.animateScrollToItem(deepLinkedContactIndex)
                }
            }
        }
    }
    

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

วันที่ ข้อมูลไหลจากเลเยอร์การผลิตข้อมูลไปยัง UI
รูปที่ 3: การใช้ตรรกะในเลเยอร์ UI

ผู้ถือรัฐและความรับผิดชอบของผู้ถือรัฐ

ความรับผิดชอบของผู้ถือรัฐคือการจัดเก็บรัฐเพื่อให้แอปอ่านรัฐได้ ในกรณีที่ต้องใช้ตรรกะ ก็จะเป็นเสมือนตัวกลางและให้การเข้าถึง ไปยังแหล่งข้อมูลที่โฮสต์ตรรกะที่จำเป็น ในกรณีนี้ ผู้ถือครองสถานะ มอบสิทธิ์ตรรกะให้กับแหล่งข้อมูลที่เหมาะสม

ซึ่งมีประโยชน์ดังต่อไปนี้

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

ไม่ว่าจะมีขนาดหรือขอบเขตเท่าใด องค์ประกอบ UI ทั้งหมดจะมีความสัมพันธ์แบบ 1:1 กับ ของเจ้าของสถานะที่เกี่ยวข้อง นอกจากนี้ ผู้ถือรัฐจะต้องสามารถ ยอมรับและประมวลผลการทำงานใดๆ ของผู้ใช้ที่อาจทำให้เกิดการเปลี่ยนแปลงสถานะ UI และ จะต้องสร้างการเปลี่ยนแปลงสถานะที่เกิดขึ้น

ประเภทของผู้ถือรัฐ

ผู้ถือสถานะมี 2 ประเภท ซึ่งคล้ายกับสถานะและตรรกะของ UI ในเลเยอร์ UI ที่กำหนดโดยความสัมพันธ์ที่มีต่อวงจร UI:

  • เจ้าของสถานะตรรกะธุรกิจ
  • ตัวยึดสถานะตรรกะ UI

ส่วนต่อไปนี้จะอธิบายประเภทของผู้ถือครองรัฐ เริ่มด้วยตัวยึดสถานะตรรกะธุรกิจ

ตรรกะทางธุรกิจและผู้ถือสถานะ

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

พร็อพเพอร์ตี้ รายละเอียด
สร้างสถานะ UI ผู้ถือสถานะตรรกะทางธุรกิจมีหน้าที่รับผิดชอบในการสร้างสถานะ UI สําหรับ UI ของตน สถานะ UI นี้มักเป็นผลมาจากการประมวลผลเหตุการณ์ของผู้ใช้และการอ่านข้อมูลจากโดเมนและชั้นข้อมูล
เก็บไว้ผ่านนันทนาการกิจกรรม เจ้าของสถานะตรรกะทางธุรกิจจะยังคงได้รับไปป์ไลน์การประมวลผลสถานะและสถานะของการดำเนินการเหล่านั้นในการนันทนาการ Activity ไว้ ซึ่งจะช่วยให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่ราบรื่น ในกรณีที่เจ้าของสถานะไม่สามารถเก็บรักษาไว้และมีการสร้างขึ้นใหม่ (โดยปกติจะเกิดขึ้นหลังกระบวนการเสียชีวิต) เจ้าของรัฐต้องสามารถสร้างสถานะสุดท้ายขึ้นใหม่ได้โดยง่ายเพื่อให้ผู้ใช้ได้รับประสบการณ์ที่สอดคล้องกัน
มีสถานะอายุยืนนาน ผู้ถือสถานะตรรกะทางธุรกิจมักจะใช้เพื่อจัดการสถานะสำหรับปลายทางการนำทาง ด้วยเหตุนี้ จึงมักรักษาสถานะของตนในการเปลี่ยนแปลงการนําทางไว้จนกว่าจะนําออกจากกราฟการนําทาง
มีลักษณะเฉพาะสำหรับ UI และนำมาใช้ซ้ำไม่ได้ โดยทั่วไปแล้ว ผู้ถือสถานะตรรกะทางธุรกิจจะสร้างสถานะสำหรับฟังก์ชันของแอปบางอย่าง เช่น TaskEditViewModel หรือ TaskListViewModel ดังนั้นจึงใช้ได้กับฟังก์ชันของแอปดังกล่าวเท่านั้น เจ้าของสถานะเดียวกันจะรองรับฟังก์ชันของแอปเหล่านี้ในอุปกรณ์รูปแบบต่างๆ ได้ เช่น แอปในเวอร์ชันอุปกรณ์เคลื่อนที่ ทีวี และแท็บเล็ตอาจใช้เจ้าของสถานะตรรกะทางธุรกิจเดียวกันซ้ำ

ตัวอย่างเช่น ลองพิจารณาปลายทางการนำทางของผู้เขียนใน "ขณะนี้อยู่ใน Android" แอป:

วันที่ แอป Now in Android แสดงให้เห็นว่าปลายทางการนำทางที่แสดงถึงฟังก์ชันหลักของแอปควรมี
เจ้าของสถานะตรรกะทางธุรกิจที่ไม่ซ้ำกัน
ภาพที่ 4: แอป Now in Android

ในฐานะผู้ถือสถานะตรรกะทางธุรกิจ AuthorViewModel จะสร้างสถานะ UI ในกรณีนี้

@HiltViewModel
class AuthorViewModel @Inject constructor(
    savedStateHandle: SavedStateHandle,
    private val authorsRepository: AuthorsRepository,
    newsRepository: NewsRepository
) : ViewModel() {

    val uiState: StateFlow<AuthorScreenUiState> = …

    // Business logic
    fun followAuthor(followed: Boolean) {
      …
    }
}

โปรดสังเกตว่า AuthorViewModel มีแอตทริบิวต์ที่ระบุไว้ก่อนหน้านี้

พร็อพเพอร์ตี้ รายละเอียด
ผลิต AuthorScreenUiState AuthorViewModel อ่านข้อมูลจาก AuthorsRepository และ NewsRepository และใช้ข้อมูลดังกล่าวเพื่อสร้าง AuthorScreenUiState นอกจากนี้ยังใช้ตรรกะทางธุรกิจเมื่อผู้ใช้ต้องการติดตามหรือเลิกติดตาม Author โดยการมอบสิทธิ์ให้ AuthorsRepository
มีสิทธิ์เข้าถึงชั้นข้อมูล อินสแตนซ์ของ AuthorsRepository และ NewsRepository จะส่งไปยังตัวสร้าง ทำให้สามารถใช้ตรรกะทางธุรกิจของการติดตาม Author ได้
เอาชีวิตรอดจากนันทนาการ Activity เนื่องจากมีการใช้ร่วมกับ ViewModel ข้อมูลจะถูกเก็บรักษาไว้ในกิจกรรมสั้นๆ ของ Activity ในกรณีที่กระบวนการหยุดทำงาน คุณสามารถอ่านออบเจ็กต์ SavedStateHandle เพื่อระบุจำนวนข้อมูลขั้นต่ำที่จำเป็นในการคืนค่าสถานะ UI จากชั้นข้อมูล
มีสถานะอายุยืนนาน ViewModel จะกำหนดขอบเขตอยู่ที่กราฟการนำทาง ดังนั้นเว้นแต่ระบบจะนำปลายทางของผู้เขียนออกจากกราฟการนำทาง สถานะ UI ใน StateFlow ของ uiState จะยังคงอยู่ในหน่วยความจำ การใช้ StateFlow ยังเพิ่มประโยชน์ของการใช้ตรรกะทางธุรกิจที่ทำให้เกิดสถานะแบบ Lazy Loading ด้วย เนื่องจากสถานะจะเกิดขึ้นในกรณีที่มีผู้รวบรวมสถานะ UI เท่านั้น
มีเอกลักษณ์ใน UI AuthorViewModel ใช้ได้กับปลายทางของการนำทางผู้เขียนเท่านั้นและจะนำมาใช้ซ้ำในที่อื่นไม่ได้ หากมีตรรกะทางธุรกิจที่นํามาใช้ซ้ำในปลายทางการนําทาง ตรรกะทางธุรกิจดังกล่าวจะต้องรวมอยู่ในคอมโพเนนต์ที่กําหนดขอบเขตระดับเลเยอร์ข้อมูลหรือระดับโดเมน

ViewModel ในฐานะเจ้าของสถานะตรรกะธุรกิจ

ประโยชน์ของ ViewModels ในการพัฒนาซอฟต์แวร์ Android ทำให้เหมาะสำหรับ ให้การเข้าถึงตรรกะทางธุรกิจและการเตรียมข้อมูลแอปพลิเคชันสำหรับ งานนำเสนอบนหน้าจอ สิทธิประโยชน์ดังกล่าวรวมถึงสิ่งต่อไปนี้

  • การดำเนินการที่ทริกเกอร์โดย ViewModels ยังคงมีผลต่อการเปลี่ยนแปลงการกำหนดค่า
  • การผสานรวมกับการนำทาง:
    • การนำทางจะแคช ViewModels ขณะที่หน้าจออยู่ที่สแต็กด้านหลัง นี่คือ ข้อมูลที่โหลดก่อนหน้านี้จะแสดงได้ทันที กลับไปยังปลายทางของคุณ ซึ่งทำได้ยากขึ้นเมื่อใช้ ที่ยึดสถานะนี้เป็นไปตามวงจรของหน้าจอ Composable
    • ระบบจะล้าง ViewModel ด้วยเมื่อปลายทางดึงออกมาจากด้านหลัง ซ้อนกัน เพื่อให้มั่นใจว่าสถานะของคุณจะได้รับการล้างโดยอัตโนมัติ นี่คือ ต่างจากการฟังฟังก์ชัน Composable ที่สามารถเกิดขึ้นได้สำหรับ หลายสาเหตุ เช่น การเปิดหน้าจอใหม่ อันเนื่องมาจากการกำหนดค่า เปลี่ยนแปลง หรือเหตุผลอื่นๆ
  • การผสานรวมกับไลบรารี Jetpack อื่นๆ เช่น Hilt

ตรรกะ UI และตัวยึดสถานะ

ตรรกะ UI คือตรรกะที่ดำเนินการกับข้อมูลที่ UI มีให้ นี่อาจเป็น ขององค์ประกอบ UI" หรือในแหล่งข้อมูล UI เช่น API สิทธิ์ หรือ Resources เจ้าของสถานะที่ใช้ตรรกะ UI มักจะมี พร็อพเพอร์ตี้ต่อไปนี้

  • สร้างสถานะ UI และจัดการสถานะองค์ประกอบ UI
  • ไม่อยู่ในเกณฑ์สันทนาการ Activity: เจ้าของรัฐที่โฮสต์ใน UI มักขึ้นอยู่กับแหล่งข้อมูลจาก UI โดยตรง และพยายามที่จะ เก็บรักษาข้อมูลนี้ในการเปลี่ยนแปลงการกำหนดค่าบ่อยกว่าไม่ทำให้เกิด หน่วยความจำรั่วไหล หากเจ้าของสถานะต้องการข้อมูลเพื่อคงอยู่ในการกำหนดค่า การเปลี่ยนแปลง จำเป็นต้องมอบสิทธิ์ให้กับคอมโพเนนต์อื่นที่เหมาะกับการรอดชีวิต Activity มากกว่า นันทนาการ เช่น ใน Jetpack Compose สถานะองค์ประกอบ UI ที่ Composable สร้างด้วยฟังก์ชัน remembered ที่มักจะมอบสิทธิ์ให้กับ rememberSaveable ให้กับ รักษาสถานะไว้ในกิจกรรม Activity ตัวอย่างของฟังก์ชันดังกล่าว รวมถึง rememberScaffoldState() และ rememberLazyListState()
  • มีการอ้างอิงถึงแหล่งข้อมูลที่กําหนดขอบเขต UI: แหล่งที่มาของข้อมูล เช่น สามารถอ้างอิงและอ่าน API และทรัพยากรของ Lifecycle ได้อย่างปลอดภัย เจ้าของสถานะมีวงจรเหมือนกับ UI
  • นำมาใช้ซ้ำได้ใน UI หลายรายการ: อินสแตนซ์ที่แตกต่างกันของตรรกะ UI เดียวกัน สามารถนำกลับมาใช้ใหม่ในส่วนต่างๆ ของแอปได้ เช่น รัฐ เจ้าของสำหรับจัดการเหตุการณ์การป้อนข้อมูลของผู้ใช้สำหรับกลุ่มชิปอาจนำไปใช้ในการค้นหา หน้าสำหรับชิปตัวกรอง และสำหรับ "ถึง" สำหรับผู้รับอีเมล

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

ข้อมูลข้างต้นอาจแสดงในตัวอย่างต่อไปนี้ใน ตัวอย่าง Now ใน Android

วันที่ ตอนนี้ใน Android จะใช้ตัวยึดสถานะคลาสธรรมดาเพื่อจัดการตรรกะ UI
ภาพที่ 5: ตัวอย่าง Now ใน Android แอป

ตัวอย่าง Now ใน Android แสดงแถบแอปด้านล่างหรือแถบนำทางสำหรับ การนำทางตามขนาดหน้าจอของอุปกรณ์ หน้าจอขนาดเล็กใช้ แถบแอปด้านล่าง และหน้าจอที่ใหญ่กว่าของแถบการนำทาง

เนื่องจากตรรกะสำหรับการเลือกองค์ประกอบ UI การไปยังส่วนต่างๆ ที่เหมาะสมที่ใช้ในองค์ประกอบ NiaApp ฟังก์ชันที่ประกอบกันได้ไม่ขึ้นอยู่กับตรรกะทางธุรกิจ แต่จัดการได้ โดยเจ้าของสถานะคลาสธรรมดาชื่อ NiaAppState:

@Stable
class NiaAppState(
    val navController: NavHostController,
    val windowSizeClass: WindowSizeClass
) {

    // UI logic
    val shouldShowBottomBar: Boolean
        get() = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact ||
            windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact

    // UI logic
    val shouldShowNavRail: Boolean
        get() = !shouldShowBottomBar

   // UI State
    val currentDestination: NavDestination?
        @Composable get() = navController
            .currentBackStackEntryAsState().value?.destination

    // UI logic
    fun navigate(destination: NiaNavigationDestination, route: String? = null) { /* ... */ }

     /* ... */
}

ในตัวอย่างด้านบน รายละเอียดต่อไปนี้เกี่ยวกับ NiaAppState คือ หมายเหตุ

  • ไม่รอดจากกิจกรรม Activity: NiaAppState เท่ากับ remembered ใน การเรียบเรียงเพลงด้วยการสร้างด้วยฟังก์ชัน Composable rememberNiaAppState ตามรูปแบบการตั้งชื่อ Compose หลังจากสร้าง Activity ขึ้นใหม่ อินสแตนซ์ก่อนหน้าสูญหายไป และระบบจะสร้างอินสแตนซ์ใหม่ที่มีอินสแตนซ์ ทรัพยากร Dependency ที่ส่งผ่าน เหมาะสำหรับการกำหนดค่าใหม่ของ สร้าง Activity ใหม่ ทรัพยากร Dependency เหล่านี้อาจเป็นทรัพยากรใหม่หรือมีการคืนค่าจาก การกำหนดค่าก่อนหน้า ตัวอย่างเช่น rememberNavController() จะใช้ใน เครื่องมือสร้าง NiaAppState และมอบสิทธิ์ให้กับ rememberSaveable ให้ รักษาสถานะไว้ในกิจกรรมนันทนาการ Activity ได้
  • มีการอ้างอิงถึงแหล่งข้อมูลที่กำหนดขอบเขตตาม UI: การอ้างอิงถึง navigationController, Resources และประเภทขอบเขตการใช้งานอื่นๆ ที่คล้ายกัน จะเก็บไว้ใน NiaAppState ได้อย่างปลอดภัย เนื่องจากใช้ขอบเขตวงจรเดียวกัน

เลือกระหว่าง ViewModel และคลาสธรรมดาสำหรับเจ้าของสถานะ

จากส่วนด้านบน ให้เลือกระหว่าง ViewModel และสถานะของชั้นเรียนทั่วไป ขึ้นอยู่กับตรรกะที่ใช้กับสถานะ UI และแหล่งข้อมูล ตรรกะจะทำงาน

สรุปคือแผนภาพด้านล่างแสดงตำแหน่งของผู้ครอบครองรัฐใน UI ไปป์ไลน์เวอร์ชันที่ใช้งานจริงของรัฐ:

วันที่ ข้อมูลจากเลเยอร์การผลิตข้อมูลไปยังเลเยอร์ UI
รูปที่ 6: เจ้าของสถานะในไปป์ไลน์การสร้างสถานะ UI ลูกศรหมายถึงการรับส่งข้อมูล

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

ผู้ถือหุ้นของรัฐรวมได้

ผู้ถือรัฐสามารถพึ่งพาผู้ถือรัฐรายอื่นๆ ได้ ตราบเท่าที่ทรัพยากร Dependency มี มีอายุการใช้งานสั้นลงหรือเท่ากัน ตัวอย่างเช่น

  • ตัวยึดสถานะตรรกะ UI จะขึ้นอยู่กับตัวยึดสถานะตรรกะ UI อื่น
  • ตัวยึดสถานะระดับหน้าจอจะขึ้นอยู่กับตัวยึดสถานะตรรกะ UI

ข้อมูลโค้ดต่อไปนี้แสดงวิธีที่DrawerStateของ Compose อ้างอิง ตัวยึดสถานะภายในอื่น SwipeableState และตรรกะ UI ของแอป ผู้ถือสถานะอาจขึ้นอยู่กับ DrawerState:

@Stable
class DrawerState(/* ... */) {
  internal val swipeableState = SwipeableState(/* ... */)
  // ...
}

@Stable
class MyAppState(
  private val drawerState: DrawerState,
  private val navController: NavHostController
) { /* ... */ }

@Composable
fun rememberMyAppState(
  drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
  navController: NavHostController = rememberNavController()
): MyAppState = remember(drawerState, navController) {
  MyAppState(drawerState, navController)
}

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

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

class MyScreenViewModel(/* ... */) {
  val uiState: StateFlow<MyScreenUiState> = /* ... */
  fun doSomething() { /* ... */ }
  fun doAnotherThing() { /* ... */ }
  // ...
}

@Stable
class MyScreenState(
  // DO NOT pass a ViewModel instance to a plain state holder class
  // private val viewModel: MyScreenViewModel,

  // Instead, pass only what it needs as a dependency
  private val someState: StateFlow<SomeState>,
  private val doSomething: () -> Unit,

  // Other UI-scoped types
  private val scaffoldState: ScaffoldState
) {
  /* ... */
}

@Composable
fun rememberMyScreenState(
  someState: StateFlow<SomeState>,
  doSomething: () -> Unit,
  scaffoldState: ScaffoldState = rememberScaffoldState()
): MyScreenState = remember(someState, doSomething, scaffoldState) {
  MyScreenState(someState, doSomething, scaffoldState)
}

@Composable
fun MyScreen(
  modifier: Modifier = Modifier,
  viewModel: MyScreenViewModel = viewModel(),
  state: MyScreenState = rememberMyScreenState(
    someState = viewModel.uiState.map { it.toSomeState() },
    doSomething = viewModel::doSomething
  ),
  // ...
) {
  /* ... */
}

แผนภาพต่อไปนี้แสดงทรัพยากร Dependency ระหว่าง UI และ สถานะของข้อมูลโค้ดก่อนหน้า

วันที่ UI ขึ้นอยู่กับเจ้าของสถานะตรรกะ UI และตัวยึดสถานะระดับหน้าจอ
รูปที่ 7: UI ขึ้นอยู่กับเจ้าของรัฐแต่ละราย ลูกศรหมายถึงทรัพยากร Dependency

ตัวอย่าง

ตัวอย่างของ Google ต่อไปนี้แสดงให้เห็นการใช้เจ้าของรัฐใน เลเยอร์ UI ศึกษาคู่มือเพื่อดูคำแนะนำนี้ในสถานการณ์จริง