มาลองใช้ Coroutine Channels ในการรับและส่งข้อมูลกันเถอะ

Sarayut.Wia
te<h @TDG
Published in
2 min readMay 26, 2020
source: https://repository-images.githubusercontent.com/61722736/08e87280-62dc-11ea-8fed-a8a4a4ea865d

Channels คืออีกหนึ่งแนวคิดที่ถูกนำมาใช้ในการรับและส่งข้อมูลใน coroutine โดยจะประกอบไปด้วยผู้ส่ง, ตัวกลางและผู้รับ ซึ่งอธิบายให้เข้าใจง่ายๆได้ประมาณนี้

  • Sender เป็นคนที่ทำหน้าที่ส่งข้อมูล
  • Channels เป็นตัวกลางทำหน้าเหมือนท่อที่เชื่อมระหว่าง sender และ receiver
  • Receiver เป็นคนที่คอยรับข้อมูลที่ถูกส่งมาจาก sender

สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับ coroutine channels สามารถอ่านได้จากที่นี่

มาเริ่มกันเลย

ตัวอย่างของโค้ดจะเป็นการยกตัวอย่างการดึงรายชื่อของดอกไม้จาก server แล้วนำมาแสดงใน recyclerview

เริ่มที่การกำหนดค่า api (ในส่วนของการ build retrofit นั้นเข้าไปดูได้จากที่นี่)
ตรง method getFlowerListAsync() จะเขียนเป็นแบบ async-await ดังนั้น return type เลยใช้เป็น Deferred<out T> แบบนี้

Deferred<GetFlowerListResponseModel>

อธิบายเกี่ยวกับ Deferred สักนิด จาก document อธิบายไว้ว่าตัว deferred จะถูกสร้างโดย coroutine builder แบบ async

It is created with the [async][CoroutineScope.async] coroutine builder or via the constructor of [CompletableDeferred] class.

และค่าของ deferred ก็คือ job (รายละเอียดเกี่ยวกับ job เข้าไปอ่านได้จากที่นี่)
โดยเวลาเราได้ข้อมูลมาแล้วข้อมูลเหล่านั่นจะถูกเก็บเอาไว้ใน job การจะเอาค่าผลลัพธ์ออกมาจาก job นั้นจะใช้คำสั่ง .await()

ใน class RemoteFlowerDataSource จะมี method getFlowerList() ที่มีการ return type เป็น ReceiveChannel<out E> แบบนี้

ReceiveChannel<ResponseResult<List<FlowerModel>>>

อธิบายโค้ดในส่วนนี้

  • GlobalScope คือการสร้าง coroutine scope แบบ global scope ซึ่งจะไม่ผูกกับ job ใดๆและไม่สามารถยกเลิกได้ก่อนเวลาอันควร
  • CoroutineScope.produce ทำการ launche coroutine ขึ้นมาใหม่เพื่อที่จะทำการส่งค่าที่ต้องการเข้าไปใน channel
  • send(element: E) เป็นคำสั่งสำหรับส่งค่าเข้าไปใน channel
  • .await() เป็นคำสั่งสำหรับ get ค่าที่ต้องการออกมาจาก job

อธิบายโค้ดในส่วนนี้

  • .receive() เป็นคำสังที่ใช้ในการรับค่าออกมาจาก channel และจะทำการลบค่านั้นๆออกจาก channel หลังจากที่รับค่าไปแล้ว

ในส่วนของ use case ก็จะออกมาเป็นประมาณนี้ จะสังเกตุเห็นว่าเวลาเรียก method getFlowerList() จะไม่ได้ return type ที่เป็น

ReceiveChannel<ResponseResult<List<FlowerModel>>>

กลับออกไปแต่จะ return type เป็น coroutines flow แทน ที่ทำแบบนี้เพราะว่าใน view model จะสามารถใช้ pattern ของ flow ที่ให้โค้ดดูเข้าใจง่ายและดูเรียบร้อยมากขึ้น โดยการแปลงค่าจาก ReceiveChannel ไปเป็น Flow ก็ง่ายๆเลยแค่ใช้คำสั่ง .consumeAsFlow() เท่านี้เองหรือถ้าหากไม่ทำเป็น flow ก็ใช้คำสั่ง .consumeEach ในการรับค่าที่ถูก return มาแทน แต่มีข้อแม้ว่าจะใช้คำสั่งนี้ได้ต้องเรียกใช้ผ่าน coroutines builder หรือ suspend function เท่านั้นนะ

ในส่วนของ view model จะออกมาเป็นประมาณนี้สำหรับคนที่เคยใช้ flow มาแล้วก็คงคุ้นเคยกับ pattern แบบนี้กันดี มาอธิบายโค้ดกันนิดนึงสำหรับคนที่ไม่เคยใช้ flow

  • .onStart { … } จะเป็นส่วนที่ถูกเรียกเมื่อเริ่มการทำงานของ flow
  • .onCompletion { … } จะถูกเรียกเมื่อจบการทำงานหรือมีการสั่งยกเลิกการทำงาน
  • .onEach { … } จะเป็นการรับค่าที่ถูก return กลับมา

จบแล้ววววววว สำหรับแนวทางการใช้ coroutines channel

สรุป

Coroutines Channels ก็เป็นอีกทางเลือกหนึ่งสำหรับใช้ในการจัดการ การรับและส่งข้อมูลให้มีประสิทธิภาพ สำหรับคนที่สนใจดู source code ตัวเต็มสามารถดูจากที่นี่ได้เลย หวังว่าบทความนี้จะเป็นประโยชน์สำหรับคนที่เข้ามาอ่านนะครับและถ้ามีข้อผิดพลาดตรงไหนก็ขออภัยด้วยครับ

References

--

--