เลเยอร์ UI

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

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

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

กรณีศึกษาเบื้องต้น

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

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

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

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

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

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

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

  • วิธีกำหนดสถานะ UI
  • โฟลว์ข้อมูลแบบทิศทางเดียว (UDF) เป็นวิธีการผลิตและจัดการ UI
  • วิธีแสดงสถานะ UI ด้วยประเภทข้อมูลที่สังเกตได้ตามหลักการ UDF
  • วิธีใช้ UI ที่ใช้สถานะ UI ที่สังเกตได้

โดยพื้นฐานที่สุดคือคำจำกัดความของสถานะ UI

กำหนดสถานะ UI

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

กล่าวคือ หากผู้ใช้เห็น UI สถานะ UI จะเป็นสิ่งที่ผู้ใช้เห็น บอกว่าพวกเขาควรเห็น เหมือนกับสองด้านของเหรียญเดียวกัน อินเทอร์เฟซ UI คือภาพ การแสดงสถานะ UI การเปลี่ยนแปลงสถานะ UI จะมีผลทันที แสดงใน UI แล้ว

วันที่ UI เป็นผลมาจากการเชื่อมโยงองค์ประกอบ UI บนหน้าจอที่มีสถานะ UI
รูปที่ 3 UI เป็นผลมาจากการเชื่อมโยงองค์ประกอบ UI บนหน้าจอที่มี สถานะ UI

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

เช่น หากแฟล็ก bookmarked ในออบเจ็กต์ NewsItemUiState จาก UI ในกรณีศึกษา ได้รับการปรับปรุงในคลาส Activity ซึ่งธงจะเป็น โดยใช้ชั้นข้อมูลเป็นแหล่งที่มาของสถานะบุ๊กมาร์ก คลาสข้อมูลที่เปลี่ยนแปลงไม่ได้มีประโยชน์มากในการป้องกัน Antipattern

รูปแบบการตั้งชื่อในคู่มือนี้

ในคู่มือนี้ คลาสสถานะ UI ได้รับการตั้งชื่อตามฟังก์ชันของ หรือหน้าจอบางส่วนที่อธิบาย ซึ่งมีรายละเอียดดังนี้

ฟังก์ชันการทำงาน + UiState

เช่น เราอาจเรียกสถานะของหน้าจอที่แสดงข่าวว่า NewsUiState และสถานะของรายการข่าวในรายการข่าวอาจเป็น NewsItemUiState

จัดการสถานะด้วยโฟลว์ข้อมูลแบบทิศทางเดียว

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

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

ส่วนนี้จะกล่าวถึงกระแสข้อมูลแบบ Unidirectional Data Flow (UDF) ซึ่งเป็นรูปแบบสถาปัตยกรรม ที่ช่วยบังคับใช้การแยกความรับผิดชอบที่มีประสิทธิภาพ

ผู้ถือรัฐ

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

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

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

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

รูปแบบที่สภาวะไหลลงและเหตุการณ์ไหลขึ้นเรียกว่า การถ่ายโอนข้อมูลแบบทิศทางเดียว (UDF) นัยยะของรูปแบบนี้สำหรับแอป ดังต่อไปนี้

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

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

วันที่
รูปที่ 5 UI ของรายการบทความในแอปกรณีศึกษา

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

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

ส่วนต่อไปนี้จะอธิบายเหตุการณ์ที่ทําให้เกิดการเปลี่ยนแปลงสถานะ และวิธีที่ประมวลผลโดยใช้ UDF

ประเภทของตรรกะ

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

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

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

หากต้องการข้อมูลเพิ่มเติมเกี่ยวกับเจ้าของรัฐและความเหมาะสมกับบริบทของ เพื่อช่วยสร้าง UI โปรดดู Jetpack Compose State

เหตุใดจึงควรใช้ UDF

UDF สร้างแบบจำลองวงจรการผลิตในสถานะดังที่แสดงในรูปที่ 4 และยังช่วยแยก สถานที่ที่รัฐเปลี่ยนแปลง สถานที่ที่เกิดการเปลี่ยนแปลง และสถานที่ที่ผู้คนใช้ การแยกนี้ช่วยให้ UI ความหมายของชื่อ: แสดงข้อมูลโดยสังเกตการเปลี่ยนแปลงสถานะ และถ่ายทอดความตั้งใจของผู้ใช้โดยส่งผ่านการเปลี่ยนแปลงเหล่านั้นไปยัง ViewModel

