Our SuccessKnowledges
NEXTZY Logo
Crews
Chat
Talk with Team
NEXTZY Logo
NEXTZY
Chat
Home>Knowledges

Save state CustomViewGroup อย่างไรให้ถูกวิธี

Share:

Save state CustomViewGroup อย่างไรให้ถูกวิธี

Table of contents

  • Save state CustomViewGroup อย่างไรให้ถูกวิธี
  • ปัญหา
  • ทำความเข้าใจเรื่อง Save state
  • 1. สำหรับ save state ของ Activity/Fragment
  • 2. สำหรับ save state ของ View/ViewGroup
  • ต้นเหตุของปัญหา
  • แก้ไขให้ถูก
  • ตัวอย่างการใช้งาน
Nonthawit

Nonthawit

CEO | Engineer | Designer

VIEW

2,601

CATEGORY

Technical

LAST UPDATED

January 18, 2017

Nonthawit

Nonthawit

CEO | Engineer | Designer

VIEW

2,601

CATEGORY

Technical

LAST UPDATED

January 18, 2017

Save state CustomViewGroup อย่างไรให้ถูกวิธี

อันดับแรกถ้าเราต้องการทดสอบการ save state บน android ให้ไปที่

Setting > Developer options > Don’t Keep Activities

เพื่อบอก activity ว่าเมื่อกดย่อแอปให้ save state ไว้แล้วทำลายตัวเองทิ้งทันที ทำให้เวลากลับเข้าแอปใหม่ onRestoreInstanceState(…) จะได้ถูกเรียกทุกครั้ง

ปัญหา

Fault ViewGroup savedstate

จากรูปข้างบนเราสร้าง ViewGroup ขึ้นมา 1 ตัวที่มี ImageView, EditText และ Switch ไม่ได้ทำอะไรมากมาย และแปะบน activity ไว้ 3 ตัว เบื้องต้นเราทำการย่อแอปและกลับมาใหม่จะสังเกตเห็นว่า “333” ที่อยู่ตัวสุดท้าย จะถูกนำมาใส่ให้ EditText ทุกตัว ซึ่งผิด เราคาดหวังว่ากลับมาต้องได้ตัวเดิม “111” “222” และ “333” ตัวเดิม

กรณีนี้ เป็นกรณีที่นักพัฒนามองข้ามหรือหลงลืมกันไป ซึ่งวันนี้เราจะมาทำให้มันถูกต้องกันครับ 😎

แต่ก่อนอื่นเราต้อง

ทำความเข้าใจเรื่อง Save state

เราขอแบ่งออกเป็น 2 เรื่องคือ save state สำหรับ Activity/Fragment และ View/ViewGroup

1. สำหรับ save state ของ Activity/Fragment

จากภาพข้างบน onSaveInstanceState( ) จะถูกเรียกทุกครั้งเมื่อกดย่อแอป

แต่ onRestoreInstanceState( ) จะถูกเรียก “ก็ต่อเมื่อ” Activity/Fragment ตัวนั้นตาย (ยกเว้นการตายที่เกิดจาก user กดออกแอปเองนะ) เพราะฉะนั้นเราไม่สามารถคาดเดาได้ว่า Activity/Fragment ตัวนั้นจะตายตอนไหน ตัวอย่างเช่น แอปอื่นๆ ram หมด จนต้อง clear แอปที่อยู่ในสถานะ background ทิ้งไป

ดังนั้นเพื่อให้ onRestoreInstanceState( ) ถูกเรียกทุกครั้ง เราต้องไป enable Don’t Keep Activites ที่ Developer Options เสียก่อนเพื่อให้ Activity ตายเสมอเมื่อกดย่อแอป

ในส่วนของ Activity/Fragment ตรงไปตรงมาครับเราไม่ต้องแก้อะไร เพราะหน้าที่ของ Activity/Framgment คือ save state ตัวมันเอง และมันจะไปเรียก onSaveInstanceState( )/onRestoreInstanceState( ) ของ View/ViewGroup แต่ละตัวให้ต่ออีกที เพราะฉะนั้นต้นเหตุมันจึงอยู่ที่ View/ViewGroup ที่เราเอาไปแปะบน Activity/Fragment ในหัวข้อต่อไปที่กำลังจะพูดถึงนั่นเอง

2. สำหรับ save state ของ View/ViewGroup

ส่วนของ View/ViewGroup อย่างที่เราบอก Activity/Fragment จะเรียก view แต่ละตัวให้ทำการ save state ของตัวมันเอง เพราะเบื้องหลังแล้ว saveHierarchyState( )/restoreHierarchyState( ) ของ view แต่ละตัวจะถูกเรียก ไล่มาจนถึง onSaveInstanceState( )/onRestoreInstanceState( ) ตามภาพข้างบนนั่นเอง

ต้นเหตุของปัญหา

ถ้าเราเข้าไปดูในแต่ละ method ปัญหาจะอยู่ที่

void dispatchSaveInstanceState( SparseArray )
และ
void dispatchRestoreInstanceState( SparseArray )

เป็น method ที่ไล่ save/restore state ของ ChildView ที่อยู่ใน ViewGroup แต่ละตัว โดยอิงจาก viewId และจะทำการ save state ลงใน SparseArray ตัวกลางที่ส่งเข้ามาผ่าน parameter

ปัญหาเลยเป็นดังภาพ

จะเห็นว่า state ของ ChildView ทุกตัวจะถูกนำไปใส่ที่ SparseArray ตัวกลาง ทำให้ state ถูกนำไปทับ viewId เดิมตลอด ทำให้เวลากดย่อแอป และกลับมาใหม่มันจึงดึง state ล่าสุดมาใส่แทน ค่าใน EditText จึงเป็น “333” หมดน้ำตาจะไหล 😭

