Android Architecture Components — Part3 : Presentation Layer
Table of Contents
- Part 1 — Introduction
- Part 2 — Data Layer
- Part 3 — Presentation Layer <- You are here
- Part 4 — Demo
- Part 5 — Addendum
Presentation Layer
source code package: com.blacklenspub.postsreader.presentation
แต่แรกเดิมทีเรามักจะยัดลอจิกทุกอย่างอยู่ใน Activity
หรือ Fragment
นานเข้า Activity
ก็จะอ้วนเทอะทะ มีทุกลอจิกกระจุกอยู่ในนั้น (ผมเคยเจอโปรเจคที่มีActivity
ความยาวกว่า 2,000 บรรทัด) Activity
หรือ Fragment
นั้นควรจะมีหน้าที่เป็นแค่ UI Controller กล่าวคือมันควรทำแค่เอาข้อมูลมาแสดงบน UI ให้ถูกต้อง มีแพทเทิร์นหลายแบบที่นิยมนำมาใช้แยกลอจิกที่ไม่เกี่ยวกับการแสดงผล UI ออกจาก Activity
และ Fragment
เช่น MVP และ MVVM ล่าสุดใน AAC ก็ได้นำคลาสที่ชื่อว่า ViewModel
เข้ามาให้ ใน Part 3 นี้ผมจะนำโค้ดในส่วนของ PostListActivity
และ PostListViewModel
มาให้ดูเท่านั้นนะครับ ส่วนของ PostDetailActivity
สามารถตามดูได้ในโปรเจคจริงเลย
ViewModel
เริ่มกันที่ PostListViewModel
ก่อนเลย
class PostListViewModel : ViewModel() { @Inject
lateinit var postRepo: PostRepository fun getAllPosts() = postRepo.getAllPosts()
}
PostListViewModel
extend คลาสViewModel
ของ AACpostRepo
โดน inject มาโดย Dagger- ฟังก์ชัน
getAllPosts()
ไปดึงPost
ทั้งหมดมาจากPostRepository
ViewModel
ViewModel
เป็น component ใน AAC ที่นอกจากจะช่วยให้เราจัดโค้ดที่ไม่เกี่ยวกับการแสดงผล UI ออกมาจากActivity
หรือFragment
แล้ว มันยังถูกออกแบบมาให้แก้ปัญหาวิธีการจัดการ configuration changes ในActivity
อย่างเช่นเวลาจอหมุนแล้วทำให้Activity
ถูกสร้างใหม่ด้วย เดี๋ยวเราจะมาดูว่ามันทำงานและช่วยแก้ปัญหานี้ได้ยังไงในเนื้อหาส่วนต่อไปครับ
ในตัวอย่างนี้ ViewModel
ของเราอาจจะดูเรียบง่ายไม่ได้ซับซ้อนอะไร แต่ในแอปที่มีความซับซ้อนมากกว่านี้ ลอจิกใน ViewModel
จะซับซ้อนกว่านี้มาก
Activity
ต่อไปมาดู Activity กัน
class PostListActivity : LifecycleActivity() { private val viewModel by lazy {
ViewModelProviders.of(this).get(PostListViewModel::class.java)
} override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) // ... other initialization logics are omitted. getAllPosts()
} private fun getAllPosts() {
viewModel.getAllPosts().observe(this, Observer {
showPosts(it)
})
} private fun showPosts(posts: List<Post>) {
// ... set data to UI widgets
}
}
PostListActivity
extend คลาสLifecycleActivity
(ในอนาคตเมื่อ AAC ออก stable version เราอาจจะไม่ต้อง extend จากคลาสนี้แต่ไปใช้คลาสใน Support Lib แทน)- วิธีการสร้าง
viewModel
ทำโดยการเรียกใช้ผ่านViewModelProviders
- ฟังก์ชัน
getAllPosts()
ของviewModel
รีเทิร์นค่าเป็นLiveData
ดังนั้นเราสามารถเรียกฟังก์ชันobserve()
จากตรงนี้ได้เลย - ฟังก์ชัน
observe()
ต้องการพารามิเตอร์แรกเป็น typeLifecycleOwner
เพื่อที่มันจะสามารถรู้ Active/Inactive state ของ Observer ได้ และเราส่งthis
เข้าไปให้เพราะPostListActivity
นั้น extendLifeCycleActivity
ซึ่ง implementLifecycleOwner
ไว้แล้ว
ความเจ๋งของ ViewModel
ใน AAC ก็คือมันไม่ได้ผูกกับ life cycle ของ Activity
หรือ Fragment
หมายความว่าถ้าเราหมุนจอแล้ว Activity ของเราเกิดการ re-create ขึ้นใหม่นั้น พอเราเรียก ViewModelProviders.of(this).get(PostListViewModel::class.java)
เราก็จะได้ ViewModel
ตัวเดิมที่เคยสร้างไว้ก่อนแล้ว คราวนี้เราก็ไม่ต้องมาโหลดข้อมูลอะไรใหม่เลยเพราะทุกอย่างมีพร้อมอยู่ใน ViewModel
เราสามารถเอามา render ใหม่ได้เลย
เพื่อความง่าย ในตัวอย่าง PostListViewModel
ผมไม่ได้แสดงการทำ cache ของ Post
ที่ดึงมาจาก PostRepository
ไว้ ลองศึกษาโค้ดเต็มๆ ได้จากโปรเจคจริงนะครับ
นอกจากนี้ถ้า ViewModel สามารถใช้ร่วมกันระหว่างหลายๆ Fragment ได้ด้วย แต่ผมไม่ได้ทำตัวอย่างของประเด็นนี้ไว้ในซีรี่ย์นี้นะครับ ถ้าสนใจสามารถดูเพิ่มเติมได้ที่ Document ของ ViewModel
Unit Test
การทำเทสสำหรับ ViewModel
นั้นทำได้โดยการที่เราทำการ mock Repository
ผมดึงเฉพาะส่วนที่เทสฟังก์ชัน getAllPosts()
ของ PostListViewModel
มาแสดงในนี้นะครับ ฉบับเต็มดูได้ในโปรเจคจริงตรงโฟลเดอร์ /test
ได้เลย
@Test
fun getAllPosts() {
// ... mocking of repository and observer are omitted. sutViewModel.getAllPosts().observeForever(observer as Observer<List<Post>>) verify(postRepo).getAllPosts()
verify(observer).onChanged(mockedPosts)
}
ผมไม่ได้ทำตัวอย่าง Activity Test นะครับ ถ้าสนใจสามารถทำได้โดยการใชไลบรารีช่วยเช่น Robolectric
ติดตามดู demo การทำงานของ LiveData
กับ ViewModel
ใน Part 4 ได้เลยครับ