กล่าวคือ UDF อนุญาตการดำเนินการต่อไปนี้

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

แสดงสถานะ UI

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

ยอดดู

class NewsViewModel(...) : ViewModel() {

    val uiState: StateFlow<NewsUiState> = 
}

เขียน

class NewsViewModel(...) : ViewModel() {

    val uiState: NewsUiState = 
}

สำหรับข้อมูลเบื้องต้นเกี่ยวกับ LiveData ในฐานะผู้ถือข้อมูลที่ได้รับอนุญาตให้สังเกตพฤติกรรมผู้ใช้ได้ โปรดดูหน้านี้ Codelab สำหรับ ข้อมูลเบื้องต้นเกี่ยวกับขั้นตอนของ Kotlin โปรดดูขั้นตอนของ Kotlin ใน Android

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

วิธีสร้างสตรีม UiState ที่ใช้กันโดยทั่วไปคือการแสดงการเปลี่ยนพื้นหลัง เป็นสตรีมที่เปลี่ยนแปลงไม่ได้จาก ViewModel ตัวอย่างเช่น การแสดง MutableStateFlow<UiState> ในฐานะ StateFlow<UiState>

ยอดดู

class NewsViewModel(...) : ViewModel() {

    private val _uiState = MutableStateFlow(NewsUiState())
    val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()

    ...

}

เขียน

class NewsViewModel(...) : ViewModel() {

    var uiState by mutableStateOf(NewsUiState())
        private set

    ...
}

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

ยอดดู

class NewsViewModel(
    private val repository: NewsRepository,
    ...
) : ViewModel() {

    private val _uiState = MutableStateFlow(NewsUiState())
    val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()

    private var fetchJob: Job? = null

    fun fetchArticles(category: String) {
        fetchJob?.cancel()
        fetchJob = viewModelScope.launch {
            try {
                val newsItems = repository.newsItemsForCategory(category)
                _uiState.update {
                    it.copy(newsItems = newsItems)
                }
            } catch (ioe: IOException) {
                // Handle the error and notify the UI when appropriate.
                _uiState.update {
                    val messages = getMessagesFromThrowable(ioe)
                    it.copy(userMessages = messages)
                 }
            }
        }
    }
}

เขียน

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 มาก ค่า มีแนวโน้มมากขึ้นที่สตรีมจะปล่อยออกมาเนื่องจากฟิลด์ใดฟิลด์หนึ่ง กำลังอัปเดต เนื่องจากการดูไม่มีกลไกความแตกต่างในการทำความเข้าใจ การปล่อยมลพิษแต่ละครั้งแตกต่างกันหรือเท่ากันหรือไม่ ทำให้เกิดการอัปเดตมุมมอง ซึ่งหมายความว่าการผ่อนปรนชั่วคราวโดยใช้Flow API หรือเมธอด เช่น distinctUntilChanged() ในวันที่ LiveData อาจจำเป็นต้องทำ

ใช้สถานะ UI

หากต้องการใช้สตรีมของออบเจ็กต์ UiState ใน UI ให้ใช้เทอร์มินัล สำหรับประเภทข้อมูลที่ได้รับอนุญาตให้สังเกตพฤติกรรมผู้ใช้ได้ที่คุณใช้อยู่ ตัวอย่างเช่น สำหรับ LiveData คุณใช้เมธอด observe() และสำหรับขั้นตอน Kotlin ให้ใช้เมธอด collect() หรือรูปแบบต่างๆ ของเมธอด

