บทบาทของ UI คือการแสดงข้อมูลแอปพลิเคชันบนหน้าจอ นอกจากนี้ UI ยังเป็นจุดโต้ตอบหลักของผู้ใช้ด้วย เมื่อใดก็ตามที่ข้อมูลเปลี่ยนแปลง ไม่ว่าจะเกิดจากการโต้ตอบของผู้ใช้ (เช่น การกดปุ่ม) หรืออินพุตภายนอก (เช่น การตอบกลับของเครือข่าย) UI จะอัปเดตเพื่อให้แสดงการเปลี่ยนแปลงเหล่านั้น โดยพื้นฐานแล้ว UI คือภาพที่แสดงสถานะของแอปพลิเคชันตามที่ ดึงมาจากชั้นข้อมูล
อย่างไรก็ตาม ข้อมูลแอปพลิเคชันที่คุณได้รับจากชั้นข้อมูลมักจะอยู่ใน รูปแบบที่แตกต่างจากข้อมูลที่คุณต้องการแสดง เช่น คุณอาจต้องการข้อมูลเพียงบางส่วนสำหรับ UI หรืออาจต้องผสานแหล่งข้อมูล 2 แหล่งที่แตกต่างกันเพื่อแสดงข้อมูลที่เกี่ยวข้องกับผู้ใช้ ไม่ว่าคุณจะใช้ตรรกะใดก็ตาม คุณต้องส่งข้อมูลทั้งหมดที่ UI ต้องการเพื่อแสดงผลอย่างเต็มรูปแบบ เลเยอร์ UI คือไปป์ไลน์ที่แปลง การเปลี่ยนแปลงข้อมูลแอปพลิเคชันเป็นรูปแบบที่ UI สามารถนำเสนอได้ แล้วจึงแสดง
กรณีศึกษาเบื้องต้น
ลองพิจารณาแอปที่ดึงบทความข่าวให้ผู้ใช้อ่าน แอปมี หน้าจอบทความที่แสดงบทความที่พร้อมให้อ่าน และยังช่วยให้ ผู้ใช้ที่ลงชื่อเข้าใช้สามารถบุ๊กมาร์กบทความที่โดดเด่นได้อีกด้วย เนื่องจากอาจมีบทความจำนวนมากในเวลาใดก็ตาม ผู้อ่านจึงต้องสามารถเรียกดูบทความตามหมวดหมู่ได้ โดยสรุปแล้ว แอปนี้ช่วยให้ผู้ใช้ทำสิ่งต่อไปนี้ได้
- ดูบทความที่พร้อมให้อ่าน
- เรียกดูบทความตามหมวดหมู่
- ลงชื่อเข้าใช้และบุ๊กมาร์กบทความบางรายการ
- เข้าถึงฟีเจอร์พรีเมียมบางรายการหากมีสิทธิ์
ส่วนต่อไปนี้จะใช้ตัวอย่างนี้เป็นกรณีศึกษาเพื่อแนะนำ หลักการของโฟลว์ข้อมูลแบบทิศทางเดียว รวมถึงแสดงปัญหา ที่หลักการเหล่านี้ช่วยแก้ปัญหาในบริบทของสถาปัตยกรรมแอปสำหรับเลเยอร์ UI
สถาปัตยกรรมเลเยอร์ UI
คำว่า UI หมายถึงองค์ประกอบ UI เช่น คอนเทนเนอร์และฟังก์ชันที่ใช้ร่วมกันได้ ซึ่งแสดงข้อมูล Jetpack Compose เป็นชุดเครื่องมือที่แนะนำสำหรับการสร้าง UI ของ Android เนื่องจากบทบาทของชั้นข้อมูลคือการจัดเก็บ จัดการ และให้สิทธิ์เข้าถึงข้อมูลแอป ชั้น UI จึงต้องทำตามขั้นตอนต่อไปนี้
- ใช้ข้อมูลแอปและเปลี่ยนเป็นข้อมูลที่ UI แสดงผลได้ง่าย
- ใช้ข้อมูลที่แสดงผลได้ใน UI และแปลงเป็นองค์ประกอบ UI เพื่อนำเสนอ ต่อผู้ใช้
- ใช้เหตุการณ์อินพุตของข้อมูลจากผู้ใช้จากองค์ประกอบ UI ที่ประกอบขึ้นเหล่านั้น และแสดงผลลัพธ์ในข้อมูล UI ตามที่จำเป็น
- ทำขั้นตอนที่ 1-3 ซ้ำตามระยะเวลาที่จำเป็น
ส่วนที่เหลือของคู่มือนี้จะแสดงวิธีกำหนดค่าเลเยอร์ UI ที่ทำตาม ขั้นตอนเหล่านี้ โดยเฉพาะอย่างยิ่ง คู่มือนี้จะครอบคลุมงานและแนวคิดต่อไปนี้
- วิธีกำหนดสถานะ UI
- โฟลว์ข้อมูลแบบทางเดียว (UDF) เป็นวิธีการสร้างและจัดการสถานะ UI
- วิธีแสดงสถานะ UI ด้วยประเภทข้อมูลที่สังเกตได้ตามหลักการ UDF
- วิธีใช้ UI ที่ใช้สถานะ UI ที่สังเกตได้
พื้นฐานที่สุดในบรรดาแนวคิดเหล่านี้คือคำจำกัดความของสถานะ UI
กำหนดสถานะ UI
ในกรณีศึกษาที่กล่าวถึงก่อนหน้านี้ UI จะแสดงรายการบทความพร้อมข้อมูลเมตาบางอย่างของแต่ละบทความ ข้อมูลนี้ ที่แอปแสดงต่อผู้ใช้คือสถานะ UI
กล่าวคือ หาก UI คือสิ่งที่ผู้ใช้เห็น สถานะ UI คือสิ่งที่แอป บอกว่าผู้ใช้ควรเห็น UI เป็นตัวแทนภาพของสถานะ UI เช่นเดียวกับเหรียญที่มี 2 ด้าน การเปลี่ยนแปลงสถานะ UI จะแสดงใน UI ทันที
พิจารณากรณีศึกษา: เพื่อให้เป็นไปตามข้อกำหนดของแอปข่าว
ข้อมูลที่จำเป็นในการแสดงผล UI อย่างเต็มรูปแบบสามารถแคปซูลไว้ใน
NewsUiState คลาสข้อมูลที่กำหนดไว้ดังนี้
data class NewsUiState( val isSignedIn: Boolean = false, val isPremium: Boolean = false, val newsItems: List<NewsItemUiState> = listOf(), val userMessages: List<Message> = listOf() ) data class NewsItemUiState( val title: String, val body: String, val bookmarked: Boolean = false, // ... )
ดูข้อมูลเพิ่มเติมเกี่ยวกับสถานะ UI ได้ที่สถานะและ Jetpack Compose
ความคงทน
คำจำกัดความสถานะ UI ในตัวอย่างก่อนหน้าจะเปลี่ยนแปลงไม่ได้ ประโยชน์หลักของ สิ่งนี้คือออบเจ็กต์ที่ไม่เปลี่ยนแปลงจะรับประกันเกี่ยวกับสถานะของ แอปพลิเคชัน ณ เวลาใดเวลาหนึ่ง ซึ่งจะช่วยให้ UI มุ่งเน้นไปที่บทบาทหลักของตนเอง นั่นคือการอ่านสถานะและอัปเดตองค์ประกอบ UI ตามนั้น ห้ามแก้ไขสถานะ UI ใน UI โดยตรง เว้นแต่ UI จะเป็นแหล่งข้อมูลเดียวของข้อมูล การละเมิดหลักการนี้จะทำให้มีแหล่งข้อมูลความจริงหลายแหล่งสำหรับข้อมูลเดียวกัน ซึ่งจะนำไปสู่ความไม่สอดคล้องกันของข้อมูลและข้อบกพร่องเล็กๆ น้อยๆ
เช่น ลองดูกรณีศึกษาที่กล่าวถึงก่อนหน้านี้
หากมีการอัปเดตbookmarked แฟล็กในออบเจ็กต์ NewsItemUiState จาก UI
state ในคลาส Activity แฟล็กนั้น
จะแข่งขันกับ Data Layer ในฐานะแหล่งที่มาของสถานะบุ๊กมาร์กของบทความ คลาสข้อมูลที่ไม่เปลี่ยนแปลงมีประโยชน์มากในการป้องกันความไม่สอดคล้องประเภทนี้
รูปแบบการตั้งชื่อในคู่มือนี้
ในคู่มือนี้ คลาสสถานะ UI จะตั้งชื่อตามฟังก์ชันการทำงานของ หน้าจอหรือส่วนของหน้าจอที่อธิบาย โดยมีรูปแบบดังนี้
functionality + UiState
เช่น สถานะของหน้าจอที่แสดงข่าวอาจเรียกว่า
NewsUiState และสถานะของรายการข่าวในรายการข่าวอาจเป็น
NewsItemUiState
จัดการสถานะด้วยการไหลของข้อมูลแบบทิศทางเดียว
ส่วนก่อนหน้านี้ได้อธิบายว่าสถานะ UI คือสแนปชอตแบบเปลี่ยนแปลงไม่ได้ของ รายละเอียดที่จำเป็นเพื่อให้ UI แสดงผล อย่างไรก็ตาม ลักษณะของข้อมูลในแอปที่เปลี่ยนแปลงอยู่ตลอดเวลาหมายความว่าสถานะอาจเปลี่ยนแปลงเมื่อเวลาผ่านไป ซึ่งอาจเกิดจากการโต้ตอบของผู้ใช้หรือเหตุการณ์อื่นๆ ที่แก้ไขข้อมูลพื้นฐานที่ใช้ในการแสดงข้อมูลในแอป
การโต้ตอบเหล่านี้จะได้รับประโยชน์จากตัวกลางในการประมวลผล โดยกำหนดตรรกะที่จะใช้กับแต่ละเหตุการณ์ และเปลี่ยนแหล่งข้อมูลสำรองเพื่อสร้างสถานะ UI แม้ว่าการโต้ตอบและตรรกะของการโต้ตอบเหล่านี้จะอยู่ใน UI เองได้ แต่การทำเช่นนี้อาจทำให้ UI ทำงานได้ไม่ดีนักอย่างรวดเร็วเนื่องจาก UI มีหน้าที่รับผิดชอบมากเกินไป นอกจากนี้ ยังอาจส่งผลต่อความสามารถในการทดสอบ เนื่องจากโค้ดที่ได้จะมีการเชื่อมโยงอย่างแน่นหนา เว้นแต่ว่าสถานะ UI จะเรียบง่ายมาก ให้ตรวจสอบว่าความรับผิดชอบเดียวของ UI คือ การใช้และแสดงสถานะ UI
ส่วนนี้จะอธิบายถึงการไหลของข้อมูลแบบทิศทางเดียว (UDF) ซึ่งเป็นรูปแบบสถาปัตยกรรม ที่ช่วยบังคับใช้การแยกความรับผิดชอบที่เหมาะสมนี้
ตัวเก็บสถานะ
State Holder คือคลาสที่มีหน้าที่รับผิดชอบในการสร้างสถานะ UI และตรรกะที่จำเป็นในการสร้างสถานะดังกล่าว ผู้ถือครองสถานะมีหลายขนาดขึ้นอยู่กับขอบเขตขององค์ประกอบ UI ที่เกี่ยวข้องซึ่งผู้ถือครองสถานะจัดการ โดยมีตั้งแต่วิดเจ็ตเดียว เช่น แถบแอปด้านล่าง ไปจนถึงทั้งหน้าจอหรือปลายทางการนำทาง
ในกรณีหลัง การติดตั้งใช้งานทั่วไปคืออินสแตนซ์ของ ViewModel แม้ว่าคลาสธรรมดาอาจเพียงพอแล้วก็ตาม ทั้งนี้ขึ้นอยู่กับข้อกำหนดของแอปพลิเคชัน ตัวอย่างเช่น แอปข่าวจากกรณีศึกษาใช้คลาส NewsViewModel เป็นตัวยึดสถานะเพื่อสร้างสถานะ UI สำหรับหน้าจอที่แสดงในส่วนนั้น
การสร้างโมเดลความสัมพันธ์แบบพึ่งพาอาศัยกันระหว่าง UI กับผู้ผลิตสถานะทำได้หลายวิธี
อย่างไรก็ตาม เนื่องจากโดยส่วนใหญ่แล้วการโต้ตอบระหว่าง UI กับViewModel
คลาสของ UI สามารถเข้าใจได้ว่าเป็นการป้อนเหตุการณ์และเอาต์พุตสถานะที่ตามมา
ความสัมพันธ์จึงแสดงได้ดังที่แสดงในไดอะแกรมต่อไปนี้
รูปแบบที่สถานะไหลลงและเหตุการณ์ไหลขึ้นเรียกว่า การไหลของข้อมูลแบบทิศทางเดียว (UDF) ผลกระทบของรูปแบบนี้ต่อสถาปัตยกรรมของแอป มีดังนี้
- ViewModel จะเก็บและแสดงสถานะเพื่อให้ UI ใช้ สถานะ UI คือข้อมูลแอปพลิเคชันที่ ViewModel เปลี่ยนรูปแบบ
- UI จะแจ้งให้ ViewModel ทราบถึงเหตุการณ์ของผู้ใช้
- ViewModel จะจัดการการกระทําของผู้ใช้และอัปเดตสถานะ
- ระบบจะส่งสถานะที่อัปเดตแล้วกลับไปยัง UI เพื่อแสดงผล
- เราจะทำซ้ำขั้นตอนข้างต้นสำหรับเหตุการณ์ใดๆ ที่ทำให้เกิดการเปลี่ยนแปลงสถานะ
สำหรับปลายทางหรือหน้าจอการนำทาง ViewModel จะทำงานร่วมกับที่เก็บหรือคลาส Use Case เพื่อรับข้อมูลและแปลงเป็นสถานะ UI พร้อมทั้งรวมผลกระทบของเหตุการณ์ที่อาจทำให้เกิดการเปลี่ยนแปลงสถานะ กรณีศึกษาที่กล่าวถึงก่อนหน้านี้มีรายการบทความ ซึ่งแต่ละบทความมีชื่อ คำอธิบาย แหล่งที่มา ชื่อผู้เขียน วันที่ตีพิมพ์ และระบุว่ามีการบุ๊กมาร์กหรือไม่ UI สำหรับแต่ละรายการบทความจะมีลักษณะดังนี้
ผู้ใช้ขอเพิ่มบทความลงในบุ๊กมาร์กเป็นตัวอย่างของเหตุการณ์ที่อาจ ทําให้เกิดการเปลี่ยนแปลงสถานะ ในฐานะผู้ผลิตสถานะ ViewModel มีหน้าที่กำหนดตรรกะทั้งหมดที่จำเป็นในการป้อนข้อมูลในช่องทั้งหมด ในสถานะ UI และประมวลผลเหตุการณ์ที่จำเป็นเพื่อให้ UI แสดงผลได้อย่างสมบูรณ์
ส่วนต่อไปนี้จะดูรายละเอียดเหตุการณ์ที่ทําให้เกิดการเปลี่ยนแปลงสถานะ และวิธีประมวลผลเหตุการณ์โดยใช้ UDF
ประเภทของตรรกะ
การบุ๊กมาร์กบทความเป็นตัวอย่างของตรรกะทางธุรกิจ เนื่องจากเป็นการเพิ่มคุณค่า ให้กับแอปของคุณ ดูข้อมูลเพิ่มเติมได้ที่หน้าเลเยอร์ข้อมูล อย่างไรก็ตาม มีตรรกะหลายประเภท ที่ควรระบุ
- ตรรกะทางธุรกิจคือการนำข้อกำหนดของผลิตภัณฑ์ไปใช้กับข้อมูลแอป ดังที่ได้กล่าวไปแล้ว ตัวอย่างหนึ่งคือการบุ๊กมาร์กบทความใน แอปกรณีศึกษา โดยปกติแล้วตรรกะทางธุรกิจจะอยู่ในโดเมนหรือชั้นข้อมูล แต่จะไม่อยู่ในเลเยอร์ UI
- ตรรกะลักษณะการทำงานของ UI หรือตรรกะ UI คือวิธีแสดงการเปลี่ยนแปลงสถานะบนหน้าจอ
ตัวอย่างเช่น การรับข้อความที่ถูกต้องเพื่อแสดงบนหน้าจอ
โดยใช้
Resourcesของ Android การไปยังหน้าจอหนึ่งๆ เมื่อผู้ใช้คลิกปุ่ม หรือ การแสดงข้อความของผู้ใช้บนหน้าจอโดยใช้ Toast หรือ Snackbar
เก็บตรรกะของ UI ไว้ใน UI ไม่ใช่ใน ViewModel โดยเฉพาะอย่างยิ่งเมื่อเกี่ยวข้องกับ
ประเภท UI เช่น Context
หาก UI มีความซับซ้อนมากขึ้นและคุณต้องการมอบหมายตรรกะ UI ให้กับคลาสอื่นเพื่อเพิ่มความสามารถในการทดสอบและการแยกข้อกังวล คุณสามารถสร้างคลาสอย่างง่ายเพื่อใช้เป็นตัวเก็บสถานะ คลาสแบบง่ายที่สร้างใน UI
สามารถใช้ทรัพยากร Dependency ของ Android SDK ได้เนื่องจากเป็นไปตามวงจรของ UI
ออบเจ็กต์ ViewModel มีอายุการใช้งานยาวนานกว่า
ดูข้อมูลเพิ่มเติมเกี่ยวกับตัวยึดสถานะและวิธีที่ตัวยึดสถานะเข้ากับบริบทของ การช่วยสร้าง UI ได้ที่คู่มือสถานะ Jetpack Compose
เหตุผลที่ควรใช้ UDF
UDF สร้างแบบจำลองวงจรการผลิตสถานะตามที่แสดงในรูปที่ 4 นอกจากนี้ยังแยก ตำแหน่งที่การเปลี่ยนแปลงสถานะเกิดขึ้น ตำแหน่งที่การเปลี่ยนแปลงสถานะได้รับการแปลง และตำแหน่งที่การเปลี่ยนแปลงสถานะได้รับการใช้ในที่สุด การแยกนี้ช่วยให้ UI ทำสิ่งที่ชื่อของมันบอกได้อย่างตรงไปตรงมา นั่นคือแสดงข้อมูลโดยสังเกตการเปลี่ยนแปลงสถานะ และถ่ายทอดความตั้งใจของผู้ใช้โดยส่งต่อการเปลี่ยนแปลงเหล่านั้นไปยัง ViewModel
กล่าวคือ UDF อนุญาตให้ทำสิ่งต่อไปนี้
- ความสอดคล้องของข้อมูล UI มีแหล่งข้อมูลที่เชื่อถือได้เพียงแหล่งเดียว
- การทดสอบได้ แหล่งที่มาของสถานะจะแยกกัน จึงทดสอบได้ โดยไม่ขึ้นอยู่กับ UI
- การบำรุงรักษา การเปลี่ยนแปลงสถานะเป็นไปตามรูปแบบที่กำหนดไว้อย่างดี โดยการเปลี่ยนแปลงเป็นผลมาจากทั้งเหตุการณ์ของผู้ใช้และแหล่งข้อมูลที่ผู้ใช้ดึงข้อมูลมา จาก
แสดงสถานะ UI
หลังจากกำหนดสถานะ UI และพิจารณาว่าจะจัดการการสร้างสถานะดังกล่าวอย่างไร ขั้นตอนถัดไปคือการนำเสนอสถานะที่สร้างขึ้นต่อ UI
เมื่อใช้ UDF เพื่อจัดการการผลิตสถานะ คุณสามารถพิจารณาสถานะที่สร้างขึ้นให้เป็นสตรีมได้ กล่าวคือ สถานะหลายเวอร์ชันจะสร้างขึ้นเมื่อเวลาผ่านไป
เปิดเผยสถานะ UI ในที่เก็บข้อมูลที่สังเกตได้ เช่น StateFlow ซึ่งช่วยให้ UI ตอบสนองต่อการเปลี่ยนแปลง
ที่เกิดขึ้นในสถานะได้โดยไม่ต้องดึงข้อมูลจาก ViewModel ด้วยตนเอง
โดยตรง นอกจากนี้ยังช่วยให้แคชสถานะ UI เวอร์ชันล่าสุดอยู่เสมอ ซึ่งเป็นประโยชน์สำหรับการคืนค่าสถานะอย่างรวดเร็วหลังจากการเปลี่ยนแปลงการกำหนดค่า
class NewsViewModel( // ... ) : ViewModel() { val uiState: NewsUiState = /* ... */ }
ดูข้อมูลเบื้องต้นเกี่ยวกับ Kotlin Flow ได้ที่ Kotlin Flow ใน Android
ดูวิธีใช้ StateFlow เป็นที่เก็บข้อมูลที่สังเกตได้
ใน Codelab สถานะขั้นสูงและผลสะท้อนใน Jetpack Compose
ในกรณีที่ข้อมูลที่แสดงต่อ UI ค่อนข้างเรียบง่าย การห่อหุ้มข้อมูลในประเภทสถานะ UI มักจะคุ้มค่า เนื่องจากจะแสดงความสัมพันธ์ระหว่างการปล่อยสถานะของตัวเก็บสถานะกับหน้าจอหรือองค์ประกอบ UI ที่เชื่อมโยง เมื่อองค์ประกอบ UI มีความซับซ้อนมากขึ้น คุณก็เพิ่มองค์ประกอบลงในคำจำกัดความของสถานะ UI ได้โดยตรง เพื่อให้รองรับข้อมูลเพิ่มเติมที่จำเป็นต่อการแสดงผลองค์ประกอบ UI
วิธีทั่วไปในการสร้างสตรีมของ UiState คือการเปิดเผยmutableStateOf
พร็อพเพอร์ตี้ที่มี private set โดยรักษาสถานะที่เปลี่ยนแปลงได้ภายใน ViewModel
แต่เป็นแบบอ่านอย่างเดียวสำหรับ UI
class NewsViewModel( // ... ) : ViewModel() { var uiState by mutableStateOf(NewsUiState()) private set // ... }
จากนั้น ViewModel จะแสดงเมธอดที่เปลี่ยนสถานะภายใน
และเผยแพร่การอัปเดตเพื่อให้ UI ใช้ ตัวอย่างเช่น กรณีที่คุณต้องดำเนินการแบบไม่พร้อมกัน
คุณสามารถเปิดใช้โครูทีนโดยใช้ viewModelScope
จากนั้นอัปเดตสถานะที่เปลี่ยนแปลงได้เมื่อดำเนินการเสร็จสมบูรณ์
class NewsViewModel( private val repository: NewsRepository, // ... ) : ViewModel() { var uiState by mutableStateOf(NewsUiState()) private set private var fetchJob: Job? = null fun fetchArticles(category: String) { fetchJob?.cancel() fetchJob = viewModelScope.launch { try { val newsItems = repository.newsItemsForCategory(category) uiState = uiState.copy(newsItems = newsItems) } catch (ioe: IOException) { // Handle the error and notify the UI when appropriate. val messages = getMessagesFromThrowable(ioe) uiState = uiState.copy(userMessages = messages) } } } }
ในตัวอย่างก่อนหน้า คลาส NewsViewModel พยายามดึงบทความ
สำหรับหมวดหมู่หนึ่งๆ แล้วแสดงผลลัพธ์ของความพยายามนั้น ไม่ว่าจะเป็น
สำเร็จหรือ
ล้มเหลว ในสถานะ UI ซึ่ง UI สามารถตอบสนองต่อสถานะดังกล่าวได้อย่างเหมาะสม
ดูข้อมูลเพิ่มเติมเกี่ยวกับการจัดการข้อผิดพลาดได้ที่ส่วนแสดงข้อผิดพลาดบนหน้าจอ
ข้อพิจารณาเพิ่มเติม
นอกเหนือจากคำแนะนำก่อนหน้านี้ โปรดพิจารณาสิ่งต่อไปนี้เมื่อแสดงสถานะ UI
ใช้ออบเจ็กต์สถานะ UI เดียวเพื่อจัดการสถานะที่ เกี่ยวข้องซึ่งกันและกัน ซึ่งจะช่วยลดความไม่สอดคล้องกันและทำให้โค้ดเข้าใจได้ง่ายขึ้น หากแสดงรายการข่าวและจำนวนบุ๊กมาร์กใน 2 สตรีมที่แตกต่างกัน คุณอาจพบว่าสตรีมหนึ่งได้รับการอัปเดต แต่อีกสตรีมหนึ่งไม่ได้รับการอัปเดต เมื่อใช้สตรีมเดียว ระบบจะอัปเดตทั้ง 2 องค์ประกอบให้เป็นเวอร์ชันล่าสุด นอกจากนี้ ตรรกะทางธุรกิจบางอย่างอาจต้องใช้แหล่งที่มาหลายแหล่งร่วมกัน ตัวอย่างเช่น คุณอาจต้องแสดงปุ่มบุ๊กมาร์กเฉพาะในกรณีที่ ผู้ใช้ลงชื่อเข้าใช้ และผู้ใช้รายนั้นเป็นสมาชิกของบริการข่าวสารแบบพรีเมียม คุณกำหนดคลาสสถานะ UI ได้ดังนี้
data class NewsUiState( val isSignedIn: Boolean = false, val isPremium: Boolean = false, val newsItems: List<NewsItemUiState> = listOf() ) val NewsUiState.canBookmarkNews: Boolean get() = isSignedIn && isPremium
ในการประกาศนี้ ระดับการมองเห็นของปุ่มบุ๊กมาร์กเป็นพร็อพเพอร์ตี้ที่ได้มาจากพร็อพเพอร์ตี้อื่นๆ 2 รายการ เมื่อตรรกะทางธุรกิจมีความซับซ้อนมากขึ้น การมี
UiStateคลาสเดียวที่พร็อพเพอร์ตี้ทั้งหมดพร้อมใช้งานทันที จะมีความสำคัญมากขึ้นสถานะ UI: สตรีมเดียวหรือหลายสตรีม หลักการสำคัญในการเลือกระหว่างการเปิดเผยสถานะ UI ในสตรีมเดียวหรือหลายสตรีมคือความสัมพันธ์ระหว่างรายการที่ปล่อยออกมา ข้อได้เปรียบที่สำคัญที่สุดของการเปิดเผยข้อมูลแบบสตรีมเดียวคือความสะดวกและ ความสอดคล้องของข้อมูล โดยผู้บริโภคของรัฐจะมีข้อมูลล่าสุด พร้อมใช้งานเสมอ อย่างไรก็ตาม ในบางกรณี การแยกสตรีมสถานะจาก ViewModel อาจเหมาะสมกว่า
ประเภทข้อมูลที่ไม่เกี่ยวข้อง: สถานะบางอย่างที่จำเป็นต่อการแสดงผล UI อาจ ไม่ขึ้นต่อกันเลย ในกรณีเช่นนี้ ต้นทุนของการ รวมสถานะที่แตกต่างกันเหล่านี้อาจมากกว่าประโยชน์ที่ได้รับ โดยเฉพาะหากรัฐใดรัฐหนึ่งอัปเดตบ่อยกว่าอีกรัฐหนึ่ง
UiStateการเปรียบเทียบ: ยิ่งมีฟิลด์ในออบเจ็กต์UiStateมากเท่าใด สตรีมก็จะยิ่งมีแนวโน้มที่จะปล่อยออกมาเป็นผลจากการอัปเดตฟิลด์ใดฟิลด์หนึ่ง มากขึ้นเท่านั้น เนื่องจากองค์ประกอบ UI ไม่มีกลไกการเปรียบเทียบเพื่อทำความเข้าใจว่าการปล่อยข้อมูลที่ต่อเนื่องนั้นแตกต่างกันหรือเหมือนกัน การปล่อยข้อมูลทุกครั้งจึงทำให้องค์ประกอบ UI ได้รับการอัปเดต ซึ่งหมายความว่าอาจต้องใช้การลดผลกระทบโดยใช้Flowเมธอด API เช่นdistinctUntilChanged()
ดูข้อมูลเพิ่มเติมเกี่ยวกับการแสดงผลและสถานะ UI ได้ที่วงจรของ Composable
ใช้สถานะ UI
หากต้องการใช้สตรีมของออบเจ็กต์ UiState ใน UI ให้ใช้ตัวดำเนินการเทอร์มินัลสำหรับประเภทข้อมูลที่ได้รับอนุญาตให้สังเกตพฤติกรรมผู้ใช้ได้ที่คุณใช้ เช่น
สำหรับโฟลว์ Kotlin ให้ใช้วิธี collect() หรือรูปแบบต่างๆ ของวิธีนี้
เมื่อใช้ตัวยึดข้อมูลที่สังเกตได้ใน UI โปรดพิจารณา
วงจรของ UI อย่าทำให้ UI สังเกตสถานะ UI เมื่อไม่ได้แสดง Composable ต่อผู้ใช้
ดูข้อมูลเพิ่มเติมเกี่ยวกับหัวข้อนี้ได้ในบล็อกโพสต์นี้ เมื่อใช้โฟลว์ คุณควรจัดการข้อกังวลเกี่ยวกับวงจรด้วยขอบเขตของโครูทีนที่เหมาะสมและ collectAsStateWithLifecycle API ดังนี้
@Composable private fun ConversationScreen( conversationViewModel: ConversationViewModel = viewModel() ) { val messages by conversationViewModel.messages.collectAsStateWithLifecycle() ConversationScreen( messages = messages, onSendMessage = { message: Message -> conversationViewModel.sendMessage(message) } ) } @Composable private fun ConversationScreen( messages: List<Message>, onSendMessage: (Message) -> Unit ) { MessagesList(messages, onSendMessage) /* ... */ }
แสดงการดำเนินการที่อยู่ระหว่างดำเนินการ
วิธีง่ายๆ ในการแสดงสถานะการโหลดในคลาส UiState คือการใช้ฟิลด์บูลีน
data class NewsUiState( val isFetchingArticles: Boolean = false, // ... )
ค่าของแฟล็กนี้แสดงถึงการมีหรือไม่มีแถบความคืบหน้าใน UI
@Composable fun LatestNewsScreen( modifier: Modifier = Modifier, viewModel: NewsViewModel = viewModel() ) { Box(modifier.fillMaxSize()) { if (viewModel.uiState.isFetchingArticles) { CircularProgressIndicator(Modifier.align(Alignment.Center)) } // Add other UI elements. For example, the list. } }
แสดงข้อผิดพลาดบนหน้าจอ
การแสดงข้อผิดพลาดใน UI จะคล้ายกับการแสดงการดำเนินการที่กำลังดำเนินการ เนื่องจาก ทั้ง 2 อย่างแสดงได้ง่ายด้วยค่าบูลีนที่ระบุว่ามีหรือไม่มี อย่างไรก็ตาม ข้อผิดพลาดอาจมีข้อความที่เกี่ยวข้องเพื่อส่งต่อกลับ ไปยังผู้ใช้ หรือการดำเนินการที่เชื่อมโยงกับข้อผิดพลาดนั้นซึ่งจะลองดำเนินการที่ล้มเหลวอีกครั้ง ดังนั้นในขณะที่การดำเนินการที่กำลังดำเนินการอยู่กำลังโหลดหรือไม่โหลด คุณอาจต้องสร้างรูปแบบสถานะข้อผิดพลาดด้วยคลาสข้อมูลที่โฮสต์ข้อมูลเมตาที่เหมาะสมกับบริบทของข้อผิดพลาด
ลองดูตัวอย่างก่อนหน้าซึ่งแสดงแถบความคืบหน้าขณะดึงข้อมูลบทความ หากการดำเนินการนี้ทำให้เกิดข้อผิดพลาด คุณอาจต้องแสดงข้อความอย่างน้อย 1 รายการแก่ผู้ใช้เพื่อแจ้งรายละเอียดเกี่ยวกับสิ่งที่ผิดพลาด
data class Message(val id: Long, val message: String) data class NewsUiState( val userMessages: List<Message> = listOf(), // ... )
จากนั้นคุณอาจแสดงข้อความแสดงข้อผิดพลาดต่อผู้ใช้ในรูปแบบขององค์ประกอบ UI เช่น แถบแสดงข้อความ ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีสร้างและใช้เหตุการณ์ UI ได้ที่เหตุการณ์ UI
การใช้ชุดข้อความและการทำงานพร้อมกัน
ตรวจสอบว่างานทั้งหมดที่ดำเนินการใน ViewModel เป็นเทรดหลักที่ปลอดภัย ซึ่งเรียกใช้จากเทรดหลักได้อย่างปลอดภัย เลเยอร์ข้อมูลและโดเมนมีหน้าที่ ย้ายงานไปยังเธรดอื่น
หาก ViewModel ดำเนินการที่ใช้เวลานาน ก็จะมีหน้าที่ ย้ายตรรกะนั้นไปยังเธรดเบื้องหลังด้วย โครูทีน Kotlin เป็นวิธีที่ยอดเยี่ยมในการ จัดการการดำเนินการพร้อมกัน และคอมโพเนนต์สถาปัตยกรรม Jetpack มี การรองรับโครูทีนในตัว ดูข้อมูลเพิ่มเติมเกี่ยวกับการใช้ Coroutine ในแอป Android ได้ที่Kotlin Coroutine ใน Android
การนำทาง
การเปลี่ยนแปลงในการนำทางของแอปมักเกิดจากการปล่อยก๊าซที่คล้ายกับเหตุการณ์ เช่น
หลังจากSignInViewModel ชั้นเรียนลงชื่อเข้าใช้แล้ว UiState อาจมี
ฟิลด์ isSignedIn ตั้งค่าเป็น true ใช้ทริกเกอร์เช่นนี้เหมือนกับที่กล่าวถึงในส่วนสถานะ UI ของการใช้ก่อนหน้านี้
แต่เลื่อนการติดตั้งใช้งานการใช้ไปยังคอมโพเนนต์การนำทาง
ดูข้อมูลเพิ่มเติมเกี่ยวกับการไปยังส่วนต่างๆ ของ UI ได้ที่การนำทาง 3
เพจจิ้ง
ไลบรารีการแบ่งหน้าจะ
ใช้ใน UI ด้วยประเภทที่เรียกว่า PagingData เนื่องจาก PagingData
แสดงและมีรายการที่อาจเปลี่ยนแปลงไปตามกาลเวลา กล่าวคือ
ไม่ใช่ประเภทที่เปลี่ยนแปลงไม่ได้ จึงไม่ควรแสดงในสถานะ UI ที่เปลี่ยนแปลงไม่ได้
แต่ให้แสดงจาก ViewModel แยกกันในสตรีมของตัวเองแทน
ตัวอย่างต่อไปนี้แสดง Compose API ของ Paging Library
@Composable fun MyScreen(flow: Flow<PagingData<String>>) { val lazyPagingItems = flow.collectAsLazyPagingItems() LazyColumn { items( lazyPagingItems.itemCount, key = lazyPagingItems.itemKey { it } ) { index -> val item = lazyPagingItems[index] Text("Item is $item") } } }
ภาพเคลื่อนไหว
หากต้องการให้การเปลี่ยนเส้นทางระดับบนสุดเป็นไปอย่างราบรื่น คุณอาจต้องรอให้หน้าจอที่ 2 โหลดข้อมูลก่อนเริ่ม ภาพเคลื่อนไหว
ดูข้อมูลเพิ่มเติมเกี่ยวกับการเปลี่ยนผ่านการนำทางได้ที่การนำทาง 3 และ การเปลี่ยนผ่านขององค์ประกอบที่ใช้ร่วมกันใน Compose
แหล่งข้อมูลเพิ่มเติม
ดูเนื้อหา
ตัวอย่าง
ตัวอย่างต่อไปนี้ของ Google แสดงการใช้เลเยอร์ UI ไปสำรวจเพื่อดูคำแนะนำนี้ในทางปฏิบัติ
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- การสร้างสถานะ UI
- ตัวเก็บสถานะและสถานะ UI {:#mad-arch}
- คำแนะนำเกี่ยวกับสถาปัตยกรรมของแอป