นอกเหนือจากการสร้าง UI ด้วย UI Toolkit ที่แอนดรอยด์มีให้ใช้ หรือใช้งาน Framwork API เพื่อให้อุปกรณ์แอนดรอยด์ทำงานตามที่ต้องการแล้ว สิ่งหนึ่งที่ขาดไปไม่ได้และสำคัญมากสำหรับการทำงานของแอปบนแอนดรอยด์ทุกตัว ก็คือการจัดการกับข้อมูล (Data) ที่อยู่ภายในแอปนั่นเอง
โดยข้อมูลที่ว่านี้จะแบ่งออกตามลักษณะการใช้งาน ไม่ว่าจะเป็น
- UI State – ข้อมูลที่เกี่ยวข้องกับการทำงานของ UI ไม่ว่าจะเป็นข้อมูลที่นำไปแสดงผลหรือข้อมูลที่ได้จากการใช้งานของผู้ใช้
- Application Data ใน ณ ที่นี้จะหมายถึงข้อมูลที่ไม่ได้เกี่ยวกับการทำงานของ UI โดยตรง แต่เป็นข้อมูลที่จำเป็นต้องใช้สำหรับการทำงานภายในแอป
และเมื่อใช้ App Architecture ยอดนิยมสำหรับนักพัฒนาแอนดรอยด์อย่าง MVVM + Clean Architecture ก็จะเห็นข้อมูลแต่ละแบบในแต่ละ Layer ของ App Architecture เป็นแบบนี้
- Activity/Fragment จะนำ UI State ไปใช้งานเท่านั้น
- ViewModel จะนำ Application Data ที่ได้จาก Domain Layer มาแปลงเป็น UI State หรือนำ UI State ที่ได้จาก Activity/Fragment มาแปลงเป็น Application Data
- Domain Layer และ Data Layer จะนำ Application Data ไปใช้งานเท่านั้น
ที่ต้องแบ่งข้อมูลในลักษณะแบบนี้ก็เพื่อให้การทำงานในแต่ละส่วนแยกออกจากกันอย่างชัดเจน เช่น Domain Layer ที่คอยจัดการกับ Business Logic ภายในแอป ก็ไม่มีเหตุผลที่จะต้องไปยุ่งเกี่ยวกับ UI State ที่เป็นข้อมูลสำหรับการทำงานใน UI Layer เท่านั้น
ถึงแม้ว่า UI State และ Application Data เป็นข้อมูลที่มีจุดประสงค์ในการใช้งานที่แตกต่างกัน แต่ก็อาจจะถือข้อมูลบางส่วนที่เหมือนกันได้นะ
และแน่นอนว่าข้อมูลที่อยู่ในแต่ละ Layer ก็ควรจะจัดเก็บให้เหมาะสมกับการทำงานใน Layer นั้น ๆ ด้วยเช่นกัน
โดยจะขอเริ่มจากข้อมูลที่อยู่ใน Layer ข้างล่างสุดอย่าง Data Layer ก่อนเลย
ข้อมูลที่อยู่ใน Data Layer
ถึงแม้ว่าส่วนใหญ่แล้วข้อมูลที่อยู่ใน Data Layer ในแอปส่วนใหญ่มักจะมาจาก Web Service ที่ไม่ต้องทำอะไรมากนักนอกจากส่งต่อไปให้ Layer ที่สูงกว่า แต่ในบางครั้งนักพัฒนาก็อาจจะต้องเก็บข้อมูลบางส่วนไว้ที่ Layer นี้ด้วยเช่นกัน เช่น
- ข้อมูลจาก Web Service ที่ต้อง Cache เก็บไว้ เพราะไม่จำเป็นต้องเรียกใหม่ทุกครั้ง หรือต้องการทำให้แอปรองรับการใช้งานแบบ Offline ด้วย
- ข้อมูลจำพวก User Credential ที่ต้องเก็บไว้ในเครื่องหลังจากที่ผู้ใช้ผ่านขั้นตอนการเข้าใช้งานระบบแล้ว
- ข้อมูลที่เป็น Core Business ของแอปนั้น ๆ เช่น แอปจดบันทึกที่จะมีข้อมูลเกี่ยวกับบันทึกที่ผู้ใช้สร้างขึ้นมา
- ข้อมูลเกี่ยวกับ User Analytics ที่จำเป็นต้องบันทึกเก็บไว้เพื่อทำ Batch Request ในภายหลัง
ข้อมูลเหล่านี้จะเป็นข้อมูลที่ไม่ได้เกี่ยวกับ UI Operation โดยตรง แต่จะถูกนำไปใช้งานและกลายเป็นส่วนหนึ่งของ UI State ในภายหลัง และข้อมูลชุดเดียวกันก็อาจจะถูกนำไปใช้งานใน UI หลาย ๆ จุดที่อยู่ในภายแอปได้เช่นกัน
และการเก็บข้อมูลใน Data Layer ก็จะมีหลายรูปแบบขึ้นอยู่กับความเหมาะสมของข้อมูล ไม่ว่าจะเป็น
- Persistence Storage - การเก็บข้อมูลไว้ใน Device Storage เช่น SharedPreferences หรือ DataStore ที่เป็นแบบ Key-value และ Room ที่เป็นแบบ Database เป็นต้น
- In-memory Storage - การเก็บข้อมูลในรูปแบบของการประกาศเป็นตัวแปรไว้ในคลาสที่ทำหน้าที่เป็น Data Source
In-memory Storage จะอยู่ที่ UI Layer หรือ Data Layer ก็ได้ แต่จะไม่นิยมทำ In-memory Storage สำหรับ UI Layer ซักเท่าไร เนื่องจากใช้ ViewModel แทนได้
โดยการเก็บข้อมูลทั้ง 2 ก็จะมีจุดประสงค์ที่แตกต่างกันออกไป เช่น การเก็บข้อมูลไว้ใน In-memory Storage จะต่างจาก Persistence Storage ตรงที่ข้อมูลจะหายไปเมื่อแอปถูกระบบแอนดรอยด์คืนหน่วยความจำในขณะที่ไม่ได้ใช้งาน จึงทำให้เราสามารถนำมาใช้ประโยชน์สำหรับการทำงานบางอย่างภายในแอปได้ ในขณะที่การเก็บข้อมูลไว้ใน Persistence Storage ก็จะมีข้อดีตรงที่ข้อมูลจะไม่หายไปในตอนที่แอปถูกปิด
ข้อมูลที่อยู่ใน Domain Layer
โดยปกติแล้ว Domain Layer ถูกออกแบบมาในลักษณะของ Optional ที่จะมีหรือไม่มีก็ได้ เพราะเป็น Layer ที่ออกแบบมาเพื่อช่วยจัดการเกี่ยวกับ Business Logic ที่มีความซับซ้อนสูง ที่ไม่ใช่หน้าที่ของ Data Layer แต่ก็ไม่อยากให้ UI Layer เอาไปจัดการเองทั้งหมด
ดังนั้น Domain Layer จึงไม่ควรมีการเก็บข้อมูลใด ๆ และถ้าข้อมูลที่เกิดจาก Domain Layer จำเป็นต้องเก็บไว้ซักที่ใดที่หนึ่ง ก็ให้เป็นหน้าที่ของ UI Layer หรือ Data Layer แทน
และในกรณีนี้จำเป็นต้องส่งข้อมูลที่เป็น Application Data ไปให้ ViewModel ที่อยู่ใน UI Layer ใช้งานต่อ ก็ควรเป็น Lightweight Application Data ที่มีเฉพาะข้อมูลที่จำเป็นสำหรับการนำไปใช้งานเท่านั้น เพื่อลดภาระที่ไม่จำเป็นให้กับ ViewModel
ข้อมูลที่อยู่ใน UI Layer
ในฝั่งของ UI Layer นั้นจะแบ่งออกเป็น 2 ส่วนด้วยกันคือ
- Activity/Fragment ที่ควรจะสนใจแค่ UI State เท่านั้น
- ViewModel ที่จะแปลงข้อมูลไปมาระหว่าง UI State กับ Application Data
ข้อมูลที่อยู่ใน ViewModel
โดย ViewModel จะเป็นตัวกลางในการทำงานร่วมกับ Domain Layer
- แปลง UI State ที่ได้จาก Activity/Fragment ให้กลายเป็น Application Data เพื่อส่งต่อให้ Domain Layer จัดการต่อ
- แปลง Application Data ที่ได้จาก Domain Layer ให้กลายเป็น UI State ตาม Business Logic แล้วส่งต่อให้ Activity/Fragment
ในขณะเดียวกัน ViewModel ก็จะต้องเก็บข้อมูลทั้ง UI State และ Application Data ไว้ในตัวเองด้วย เพราะถึงแม้ว่า ViewModel จะไม่ถูกทำลายในตอนที่เกิด Configuration Changes ก็ตาม แต่ยังคงถูกทำลายในตอน Application Recreation อยู่ดี จึงทำให้นักพัฒนาต้องจัดการกับข้อมูลที่อยู่ใน ViewModel ให้ถูกต้องด้วย เพื่อไม่ให้ข้อมูลหายไประหว่างการทำงานจนทำให้เกิดบั๊ก
สามารถอ่านเรื่องราวของ Configuration Changes ได้ในบทความ Configuration Changes เรื่องสำคัญที่ Android Dev ไม่ควรพลาด
Applicaiton Recreation จะเกิดขึ้นตอนที่แอปถูกย่อหรือซ่อนอยู่ และแอปที่กำลังทำงานอยู่มีการใช้ Memory เยอะจนทำให้ระบบแอนดรอยด์ต้องปิดแอปที่ย่อหรือซ่อนไว้ชั่วคราวเพื่อคืน Memory กลับมา โดยระบบแอนดรอยด์จะเก็บข้อมูลบางส่วนของแอปเหล่านั้นไว้ให้ด้วย เพื่อคืนข้อมูลทั้งหมดให้อีกครั้งในตอนที่แอปถูกเปิดขึ้นมาใหม่ จึงทำให้แอปอยู่ในสถานะที่ทำงานต่อจากเดิมได้ทันที
โดย ViewModel จะมีชุดคำสั่งที่เรียกว่า Saved State API ให้นักพัฒนาเก็บข้อมูลไว้ในนั้นชั่วคราวในระหว่าง Application Recreation เพื่อไม่ให้ข้อมูลที่ส่งผลต่อการทำงานของแอปนั้นหายไป และทำให้แอปสามารถกลับมาทำงานต่อจากเดิมได้อย่างแนบเนียน
หรือจะอ่านจากบทความการ Save และ Restore UI State ที่อยู่ใน ViewModel ก็ได้เช่นกัน
ข้อมูลที่อยู่ใน Activity/Fragment
สำหรับ Activity และ Fragment นั้นจะเน้นไปที่การใช้งาน UI State ทั้งหมดเพื่อขับเคลื่อน UI ให้แสดงผลและทำงานตามที่แอปต้องการ ในระหว่างนั้นก็อาจจะเกิด UI State ที่เกี่ยวข้องขึ้นมา และเป็นข้อมูลที่ใช้สำหรับการทำงานภายใน Activity/Fragment เท่านั้น ไม่ต้องการส่งให้ ViewModel
ดังนั้นนักพัฒนาก็ควรจะต้องจัดการกับ UI State เหล่านี้ให้เหมาะสมด้วย เพราะ Activity/Fragment เป็น Component ที่สามารถถูกทำลายและสร้างขึ้นมาใหม่ (Recreation) เมื่อเกิด Configuration Changes และ Application Recreation
และในการจัดการกับ UI State ดังกล่าวจะขึ้นอยู่กับว่านักพัฒนาสร้าง UI ด้วยวิธีไหน
- Android Views – ใช้คำสั่ง
onSaveInstanceState()
- Jetpack Compose – ใช้คำสั่ง
rememberSaveable
สามารถอ่านบทความสำหรับการจัดการกับข้อมูลที่อยู่ใน Activity/Fragment ที่เจ้าของบล็อกเคยเขียนไว้บางส่วนได้เช่นกัน
- Save และ Restore UI State ใน Activity (สำหรับ Android Views)
- Save และ Restore UI State ใน Fragment (สำหรับ Android Views)
ควรออกแบบและจัดการกับ UI State และ Application Data ให้เหมาะสม
ไม่ว่าจะเป็น UI State หรือ Applicate Data ก็ตาม ต่างก็มีรูปแบบของข้อมูลแยกย่อยกันออกไปตามการใช้งานในแต่ละส่วน ดังนั้นนักพัฒนาจึงควรสร้างข้อมูลให้เหมาะสมกับการนำไปใช้งานด้วย เช่น
- Activity/Fragment ควรถือ UI State แค่บางส่วนที่จำเป็นสำหรับ User Interaction เท่านั้น นอกนั้นให้เป็นหน้าที่ของ ViewModel แทน เพราะ ViewModel สามารถอยู่ข้าม Configuration Changes ได้ จึงทำให้การทำงานของ Saved State API ถูกใช้งานแค่ตอน Application Recreation เท่านั้น
- Application Data ส่วนใหญ่ควรให้ Data Layer จัดการให้ และควรทำให้ข้อมูลเหล่านั้น Flatten และเป็น Lightweight Application Data ที่เพียงต่อสำหรับการทำงานของ ViewModel เพื่อลดภาระในการจัดการข้อมูลที่ฝั่ง ViewModel ให้น้อยลง
- ในกรณีที่ Application Data มีขนาดใหญ่มาก ควรใช้ Persistence Storage แทนการเก็บข้อมูลไว้ใน Memory โดยตรง ซึ่งจะช่วยให้จัดการกับข้อมูลได้อย่างมีประสิทธิภาพกว่า
Purpose | Architecture Layer | Data Capacity | Read/Write Speed | |
---|---|---|---|---|
Activity/Fragment | Transient UI State | UI Layer | Light | Moderate |
ViewModel | UI State & Light-weight Application Data | UI Layer | Moderate | Moderate |
In-memory Storage | Application Data | Data Layer | Moderate | Fast |
Persistence Storage | Application Data | Data Layer | Heavy | Slow |
นอกจากนี้ ข้อมูลในแต่ละส่วนก็ควรจะเลือกจัดการให้ถูกต้องและเหมาะสมด้วย เนื่องจากการเก็บข้อมูลในแต่ละแบบจะมีจุดประสงค์, ข้อดี, และข้อเสียที่แตกต่างกันออกไป เช่น
- การใช้ Saved Instance State API จะเหมาะกับ UI State แค่บางส่วนที่จำเป็นสำหรับ User Interaction เพราะว่าเบื้องหลังในการทำงานของ API ดังกล่าวคือการเก็บข้อมูลไว้ในรูปแบบของ Bundle ที่มีขนาดได้สูงสุดแค่เพียง 1MB เท่านั้น
- การเก็บข้อมูลไว้ใน ViewModel ก็เหมือนกับ In-memory Storage ที่อยู่ใน Data Layer เพราะข้อมูลจะไม่หายไปเมื่อเกิด Configuration Changes แต่จะยังคงหายไปเมื่อเกิด Application Recreation ดังนั้นจึงต้องใช้ Saved State API ของ ViewModel เข้ามาช่วย เพื่อแก้ปัญหาดังกล่าว
- การเก็บ Application Data ไว้ใน Persistence Storage จะมีข้อดีตรงที่ข้อมูลอยู่ถาวร (จนกว่าผู้ใช้จะกด Clear App Data หรือลบแอปทิ้ง) และเหมาะกับ Application Data ที่มีขนาดใหญ่ แต่ก็จะใช้พื้นที่หน่วยความจำของเครื่องและมีความเร็วในการอ่านเขียนข้อมูลได้ไม่เร็วเท่ากับวิธีอื่นที่เก็บข้อมูลไว้ใน Memory โดยตรง
จึงสรุปได้ออกมาเป็นตารางแบบนี้
Purpose | Architecture Layer | Storage Location | Survives Config Change | Survives User Complete Activity | Survives App Recreation | Survives App Terminate | Data Capacity | Data Limitation | Read/Write Speed | |
---|---|---|---|---|---|---|---|---|---|---|
Saved Instance State API | Transient UI State | UI Layer | Memory | Yes | No | Yes | No | Light | Bundle | Moderate |
Remember Saveable API | Transient UI State | UI Layer | Memory | Yes | No | Yes | No | Light | Saveable | Moderate |
ViewModel Saved State API | UI State & Lightweight Application Data | UI Layer | Memory | Yes | No | Yes | No | Moderate | Available Memory | Moderate |
In-memory Storage | Application Data | Data Layer | Memory | Yes | Yes | No | No | Moderate | Available Memory | Fast |
Persistence Storage | Application Data | Data Layer | Disk | Yes | Yes | Yes | Yes | Heavy | Disk Space | Slow |
และทั้งหมดนี้ก็คือรูปแบบของข้อมูลและวิธีการจัดการกับข้อมูลภายในแอปบนแอนดรอยด์ เพื่อให้นักพัฒนาสามารถจัดการกับการทำงานของแอปได้อย่างเป็นระบบและจัดการกับข้อมูลได้อย่างมีประสิทธิภาพนั่นเอง
แหล่งข้อมูลอ้างอิง
- Save UI states [Android Developers]
- Save UI state in Compose [Android Developers]
- Save simple data with SharedPreferences [Android Developers]
- Save data in a local database using Room [Android Developers]
- Handle configuration changes [Android Developers]
- Best practices for saving UI state on Android [YouTube]