เมื่อใช้ผู้ถือข้อมูลที่ได้รับอนุญาตให้สังเกตพฤติกรรมผู้ใช้ได้ใน UI ให้ตรวจสอบว่าคุณได้เลือก ของ UI มาพิจารณาด้วย ซึ่งเป็นสิ่งสำคัญเพราะ UI ไม่ควรสังเกตสถานะ UI เมื่อมุมมองไม่ได้แสดงผลต่อ ผู้ใช้ หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับหัวข้อนี้ โปรดดูที่บล็อกนี้ โพสต์ เมื่อใช้ LiveData LifecycleOwner จะดูแลวงจรโดยปริยาย ของ Google เมื่อใช้ขั้นตอน วิธีที่ดีที่สุดคือจัดการเรื่องนี้ด้วย ขอบเขตของ Coroutine และ repeatOnLifecycle API

ยอดดู

class NewsActivity : AppCompatActivity() {

    private val viewModel: NewsViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        ...

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Update UI elements
                }
            }
        }
    }
}

เขียน

@Composable
fun LatestNewsScreen(
    viewModel: NewsViewModel = viewModel()
) {
    // Show UI elements based on the viewModel.uiState
}

แสดงการดำเนินการที่กำลังดำเนินการ

วิธีง่ายๆ ในการแสดงสถานะการโหลดในคลาส UiState คือการใช้ ฟิลด์บูลีน:

data class NewsUiState(
    val isFetchingArticles: Boolean = false,
    ...
)

ค่าของแฟล็กนี้แสดงถึงการมีหรือไม่มีแถบความคืบหน้าใน UI

ยอดดู

class NewsActivity : AppCompatActivity() {

    private val viewModel: NewsViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        ...

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                // Bind the visibility of the progressBar to the state
                // of isFetchingArticles.
                viewModel.uiState
                    .map { it.isFetchingArticles }
                    .distinctUntilChanged()
                    .collect { progressBar.isVisible = it }
            }
        }
    }
}

เขียน

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

ชุดข้อความและการเกิดขึ้นพร้อมกัน

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

หาก ViewModel ดําเนินการเป็นเวลานานก็จะส่งผลต่อ การย้ายตรรกะนั้นไปยังเทรดเบื้องหลัง โครูทีน Kotlin เป็นวิธีที่ดีในการ จัดการการดำเนินงานพร้อมกัน และ Jetpack Architecture Components ก็ให้ การสนับสนุนในตัว หากต้องการดูข้อมูลเพิ่มเติมเกี่ยวกับการใช้โครูทีนในแอป Android ดูโครูทีน Kotlin ใน Android

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

การแบ่งหน้า

ไลบรารีการแบ่งหน้า คือ ใช้ใน UI ที่มีประเภทชื่อ PagingData เนื่องจากPagingData แสดงถึงและมีรายการที่เปลี่ยนแปลงได้เมื่อเวลาผ่านไป กล่าวคือ ไม่ใช่ประเภทที่เปลี่ยนแปลงไม่ได้ จึงไม่ควรแสดงอยู่ในสถานะ UI ที่เปลี่ยนแปลงไม่ได้ คุณควรแสดงโมเดลจาก ViewModel แยกต่างหากจากตัวมันเอง สตรีม ดู Codelab เกี่ยวกับ Android Paging ตัวอย่างที่เจาะจงของเรื่องนี้

ภาพเคลื่อนไหว

เพื่อให้การเปลี่ยนการนำทางระดับบนสุดราบรื่นและลื่นไหล คุณอาจต้องทำดังนี้ ต้องการรอให้หน้าจอที่ 2 โหลดข้อมูลก่อนที่จะเริ่มภาพเคลื่อนไหว เฟรมเวิร์ก Android View มีฮุกเพื่อชะลอการเปลี่ยนระหว่างส่วนย่อย ปลายทางที่มี postponeEnterTransition() และ startPostponedEnterTransition() API API เหล่านี้ช่วยให้มั่นใจได้ ว่าองค์ประกอบ UI ใน หน้าจอ (โดยปกติจะเป็นรูปภาพที่ดึงมาจากเครือข่าย) พร้อมที่จะแสดง ก่อนที่ UI จะทำภาพเคลื่อนไหวเมื่อเปลี่ยนเป็นหน้าจอนั้น สำหรับรายละเอียดเพิ่มเติมและ ดูข้อกำหนดเฉพาะในการใช้งานได้ที่ Android Motion ตัวอย่าง

ตัวอย่าง

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