ซึ่งนี่ถือเป็นเคสที่ร้ายแรงพอสมควรเลยนะ ถ้าเกิดข้อมูลตัวสุดท้ายของ EditText เป็นข้อมูลอย่าง sensitive อย่างเช่น password หรือ รหัสบางอย่าง นี่ตายแน่ๆคนอื่นเห็นหมดกันพอดี

แก้ไขให้ถูก

ไอเดียการแก้ไขเรื่องนี้คือ เราต้องทำให้การเก็บ state ของแต่ละ ViewGroup ให้แยกอิสระต่อกันโดยต้องสร้าง SparseArray มาอีกชั้นก่อนจะยัดใส่ลงใน SparseArray ตัวกลางดังภาพ เพื่อกันการ saved state ไปทับ viewId เดิม

จากรูปเราจึงได้ “111” “222” และ “333” restore กลับมา view ถูกตัวแล้ว

เมื่อเห็นภาพแล้วทั้งหมดแล้ว ถึงเวลาที่เราต้องมาลง code กันละ โดยการสร้าง base class ขึ้นมาหนึ่งตัว

override 2 method

เพื่อบอก ViewGroup ตัวนี้ว่าไม่ต้องไปไล่ save/restore state ChildView นะ เพราะเราจะทำการ handle เอง

ต่อไป override

void onRestoreInstanceState(Parcelable state)

และ สร้าง method เพิ่มอีก 2 ตัวคือ

Parcelable onSaveInstanceChildState( ChildSavedState ss ) 
และ
void onRestoreInstanceChildState( ChildSavedState ss)
Parcelable onSaveInstanceChildState( ChildSavedState ss )

จะเห็นว่าใน method นี้เราสร้าง SparseArray ไว้อีกชั้นเพื่อกันการ save state ไปซ้ำ viewId เดิม และ

void onRestoreInstanceChildState( ChildSavedState ss)

ก็ในลักษณะเดียวกัน

สุดท้ายสร้าง ChildSavedState เป็น inner class เพื่อเก็บ state ต่างๆของ ChildView นั่นเอง

แค่นี้ก็ได้ Base class สำหรับ ViewGroup เป็นอันเสร็จนำไปใช้ต่อกันได้

ตัวอย่างการใช้งาน

สร้าง class SwitchViewGroup ที่ extend BaseViewGroup ที่เราทำไว้แล้ว

override 2 method

สังเกตใน method onSaveInstanceState( ) เราเรียก

SavedState ss = (SavedState) onSaveInstanceChildState( 
new SavedState( superState )
);

เพื่อไปไล่ save state ของ ChildView ให้เสร็จก่อน เพราะฉะนั้น SavedState ss จะมี state ของ ChildView อยู่ เสร็จแล้วเราจึงทำการ save state ของ SwitchViewGroup (ตัวเอง) ต่อ

และสร้าง SaveState inner class ที่ extend ChildSavedState ที่เราทำเป็น inner class ไว้เช่นกัน

แค่นี้การทำ custom ViewGroup ก็เป็นอันเสร็จเรียบร้อย

restore กลับมาถูกต้วไม่มั่วเหมือนตอนแรก

เป็นตัวอย่าง boilerplate code ของ CustomViewGroup ลองเอาไปปรับใช้กันดูนะครับวันนี้เอาไว้แค่นี้ก่อนเจอกัน blog หน้าเนาะ

nontravis/ViewGroupSavedState

อย่าลืม  share ให้มนุษย์ Android คนอื่นด้วยหละ 😎

Share:

KNOWLEDGE

Related Articles

เข้าใจการทำ Selector แบบ Ripple effect
Nonthawit

Nonthawit

CEO | Engineer | Designer

เข้าใจการทำ Selector แบบ Ripple effect

20 สิ่ง ที่ได้หลังจากเป็น Android developer ที่ Nextzy 3 เดือน
Nonthawit

Nonthawit

CEO | Engineer | Designer

20 สิ่ง ที่ได้หลังจากเป็น Android developer ที่ Nextzy 3 เดือน

บทความนี้แชร์ประสบการณ์ 3 เดือนแรกของการทำงานเป็น Android Developer ที่ Nextzy ครอบคลุมทั้งด้าน technical เช่น MVP architecture, Android Lifecycle, ProGuard, Git workflow และการเขียน Unit Test รวมถึงด้าน soft skill อย่างการสื่อสารกับทีม, การแชร์ความรู้, และการเขียนโค้ดให้ readable และยืดหยุ่น นอกจากนี้ยังสะท้อนวัฒนธรรมองค์กรที่เน้นทีมเวิร์ค การ review โค้ด และบรรยากาศการทำงานที่สนุกสนาน ซึ่งล้วนช่วยลด learning curve และพัฒนาทักษะได้เร็วกว่าการเรียนรู้คนเดียว

[Tip/Trick] วิธีติดต่อกับ WebView ผ่าน JavascriptInterface มันเท่มาก
Nonthawit

Nonthawit

CEO | Engineer | Designer

[Tip/Trick] วิธีติดต่อกับ WebView ผ่าน JavascriptInterface มันเท่มาก

NEXTZY Logo
NEXTZY
48/27 Ratchadaphisek Rd, Samsen Nok, Huai Khwang, Bangkok 10310

Base at Thailand

Thailand

Home

Crews

Success

Download press kits

Knowledges

Chat

Talk with team

SCHEDULE