Thursday, March 28

Android Retrofit 2 วิธีรับ Response หลายแบบ

Google+ Pinterest LinkedIn Tumblr +

ตอนนี้คงไม่มีใครไม่รู้จัก Library Retrofit2 ที่ช่วยเพิ่มเวลานอนให้กับ Android Developer อย่างเราแล้ว แถมยังมีคนทำ PlugIn ดีๆมาให้ใช้ร่วมกันอีกมากมาย อย่าง Gson ที่แปลง Response จับยัดเข้า Model ให้เราเอาใช้งานได้เลย

ใครยังไม่รู้จัก Retrofit2 ลองดูจากบทความเหล่านี้ได้เพื่อความเข้าใจ

Retrofit อัพเดตครั้งใหญ่ สู่เวอร์ชั่น 2.0 อย่างเป็นทางการ พร้อมฟีเจอร์และการเปลี่ยนแปลงเพียบ
[Android] มาเชื่อมต่อ Network ด้วย Retrofit2(อย่างง่ายๆ) กันเถอะ
[Android] ผลจากการอัพโหลดรูปด้วย Retrofit2 เลยได้ลองใช้ Annotation ตัวอื่นๆบ้าง

แต่เราคงเคยเจอท่าแปลกๆอย่างกันส่ง Response แบบปกติมาเป็น List ของ Object แต่พอส่ง Error Response ดันเป็น Json object ธรรมดา ผลก็คือเข้า onFailure เพราะ Parse Json เข้า Model ไม่ได้ ปัญหาจึงบังเกิด

ตัวอย่าง Response แบบธรรมดา

[xml] [
{
"moviename": "Iron Man",
"year": "2008"
},
{
"moviename": "Iron Man2",
"year": "2010"
},
{
"moviename": "Iron Man3",
"year": "2013"
}
]
[/xml]

ตัวอย่าง Response แบบ Error

[xml] {
"status": "no",
"reason": "not match token"
}
[/xml]

การทำงานตามปกติ

แบบปกติเราสามารถรับมาเป็น Call List ของ MovieModel กันได้เลยแบบนี้

[java] @FormUrlEncoded
@POST("Movie/list")
Call<List<MovieModel>> requestMovieList(
@Field("token") String token,
@Field("accountNo") String accountNo);
[/java]

พอตอน onResponse ก็แค่รับ response.body() ไปใช้ได้เลยแบบนี้
[java] callMovieList.enqueue(new Callback<List<MovieModel>>() {
@Override
public void onResponse(Call<List<MovieModel>> call, Response<List<MovieModel>> response) {
if (response.isSuccessful()) {
movieList = (ArrayList<MovieModel>) response.body();
/**Do something**/
} else {
/**Do something**/
}
}

@Override
public void onFailure(Call<List<MovieModel>> call, Throwable t) {
/**Do something**/
}
});
[/java]

เราแค่นำ movieList ไปใช้ได้เลยง่ายเนอะ แต่!!! ถ้าเกิดอาการอย่าง token หรือ accountNo ที่ส่งไปกับ Request ผิดล่ะ ผลก็คือมันจะตอบมาแบบ Response Error ไง แต่มันจะไม่ไปเข้า onResponse ดันไปเข้า onFailure เพราะเกิดการ Error เนื่องจาก Parse Json ของ Object ไปใส่ List ของ MovieModel ไม่ได้เลยเกิด Error ขึ้นมา แถมจะไปแกะออกมาดูเพื่อเอา Json Object “reason” ก็ไม่ได้

แต่ปัญหามีทางแก้ครับ

***วิธีนี้อาจจะไม่ใช่วิธีที่ดีที่สุด ใครมีวิธีที่ดีกว่านี้แนะนำมาได้นะครับ***

ก่อนอื่นสร้าง ErrorResponseModel ขึ้นมาก่อนเพื่อจะเอาไว้รับ Response ที่ Error

[java] public class ErrorResponseModel {
private String status;
private String reason;

/*** Get Set Medthod***/
}
[/java]

