บทความนี้ เป็นส่วนหนึ่งของชุดบทความที่พูดถึง RxJava2 โดยบทความชุดนี้จะประกอบด้วยหัวข้อต่างๆดังต่อไปนี้
- Part 1 - อะไรเอ่ย ReactiveX?
- Part 2 - รู้จักกับ Observer & Observable ประเภทต่างๆ
- Part 3 - Observable สร้างยังไงหนอ แล้วควรระวังอะไรบ้าง
- Part 4 - Hot Observable Vs Cold Observable
- Part 5 - Observer Vs Subscriber Vs Consumer แตกต่างกันยังไง
- Part 6 - observeOn Vs subscribeOn ใช้ต่างกันอย่างไร
- Part 7 - Operator ที่ควรรู้ใน ReactiveX
- Part 8 - Subject คืออะไร แล้วใช้มันอย่างไร
และแล้วก็มาถึงบทความสุดท้ายของ series RxJava ชุดนี้กันแล้วนะครับ โดยในบทความที่ผ่านมาเราได้รู้จักกับ Observable Observer รวมถึง Operator ต่างๆ ที่มีให้ใช้ใน Rxjava2 กันไปแล้ว มาในบทความนี้ เราจะมารู้จักกับ Subject กันว่า มันคืออะไร แล้วเราจะใช้มันอย่างไร
Subject
Subject เป็นเหมือนสะพานที่เชื่อมระหว่าง Observable แล้ว Observer (ต่อไปจะขอเรียกว่า subscriber) คือ Subject จะทำหน้าที่เป็นทั้ง Observable และ Subscriber ในตัวเดียวกัน นั้นหมายความว่าเราสามารถใช้ Subject แทน Subscriber ในการ subscribe
Observable ได้ และยังสามารถใช้มันในการส่งต่อ Item ที่ได้รับมาไปยัง Subscriber ตัวอื่นที่ subscribe
มันอยู่ หรือแม้กระทั้งสร้าง Item ใหม่ขึ้นมาได้ เพราะเนื่องจากมันเป็น Observable ในตัวมันเองอยู่แล้ว
Subject เป็น Hot observable (ที่กล่าวถึงในบทความที่ 4) นั้นหมายความว่า มันยังคงทำงานต่อไปได้ถึงแม้ไม่มีใคร subscribe
มันอยู่ ให้เราลองนึกถึงสถานีวิทยุที่ยังคงเปิดเพลงต่อไป ถึงแม้ว่าเราจะปิดวิทยุของเราไปแล้วก็ตาม และเนื่องจากมันเป็น Hot observable ดังนั้นมันจะไม่ execute operation ใหม่ทุกครั้งที่เกิดการ subscribe
นั้นหมายความว่า Subscriber ทุกตัวที่ subscribe
มันอยู่จะได้รับข้อมูลชุดเดียวกัน โดยไม่มีการรันโค้ดภายใน Subject ใหม่ ซึ่งผิดกับ Cold Observable ตรงที่จะรันโค้ดภายใน Observable ใหม่ทุกครั้งที่เกิดการ subscribe
ขึ้น
Varieties of Subject
Subject ที่มีให้เราใช้ใน RxJava2 มีอยู่ 4 ตัวด้วยกัน โดยแต่ละตัวก็จะมีพฤติกรรมการทำงานที่แตกต่างกัน โดยเราจะมาดูกันว่าแต่ละตัวทำงานแตกต่างกันอย่างไร
PublishSubject
PublishSubject
คือ Subject ที่ปล่อย Item ออกมาตามช่วงเวลาที่ Source Observable (Observable ที่ผลิตข้อมูล) ปล่อยข้อมูลนั้นๆออกมา โดยที่ Subscriber จะได้รับข้อมูลตามช่วงเวลาที่เข้ามา subscribe
นั่นหมายความว่า หาก Subscriber เข้าไป subscribe
ตั้งแต่ Subject เริ่มปล่อย Item ออกมา Subscriber นั้นก็จะได้รับ item ที่ปล่อยออกมาตั้งแต่เริ่ม แต่หากไป subscribe
ตอนช่วงกลางๆ หลังจากที่ Subject มีการปล่อย Item ออกไปบ้างแล้ว Subscriber ตัวนั้นก็จะได้รับแค่เฉพาะ Item ที่เกิดขึ้นหลังจากการ subscribe
เท่านั้น ลองดูแผนภาพ Marble ด้านบนประกอบครับ
หากเปรียบเป็นนักเรียนกับห้องเรียน PublishSubject
ก็จะเหมือนกับตอนที่นักเรียนเข้าเรียนตอนช่วงเวลาใดๆ ของ class นักเรียนคนนั้นก็จะได้เรียนแต่สิ่งที่อาจารย์สอนนับตั้งแต่ช่วงเวลาที่เข้าเรียน โดยจะไม่รู้เลยว่าก่อนหน้านั้นอาจารย์สอนอะไรไปบ้าง
ลองดูตัวอย่างการทำงานของมันกันครับ
BehaviorSubject
BehaviorSubject
คือ Subject ที่เริ่มต้นด้วยการปล่อย Item ตัวล่าสุด (most recent item) ที่ถูกปล่อยออกมาจาก Source Observable จากนั้นจึงค่อยปล่อย Item ที่เกิดขึ้นหลังจากการ subscribe
ไปแล้วให้กับ Subscriber ตามปกติเหมือน PublishSubject
ดังนั้น BehaviorSubject
จึงมีความแตกต่างกับ PublishSubject
ตรงที่ BehaviorSubject
จะมีค่า Default หรือ ค่าล่าสุด ปล่อยออกไปให้กับ Subscriber ก่อน แต่หาก Source Observable ไม่ได้ปล่อย Item ใดๆ ออกมาก่อนหน้าการ subscribe
หรือ เราไม่ได้กำหนดค่า Default ใดๆให้กับ BehaviorSubject
ตัว Subscriber ก็จะไม่ได้รับ Item ใดๆ ออกมาเช่นกัน
หากเปรียบเป็นนักเรียนกับห้องเรียน BehaviorSubject
ก็จะเหมือนกับตอนที่นักเรียนเข้าห้องเรียนสาย แต่โชคดีที่มีเพื่อนคอยบอกให้ว่า อาจารย์พูดเรื่องอะไรไปก่อนหน้านี้ ทำให้นักเรียนคนนั้นพอจะประติดประต่อเรื่องราวได้บ้าง
ลองดูตัวอย่างการทำงานของมันกันครับ
ReplaySubject
ReplaySubject
คือ Subject ที่จะปล่อย Item ทั้งหมดตั้งแต่ Item แรกให้กับ Subscriber ที่เข้ามา subscribe
โดยที่ไม่สนใจว่า Subscriber นั้นจะเข้ามา subscribe
ตอนไหน จากนั้นจึงปล่อย Item ที่เกิดขึ้น (จาก Source Observable) หลังจากการ subscribe
ตามปกติเหมือน PublishSubject
หากเปรียบเป็นนักเรียนกับห้องเรียน ReplaySubject
ก็จะเหมือนกับการที่นักเรียนเรียนออนไลน์ผ่านทาง Youtube channel ซึ่งหากนักเรียนเปิดเข้ามาเรียนไม่ทันตอนเริ่ม class นักเรียนก็สามารถย้อนกลับไปดู replay ที่อาจารย์ได้สอนไปในช่วงต้นชั่วโมงได้นั้นเอง
ลองดูตัวอย่างการทำงานกันครับ
AsyncSubject
AsyncSubject
คือ Subject ที่จะปล่อย Item ออกมาแค่ค่าสุดท้ายค่าเดียวเท่านั้น และจะปล่อยออกมาเมื่อ Source Observable ทำงานเสร็จแล้วเท่านั้น ดังนั้น หาก Source Observable ไม่ปล่อยค่าใดๆ ออกมา หรือ เกิด Error ขึ้นก่อน จะทำให้ AsyncSubject
หยุดการทำงานโดยที่ไม่ปล่อย Item ใดๆ ออกมาได้เช่นกัน (แต่จะเรียก onError()
แทนในกรณีที่เกิด Error ขึ้น)
หากเปรียบเป็นนักเรียนกับห้องเรียน ก็คงเปรียบได้กับกรณีที่นักเรียนเข้าห้องเรียนไม่ว่าช่วงเวลาใดก็ตาม แต่รอฟังแค่บทสรุป (หรือสิ่งที่อาจารย์สอนเป็นอย่างสุดท้าย) ของ class เรียนนั้นเท่านั้น
ลองดูตัวอย่างการทำงานของมันกันครับ
แล้วเราจะใช้มันอย่างไร
สำหรับคำถามที่ว่าจะใช้มันอย่างไร หรือใช้มันตอนไหนนั้น อันนี้ก็ขึ้นอยู่กับจุดประสงค์ของการใช้งานแล้วล่ะครับ ในบทความนี้ขอยกตัวอย่างเหตุการณ์ที่ใช้ BehaviorSubject
และ PublishSubject
สองตัวนี้มาให้ดูกันนะครับ เนื่องจากเป็นตัวที่เราใช้ค่อนข้างบ่อย ซึ่งอีกสองตัวยังไม่เคยเจอเหตุการณ์ที่ต้องใช้มันจริงๆจังๆเหมือนกัน หากใครมีไอเดียก็มาแลกเปลี่ยนกันได้ครับ แต่ผมเชื่อว่าหากใช้สองตัวนี้เป็น อีกสองตัวที่เหลือก็ไม่น่าเป็นห่วงเท่าไหร่ครับ
ตัวอย่างของการเลือกใช้ BehaviorSubject
ได้แก่ กรณี Location update แน่นอนว่าเราคงไม่อยากได้ข้อมูล Location ทั้งหมดตั้งแต่เริ่มทำงาน แต่เราอยากได้แค่ Location ล่าสุดเท่านั้น และควรจะได้ Location ทันทีที่ subscribe
ด้วย ดังนั้น BehaviorSubject
จึงดูเหมาะสมที่สุดครับ ลองดูตัวอย่างการใช้งานครับ
จากโค้ดจะเห็นว่าเราสร้าง locationUpdateSubject
ขึ้นมาเป็น BehaviorSubject
โดยทุกครั้งที่ได้รับ Location update ก็จะทำการเรียก onNext(location)
เพื่อส่ง update location ไปยัง Subscriber ซึ่งทางฝั่ง Subscriber ที่อยู่ใน Fragment
ก็ทำหน้าที่ subscribe
เจ้า locationUpdateSubject
และเมื่อได้ผลลัพธ์ ก็จะนำ Location ที่ได้ไปใช้งานต่อไป โดยเราจะเห็นว่า เราสามาถ subscribe
ได้มากกว่าหนึ่งตัว โดยที่ทุกตัวจะได้ข้อมูลเดียวกัน
ส่วน PublishSubject
ที่ผมใช้บ่อยนั้น จะใช้ในกรณีที่เราไม่อาจ subscribe
ระหว่าง Source Observable และ Subscriber กันได้โดยตรง ตัวอย่างเช่น หากใครใช้ MVVM อยู่ ก็คงไม่อยากให้ ViewModel รู้จัก View มากสักเท่าไหร่หากไม่จำเป็น ซึ่งถ้าเราต้องการดักฟังเหตุการณ์การกดปุ่มของผู้ใช้ที่เกิดขึ้นในฝั่ง View แล้วส่งต่อเหตุการณ์นั้นให้กับ Subscriber ที่อยู่ในฝั่ง ViewModel แล้วละก้อ หากเราให้ Subscriber เหล่านั้นมา subscribe
View โดยตรงก็จะเกิด Dependency ขึ้น ดังนั้นเราจึงนำ PublishSubject
มาช่วยในกรณีนี้ได้ ซึ่งสาเหตุที่เลือกใช้ PublishSubject
กับกรณีการกดปุ่มนั้นเพราะว่า Subscriber ไม่จำเป็นต้องสนใจว่าก่อนหน้าที่จะ subscribe
นั้น ผู้ใช้กดปุ่มไปแล้วกี่ครั้ง แต่จะยึดตามเหตุการณ์ที่เกิดขึ้นหลังจาก subscribe
แล้วเป็นสำคัญ และตอบสนองต่อเหตุการณ์ที่เกิดขึ้นเหล่านั้น ดังนั้นการเลือกใช้ PublishSubject
จึงเหมาะกับเหตุการณ์นี้ ลองดูตัวอย่างครับ
จากโค้ดจะเห็นว่าเราสร้าง Interface ของ ViewModel ไว้ โดยมีทั้ง Input
และ Output
อยู่ภายใน ให้เราสังเกตุที่ Input
ว่าเราประกาศ refresh
เป็น Observer
แต่เมื่อ Implement จริงใน SampleViewModelImpl
เราประกาศให้เป็น PublishSubject
แทน สาเหตุที่ทำแบบนี้เนื่องจาก PublishSubject
นั้นเป็นได้ทั้ง Observable และ Observer ดังนั้นเราจึงสามารถประกาศ Interface เป็น Observer
ได้ อีกเหตุผลหนึ่งก็คือ เราไม่อยากให้ฝั่ง View สามารถเรียก onNext()
ได้เองโดยตรง ดังนั้นการประกาศเป็น Observer
จะช่วยป้องกันไม่ให้ฝั่ง View เรียก onNext()
โดยพลการได้นั้นเอง ต่อไปให้เราสังเกตที่ฟังก์ชั่น initInput
ใน SampleFragment
ซึ่งจะเห็นว่าตอนนี้ refresh
ทำหน้าที่เป็น Observer ที่รอรับข้อมูลจาก RxView.clicks()
และเมื่อเราไปดูฟังก์ชั่น init
ใน SampleViewModelImpl
เราก็จะเห็นว่า refresh
จะทำหน้าที่เป็น Observable ที่นำข้อมูลที่ได้รับมา ไปทำอะไรบางอย่างเพื่อให้ได้ผลลัพธ์ออกมานั้นเอง
หากใครสนใจดูตัวอย่างโปรเจคของการเขียนโค้ดแบบ MVVM ที่ใช้ Rxjava สามารถ check out โค้ดออกมาดูได้จากโค้ดตัวอย่างนี้ด้านล่างเลยครับ
Conclusion
ในบทความนี้ เราได้เรียนรู้แล้วว่า Subject คือตัวที่ทำหน้าที่เป็นทั้ง Observable และ Subscriber ในตัวเดียวกัน ซึ่งมีถึง 4 แบบให้เราเลือกใช้ได้แก่ PublishSubject
, BehaviorSubject
, ReplaySubject
และ AsyncSubject
โดยที่แต่ละแบบมีลักษณะที่แตกต่างกัน ซึ่งในบทความเราได้ยกตัวอย่างของการนำ Subject ไปใช้กันให้เห็นไปบ้างแล้ว ซึ่งการจะเลือกใช้ Subject แบบไหนนั้น ก็ขึ้นอยู่กับงานที่ผู้ใช้จะทำ หากเราต้องการให้ทุกครั้งที่ subscribe
จะต้องได้ Item มาเสมอ ก็ให้เลือกใช้ระหว่าง BehaviorSubject
หรือ ReplaySubject
แต่หากเราต้องการจัดการกับ Item ล่าสุดเสมอโดยไม่สนใจ Item ที่ปล่อยออกมาก่อนหน้า ก็ให้เลือกใช้ PublishSubject
แทนครับ และอย่างสุดท้ายหากต้องการแค่ Item ตัวสุดท้าย ก็เลือกใช้เป็น AsyncSubject
ครับ
มาถึงบทความสุดท้ายแล้ว ก็หวังว่า ตลอดบทความที่ผ่านมาจะช่วยให้ผู้อ่าน รู้จัก และ เข้าใจ การทำงานของ RxJava2 มากขึ้น และหวังว่าจะสามารถนำความรู้นี้ไปต่อยอดได้ ทั้งนี้หากมีข้อผิดพลาดประการใด ต้องขออภัยมา ณ ที่นี้ด้วยครับ
สุดท้ายนี้ก็เช่นเคยครับ หากผู้อ่านคิดว่าบทความนี้มีประโยชน์ ก็ฝากกด 👏 เป็นกำลังใจให้ด้วยนะครับ