Back to Basic of Fuel เขียนเรียก API แบบไม่ยุ่งยาก

Android Sep 20, 2018

คราวก่อนใช้ท่ายากไปนิดนึง ดูไม่ยืดหยุ่น แถมเจอหลายๆเคสเพิ่มเติมด้วย เลยกลายเป็นบล็อกตอนสองออกมา

วาดเองจากเครื่องเทส อย่าตัดเครดิตเน้อ เดี๋ยวเราจะมาอธิบายแต่ละส่วนกัน

ย้อนความเดิมตอนที่แล้ว เราได้ใช้ท่ายากในการทำ Routing Class เพื่อพูดคุยกับ API แต่แล้วเราค้นพบว่าการใช้งานในบางกรณีมันไม่ยืดหยุ่นเอาซะเลยนี่สิ แถมรู้สึกว่าเหมือนเราสร้าง 2 class คือ routing และ presenter แยกกันแบบเปลืองๆยังไงไม่รู้แหะ

วิธีเปลี่ยนกระบวนท่า จาก Retrofit ไป Fuel
โปรเจกเดิมเป็น Retrofit โปรเจกใหม่ใช้ Fuel โดยคนในทีม พอเราใช้เลยงงเลยทีนี้ อะแง มันจะยากไหมน๊า

ดังนั้นเราจึงต้องมา Back to Basic มาใช้แบบง่ายๆกันเถอะ

ถ้าจำบล็อกก่อนได้ (ถ้าไม่ได้อ่านข้ามตรงนี้ได้นะ)

เราบอกว่าใน Kotlin แนะนำให้ลองใช้ Fuel ซึ่งเรานำเสนอท่ายากไป ดังนั้น เราจึงเอามาเปรียบเทียบกับ Retrofit (ซึ่งลืมไปแล้วว่าเขียนยังไง ฮ่าๆ) ว่ามีอะไรเหมือนหรือต่างกัน

ซึ่งต่างกันที่ gateway ของ Retrofit และ routing ของ Fuel นั่นเอง เรียกง่ายๆว่าใช้ท่าไม่เหมือนกันนั่นเอง

เรามองว่าพอมาแยก class ส่วน Routing และ Presenter พบว่าเหมือนสร้าง class เยอะเกินความจำเป็นในส่วนของ Routing เลยจะรวบให้อยู่ใน Presenter ให้หมดซะเลย (เพราะไม่อยากให้โปรเจกมันบวมไปกว่านี้ บวกกับขี้เกียจหาเวลาต้องมาแก้อะไรบางอย่างใน Routing นั่นเอง ผ่ามมม) ซึ่งถ้าเทียบจำนวนบรรทัดแล้วคุ้มค่าต่อการลบ class Routing มาก ฮ่าๆ ดังนั้นเราค่อยๆย้ายมาอยู่ที่ Presenter ให้หมดเลย ดังรูปที่วาดน้ี

ซึ่งหน้าตาวิธีการย้ายนั้น ไม่ได้ยากอะไรนัก แบบในรูปนี้ ตัว Routing เป็น sealed class เนอะ ภายในประกอบได้ด้วย header, method, params และ path ถูกรวบไปเป็น sub-class ของ Routing class อีกทีนึง ตอน request

การทำ Fuel ขั้นพื้นฐาน (ถ้าไม่ได้อ่านบล็อกที่แล้ว เริ่มอ่านตรงนี้ได้จ้า)

เราสามารถใช้งาน Fuel ได้ไม่ยากเลยจ้า มีรูปแบบการใช้งานที่ดูเป็นมิตรแบบนี้