วิธีการของผมคือ เปลี่ยน Request จาก Call List ของ MovieModel เป็น Call Object แทนแบบนี้
[java] @FormUrlEncoded
@POST("Movie/list")
Call<Object> requestMovieList(
@Field("token") String token,
@Field("accountNo") String accountNo);
[/java]

พอตอน onResponse ต้องใช้ท่ายากนิดนึง คือเช็ค object ที่ได้มาว่าเป็น ArrayList ใช่หรือไม่ ถ้าใช่นั่นคือ Response ที่เราต้องการแต่ถ้าไม่ใช่มันคือ Error Response ดังนั้นจึงได้ออกมาแบบนี้

[java] callMovieList.enqueue(new Callback<Object>() {
@Override
public void onResponse(Call<Object> call, Response<Object> response) {
if (response.isSuccessful()) {
if (response.body() instanceof ArrayList) {
movieList = (ArrayList<MovieModel>) response.body();
ArrayList<MovieModel> tempMovieList = new ArrayList<MovieModel>();
for (int i = 0; i < movieList.size(); i++) {
Gson gson = new Gson();
JsonObject jsonObject = gson.toJsonTree(movieList.get(i)).getAsJsonObject();
MovieModel movieModel = gson.fromJson(jsonObject, MovieModel.class);
tempMovieList.add(movieModel);
}
movieList = tempMovieList;
/**Do something with movieList**/
} else {
Gson gson = new Gson();
JsonObject jsonObject = gson.toJsonTree(response.body()).getAsJsonObject();
ErrorResponseModel errorModel = gson.fromJson(jsonObject, ErrorResponseModel.class);
/**Do something with error response**/
}
} else {
/**Do something**/
}
}

@Override
public void onFailure(Call<Object> call, Throwable t) {
/**Do something**/
}
});
[/java]

เอาทีละอย่างเดี๋ยวจะงงกัน

เริ่มกันตรงที่ if instanceof ArrayList ตรงนี้อย่างที่บอกว่าทำเพื่อดูว่าเป็น Response ปกติหรือไม่ ถ้าใช่ ด้านในคือการแปลง Object ไปเป็น ArrayList ของ MovieModel
การ Convert ครั้งแรกที่เอา response.body() ใส่ movieList เราจะไม่ได้ ArrayList ของMovieModel แต่มันคือ ArrayList ของ LinkedTreeMap ดังนั้นเราจะต้องแปลง LinkedTreeMap ให้กลายเป็น MovieModel อีกครั้งโดยใช้ Gson ซึ่งก็คือตรง for loop นั่นเอง เมื่อทำเสร็จเราก็สามารถนำ movieList ไปใช้ได้เลย

ส่วนตรง Else ที่ได้ Error Response มาเราก็ทำเหมือนกันคือแปลง LinkedTreeMap ให้กลายเป็น ErrorResponseModel แล้วเราค่อยเอาไปใช้งานต่อ

จบแล้วจ้า

สำหรับผมวิธีนี้ถือเป็นท่ายากพอสมควร อย่างที่บอกว่าถ้าใครมีวิธีที่ดีกว่านี้ช่วยแนะนำผมมาทีนะครับ ผมจะได้เอาไปใช้บ้าง หวังว่าบทความนี้จะช่วยให้คุณทำงานง่ายขึ้นนะครับ

Link อ้างอิง

http://stackoverflow.com/questions/35583308/how-can-we-handle-different-response-type-with-retrofit-2
http://stackoverflow.com/questions/38057172/how-do-i-convert-a-linkedtreemap-to-gson-jsonobject
http://stackoverflow.com/questions/7335018/cannot-perform-instanceof-check-against-parameterized-type-arraylistfoo

Facebook Comments
Share.

About Author

สวัสดีครับ ผมไอซ์ กมลวัฒน์ ผู้ก่อตั้งช่อง Youtube Kamonway และเว็บไซต์ kamonway.com เพื่อช่วยแนะนำความรู้จากเกม และเป็นช่องทางที่ช่วยให้ทุกท่านเล่นเกมอย่างสนุกสนานยิ่งขึ้น

Comments are closed.