Android Architecture Components — Part3 : Presentation Layer

Dew
Black Lens
Published in
2 min readJun 20, 2017

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

Presentation Layer

แต่แรกเดิมทีเรามักจะยัดลอจิกทุกอย่างอยู่ใน 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 ของ AAC
  • postRepo โดน 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() ต้องการพารามิเตอร์แรกเป็น type LifecycleOwner เพื่อที่มันจะสามารถรู้ Active/Inactive state ของ Observer ได้ และเราส่ง this เข้าไปให้เพราะ PostListActivity นั้น extend LifeCycleActivity ซึ่ง implement LifecycleOwner ไว้แล้ว

ความเจ๋งของ ViewModel ใน AAC ก็คือมันไม่ได้ผูกกับ life cycle ของ Activity หรือ Fragment หมายความว่าถ้าเราหมุนจอแล้ว Activity ของเราเกิดการ re-create ขึ้นใหม่นั้น พอเราเรียก ViewModelProviders.of(this).get(PostListViewModel::class.java) เราก็จะได้ ViewModel ตัวเดิมที่เคยสร้างไว้ก่อนแล้ว คราวนี้เราก็ไม่ต้องมาโหลดข้อมูลอะไรใหม่เลยเพราะทุกอย่างมีพร้อมอยู่ใน ViewModel เราสามารถเอามา render ใหม่ได้เลย

เพื่อความง่าย ในตัวอย่าง PostListViewModel ผมไม่ได้แสดงการทำ cache ของ Post ที่ดึงมาจาก PostRepository ไว้ ลองศึกษาโค้ดเต็มๆ ได้จากโปรเจคจริงนะครับ

Lifecycle ของ ViewModel ภาพประกอบจาก https://developer.android.com/topic/libraries/architecture/viewmodel.html

นอกจากนี้ถ้า 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 ได้เลยครับ

--

--