Fuel.<method>(<path>, <params>)
    .header(<header>)
    .responseObject(ModelClass.Deserializer()) {
        ...
}
  • Method : หลักๆที่เราใช้มี Get, Post, Put นะ ซึ่งครอบคลุมนะ
  • Path : url ของ API ที่เราเรียกใช้นั่นเอง
  • Params : parameter นั่นเอง โดยเจ้านี่เป็น type List<Pair<String, Any?>>? วิธีการใส่ คือ listOf(“key” to “value”)
  • Headers : ก็คือ header นั่นเอง ว่าเราจะใส่ค่าอะไรเข้าไป เช่น access token, api key หรือต่างๆนานา โดย headers จะต้องใส่เป็น type Map<String, String>? วิธีการใส่ คือ mapOf(“key” to “value”)

จริงๆทั้งหมดทั้งมวลสามารถไปอ่านที่ library เขาได้เลยหนาาา

kittinunf/fuel
The easiest HTTP networking library for Kotlin/Android - kittinunf/fuel

ลองมาสับเปลี่ยนกระบวนท่ากันเถอะ อันนี้สร้างแบบ basic ตามที่อธิบายตามขั้นต้น

อันนี้แบบยาก สร้าง class Routing กับ Presenter แยกกัน

หลังจากบล็อกที่แล้วที่เขียนเกี่ยวกับ Fuel เราก็ได้เจอหลากหลายตัวอย่างมากขึ้นนะ มีอะไรบ้างน๊อออ

การรับ data ก้อนนึงจาก backend มา แล้วเป็น ArrayList ของ Object ตัวนึง

อันนี้มีคนถามมาแล้ว ขอเล่าอีกรอบนึงแล้วกันเนอะ

สมมุติว่าได้ object ก้อนนึง เป็นประมาณนี้

{ "data": [{...}, {...}, {...}, ..., {...}]}

ซึ่งเจ้า {…} ก็คือ class model ที่เราสร้างขึ้น แต่เจ้า response object ที่ได้คือ ArrayList ของ model นั้นๆหน่ะสิ เมื่อเราใส่ response object ผิด ชีวิตเปลี่ยนทันที แอป crash จ้า

ดังนั้นรับเจ้า response body ที่เป็น json กันก่อน ด้วย responseJson และแปลงเป็น ArrayList ของ model ตัวนึง นั่นคือ ArrayList<Data> ซึ่งในที่นี้ชื่อตัวแปรว่า data เพราะนึกชื่อไม่ออกแล้วนี่เอง

และอย่าลืมสร้างเจ้า listener class เพื่อนำมา handle case ต่างๆด้วยนะ

interface DataListener {
    fun onLoadDataSuccess(video: ArrayList<Data>)
    fun onLoadDataFailure(error: FuelError)
}

เท่านี้ก็เรียบร้อย ใช้งานได้ตามปกติสุขหล่ะ

ยัดก้อน body เป็น json ที่ขาเข้าแล้ว แต่ฝั่ง backend ไม่เห็นเป็น json ง่ะ

แยกเป็น 2 ประเด็น คือ ทำก้อน json ขึ้นมา กับใส่ Content-Type ให้เป็น json

บาง API อาจจะให้ frontend ยัดก้อน json เขาไปในส่วนของ body เนอะ ซึ่งการสร้างเจ้าก้อน json นั้นก็ไม่ได้ยากเท่าไหร่นัก

สมมุติแล้วกันว่า เรากด clap ในแอปอ่านบล็อกนึงที่คล้ายๆ medium ให้กับบทความนึง แล้วเราส่งเจ้า slug ของ blog และจำนวนที่เรา clap ดังนั้นเราสร้าง json

val json = JSONObject()
json.put(PARAM_CLAP_COUNT, shakesCount)
json.put(PARAM_BLOG_SLUG, slug)

พอมาใส่ใน function หนึ่งใน presenter class ก็จะเป็นแบบนี้

เราก็คิดว่ามันน่าจะจบแล้วเนอะ ยัดเจ้า body ไปแล้วอ่ะ run บนเครื่องเราน่าจะใช้ได้แล้วแหละ

แต่มันไม่เป็นแบบนั้นค่ะคุณณณณณณณณณณ เข้า failure จ้า

นี่นั่ง check กับ backend dev ว่าใส่ถูกไหม มันไม่น่าจะมีตรงไหนผิดหรอก แต่ๆๆๆๆ ตอนที่ฝั่ง backend ได้มานั้น ดั๊นนนน ไม่ใช่ type json หล่ะสิ ดันขึ้น Content-Type เป็น application/x-www-form-urlencoded แทนที่จะเป็น application/json หล่ะสิ

ดังนั้น เราต้องใส่เจ้า Content-Type ที่ header ด้วย เพื่อบอกว่าก้อนนี้เป็น json นะเว้ย แบบนี้

header(mapOf("Content-Type" to "application/json"))

ทั้งหมดทั้งมวลจะเป็นแบบนี้จ้า คราวนี้ก็จะถูกต้องสมบูรณ์แล้วจ้า รอดไปอีกหนึ่งที

upload file ไปให้ API

บางแอปอาจจะอัพอะไรบางอย่างขึ้นไป เช่น อัพรูป อัพวิดีโอ อัพไฟล์ อะไรแบบนี้ ซึ่งเจ้า Fuel สามารถเขียนเจ้า upload ได้แบบง่ายๆ สบายๆ ซึ่งไฟล์ที่รองรับก็น่าจะได้หมดนะ เท่าที่เห็นตัวอย่าง และมีหลายท่าด้วยนะ

สมมุติอัพรูปและวิดีโอขึ้นไปแล้วกันนะ ที่ใช้ท่า dataParts เพราะว่าเราอัพรูปและวิดีโอขึ้นไปเนอะ ซึ่งเราก็รู้ type และนามสกุลไฟล์ของมันอยู่แล้ว เราจึงสร้าง list ของ DataPart มาเพื่อให้เอาไฟล์จากในนี้เอาขึ้นไปหน่อยนะ ประมาณนี้

.dataParts { _, _ ->                    
   listOf(DataPart(story, name = FILE_VIDEO, type = "video/mp4"),                             
          DataPart(image, name = FILE_IMAGE, type = "image/jpeg")) 
}

ในกรณีที่ internet ช้า และทำให้เข้า failure เนื่องจาก timeout เราก็สามารถ set timeout ได้นะเออ ซึ่งเจ้า timeout ใน Fuel จะมีให้ใช้ 2 ตัว เช่นกัน

  • timeout : อันนี้ใช้ฝั่ง request เนอะ
  • timeoutRead : เจ้านี่มัน read ใช้กับ response

ซึ่งทั้งสองตัวนั้น จะมี default ที่ 15,000 ms คิดเป็น 15 วินาทีนั่นเอง

ดังนั้นการใช้งานก็แสนจะง่าย คือ เราสามารถกำหนดเวลาที่ timeout ซึ่งใส่เป็น parameter ได้แบบนี้

.timeoutRead(10 * 60 * 1000)
.timeout(10 * 60 * 1000)

เวลาใส่ก็ใส่ก่อน responseObject อะเนอะ ทั้งหมดทั้งมวลส่วน upload

ตอนนี้ก็ถือว่าน่าจะครบกระบวนท่าการใช้ Fuel เพียงเท่านี้ ก็ยาวพอสมควรเลยนะเนี่ย แต่เอาจริงๆบทความหรืออะไรต่างๆที่เกี่ยวกับ Fuel ก็น้อยเหลือเกิน


สุดท้ายฝากร้านกันสักนิด ฝากเพจด้วยนะจ๊ะ

อย่าลืมกด like กด share บทความกันด้วยนะคะ :)

Posted by MikkiPastel on Sunday, 10 December 2017

Tags

Minseo Chayabanjonglerd

I am a full-time Android Developer and part-time contributor with developer community and web3 world, who believe people have hard skills and soft skills to up-skill to da moon.