ตัวอย่างการทำ ListView อ่านข้อมูล JSON ด้วย GSon
บทความนี้จะมาพูดถึง การเขียนแอพพลิเคชันแอนดรอยส์เพื่อดึงข้อมูล JSON โดยมาแสดงเป็น ListView ในส่วน ListView ก็จะใช้เป็น Custom ListView ครับ บทความนี้จะครอบคลุมเนื้อหาเรื่องใหญ่ๆ 3 เรื่องเลย ได้แก่
- การอ่านข้อมูล JSON
- การทำ Custom ListView
- การใช้ AsyncTask
ตัวอย่างในบทความนี้ ผมจะใช้ JSON API ของทาง Teamtreehouse ครับ จากลิงค์นี้ http://blog.teamtreehouse.com/api/get_recent_summary/ ซึ่งเป็น API ที่ไว้แสดงบทความในบล็อก หน้าตาของ JSON จะมีประมาณนี้
จากรายชื่อใน JSON ก็จะมี JSONObject ชื่อ posts นั้นมีรายชื่อบทความแต่ละบทความอยู่ ภายในแต่ละบทความ ก็จะมี id, url ของบทความ, ชื่อบทความ, วันที่โพส, ชื่อผู้โพส และ Path ของรูป thumbail เราจะใช้ข้อมูลพวกนี้แหละครับ มาแสดงในแอพของเรา
ในส่วน JSON รายละเอียดเชิงลึก จะไม่ขอพูดถึงนะครับ ใครอยากรู้อะไรเพิ่มเติม Google ครับ
หัวข้อในบทความ
- Step 1: เริ่มต้นสร้างโปรเจ็ค
- Step 2: เพิ่ม Library Gson
- Step 3: เพิ่มคลาส Model
- Step 4: วิธีการทำ Custom ListView
- Step 5: Implement CustomAdapter
Step 1: เริ่มต้นสร้างโปรเจ็ค
ทำการสร้างโปรเจ็คเลยครับ ขอข้ามในส่วนการสร้างโปรเจ็คไปเลยละกัน ใครทำไม่เป็น อ่านที่นี่ครับ การสร้างโปรเจ็คด้วย Android Studio ถ้าเป็น Eclipse ลองหา Google ครับ มีคนเขียนไว้เยอะมาก
เริ่มแรก ทำการสร้าง Layout สำหรับหน้าแรก ตั้งชื่อว่า activity_main.xml
เพื่อแสดงรายชื่อบทความ ก็ไม่มีอะไรมาก ใช้แค่ ListView
ธรรมดาได้เลย
ต่อมาในส่วนคลาส MainActivity.java
ผมทำการประกาศ static String ชื่อว่า URL ดังนี้
ต่อมาก็ใช้ AsyncTask เพื่อทำการอ่านข้อมูล JSON จากเว็บไซต์ สำหรับบทความ AsyncTask อ่านเพิ่มเติมได้ที่นี่ครับ
ด้านบน ผมทำการสร้างคลาส SimpleTask
โดยทำการ extends AsyncTask
เพื่อทำการอ่านข้อมูลจาก URL ที่กำหนด ในส่วน onPostExecute()
จะเห็นว่าเรียกเมธอด showData(String jsonString)
ซึ่งยังไม่ได้สร้างเมธอดนี้นะครับ
ทำการสั่ง execute ที่เมธอด onCreate()
จากนั้นเมธอด showData()
ก็ประกาศไว้แล้วใช้ Toast แสดงดูว่าอ่านค่าได้หรือไม่
สุดท้ายเพิ่ม Permission Internet ที่ไฟล์ AndroidManifest.xml
ด้วยครับ
ไฟล์ MainActivity.java
ตอนนี้ก็จะได้ประมาณนี้
หาก Toast แสดงข้อมูล JSON ได้ แสดงว่าคุณได้ไปต่อครับ
หากใครไม่ขึ้นแสดงว่าคุณทำผิดแล้วละครับ ลองอ่านและทำดูใหม่ครับ :D
Step 2: เพิ่ม Library Gson
จะเห็นว่าเราสามารถแสดงข้อมูลที่ได้จากเว็บได้แล้ว ที่เป็น JSON ทีนี้ เราจะอ่านมันยังไงละ เผอิญว่า Java นั้นมี Library สำหรับ Json โดยเฉพาะครับ เด่นๆ ก็จะมีพวก Gson, Jackson สำหรับบทความนี้ผมจะใช้ Gson นะครับ ใครไม่รู้จักก็อ่านเพิ่มเติมที่ เว็บไซต์ของ Gson เลยครับ
ทำการ ดาวน์โหลด Library ได้ที่นี่ ปัจจุบัน เวอร์ชั่น gson-2.2.4 นะครับ จากนั้นก็ทำการ Add Library / Add Build Path ครับ
สำหรับ Android Studio หรือ Eclipse ที่ใช้ Gradle ก็ไม่จำเป็นต้องโหลดก็ได้ครับ ตั้งค่า Gradle แล้วให้มัน Auto Load ให้เองครับ เปิด build.gradle
เพิ่มนี้ลงไป และกด Sync Now
Note:
compile 'com.google.code.gson:gson:2.2.4'
คือ GroupId:ArtifactId:version
ทดลองดูว่าเพิ่ม Library ได้ถูกต้องหรือไม่ ลองทำการ import class Gson ดูที่ MainActivity
ถ้า import มีปัญหา แสดงว่าเพิ่ม Library ผิด ลองกลับไปทำดูใหม่ครับ สำหรับ Eclipse อย่าลืม Add to Buld Path ด้วยนะครับ ส่วนใครที่ import ได้ไม่มีปัญหา แสดงว่าเพิ่ม Library ถูกแล้ว ไปขั้นตอนต่อไปได้เลยครับ
Step 3: เพิ่มคลาส Model
ขั้นตอนนี้ ผมจะทำการสร้างคลาสขึ้้นมาใหม่ครับ เนื่องจากการใช้ Gson นั้นจำเป็นจำเป็นจะต้อง Mapping คลาสเรา กับ JSON ครับ ในตอนแรก คิดว่าอาจจะงงๆเล็กน้อย หรือยังมองภาพรวมไม่ออก ก็พยายามลองทำก่อนครับ :D
เมื่อเปิดหน้า API จะเห็นว่า JSON ของทาง Teamtreehouse มีโครงสร้าง ประมาณนี้นะครับ
โดยในส่วนแต่ละโพส ก็จะมีรายละเอียดต่างๆเช่น id, title อื่นๆ ที่ได้พูดไปก่อนหน้านี้แล้ว ฉะนั้น ก็เลยสร้างคลาส Model เพื่อทำการ Mapping กับ JSON ครับ ทำการสร้างคลาสใหม่ขึ้นมา ตั้งชื่อคลาสว่า Post.java
จะเห็นว่า ที่ JSON มีค่าอะไรบ้าง ตรงส่วนคลาส Post ก็จะตั้งชื่อเหมือนกันเลย ในส่วน Getter, Setter ก็ Generate เอาเลยครับ
ต่อมาผมสร้างคลาสขึ้นมาใหม่อีกคลาสชื่อว่า Blog.java
สำหรับเอาไว้ Mapping ทั้งบล็อกเลย คนละส่วนกับ Post.java
นะครับ ซึ่ง Post.java นั้น Mapping เฉพาะโพสๆหนึ่งเท่านั้น แต่ Blog.java
จะ map ทั้ง JSON เลย ฉะนั้น โค๊ดก็จะได้ประมาณนี้
จะเห็นได้ว่า ชื่อตัวแปรในคลาส Blog ก็ใช้ชื่อเดียวกับ field ใน JSON ส่วนตัวแปรไหนที่ชื่อไม่ตรงกัน เพราะว่าปกติ Java ไม่นิยมตั้งชื่อด้วย (_) underscore ก็จะใช้ @SerializeName("")
แทนครับ
ทีนี้เมื่อสร้างคลาส Model 2 คลาสเสร็จแล้ว ทีนี้มาลองใช้งาน Gson กันเลยครับ กลับไปที่ MainActivity.java
ตรงเมธอด showData()
ที่่ตอนนี้แสดง Toast อย่างเดียวเลย ก็ใส่โค๊ดนี้ลงไปครับ
ด้านบน ทำการสร้าง new Instance Gson ขึ้นม จากนั้นก็ใช้ gson.fromJson()
โดย argument ตัวแรกคือ JSON และตัวสองเป็น คลาสที่เราต้องการจะ Map ครับ ในที่นี้ก็ใช้คลาส Blog
ทีนี้เวลาจะเข้าถึงข้อมูลก็เพียงแค่เรียก getter ที่เราประกาศไว้ เช่น getTitle(), getAuthor()
ครับ
ใน Blog ก็จะมีแต่ละ Post อยู่ ก็ทำการวนลูป แล้วแสดง Post ทั้งหมด โดยดึงเฉพาะ Title ด้วย Toast ดูครับ
ใครขึ้นแบบด้านบนแสดงว่า ทำถูกแล้ว
จริงๆเนื้อหาในส่วน Gson จริงๆมันมีแค่นี้เองครับ เมื่อเรียกข้อมูลได้แล้ว ที่เหลือก็เอาไปประยุกต์ได้หลากหลายครับ ในส่วนต่อไปของบทความนี้จะเป็นเรื่อง Custom View แล้วครับ หากใคร ทำ Custom View เป็นแล้ว ก็ข้ามส่วนนี้ไปได้เลยครับ
Step 4: วิธีการทำ Custom ListView
ทำไมผมถึงเอาเนื้อหา Custom ListView มาอยู่ตรงส่วนนี้?
ก็เพราะว่า โดยปกติแล้วผมมักจะเห็นการใช้งาน Custom ListView กับการดึงข้อมูลต่างๆมาจากเว็บไซต์ไม่ว่า FEED แบบ XML หรือว่า JSON ก็เลยเอามาประยุกต์รวมกับ Gson เลยเพื่อให้เห็นภาพ
ทีนี้ทำไมเราถึงต้องทำ Custom ListView ?
เนื่องจาก ListView ธรรมดา หรือแบบ ArrayAdapter นั้นมีข้อจำกัดอยู่ครับ คือจะแสดงได้เฉพาะ TextView แบบ แถวเดียว หรือสองแถวได้ แต่ถ้าเราอยากจะใส่ ImageView หรือ Button ด้วยก็จำเป็นต้องทำ Custom ListView ครับ
การทำ Custom ListView นั้นจำเป็นต้องมี 2 ส่วนครับ คือ หน้า Layout ไฟล์ ซึ่งเป็น xml และคลาส java 1 คลาส
เริ่มแรก ทำการสร้างเลเอาท์ที่ต้องการ โดยผมทำต้องการให้เป็นประมาณนี้
ทำการตั้งชื่อไฟล์ว่า post.xml
เซฟไว้ที่ /res/layout
ต่อมาก็สร้างคลาสขึ้นมาหนึ่งคลาส ผมตั้งชื่อคลาสใหม่ว่า CustomAdapter.java
แล้วทำการ extends BaseAdapter
และ override เมธอด ทั้งหมดแบบนี้
ปกติเวลาเราทำ ListView แบบธรรมดา เราต้องมีข้อมูลที่เป็น List, Array ด้วยใช่มั้ยครับ Custom ListView ก็ต้องการเหมือนกัน ทีนี้เราจะส่งข้อมูลมาให้คลาส CustomAdapter
นี้ได้ยังไง?
ก็ต้องสร้าง Constructor ให้มันครับ ผมสร้าง constructor แบบนี้
โดย constructor รับ parameter 2 ค่าครับ คือ Activity
และ List<Post>
ทีนี้ส่วนเมธอด ที่ได้ทำการ override มาก็แก้ไขเป็นด้งนี้
ส่วนในเมธอด getView()
ส่วนนี้คือส่วนที่จะแสดงผล คล้ายๆ setContentView()
ใน onCreate()
ของ Activity ครับ ในส่วนนี้ก็จะทำการเชื่อม View ในเลเอาท์ครับ ก่อนเชื่อม ผมได้ใช้ ViewHolder Pattern เข้ามาช่วย เพื่อเพิ่ม Perfomance ของ ListView ครับ หากใครอยากรู้รายละเอียด ก็ลองไปศึกษาเรื่อง Android ViewHolder Pattern ดูเพิ่มเติมครับ
ทำการสร้าง inner class ข้างใน CustomAdapter.java
ชื่อ ViewHolder
ขึ้นมา ภายในก็มี View เหมือนกับที่สร้างไว้ใน Layout
จากนั้นเมธอด getView()
ก็จะได้ประมาณนี้
เช็คว่า convertView เป็น null หรือไม่ ถ้าเป็น ก็ทำการสร้าง View ขึ้นมาใหม่ จาก Layout ที่ประกาศ ถ้าไม่ใช่ ก็ใช้ View อันเดิม เป็นการ Reuse นำกลับมาใช้ใหม่ครับ
สุดท้าย ประกาศตัวแปร global ไว้ในคลาส CustomAdapter
ทีนี้คลาส CustomAdapter ละไว้เท่านี้ก่อน กลับไปที่ MainActivity.java
อีกครั้ง
Step 5: Implement CustomAdapter
มาถึงขั้นตอนสุดท้าย คือการนำ CustomAdapter ที่สร้างไว้มาใช้งานนั่นเอง ก็กลับมาที่คลาส MainActivity.java
แล้ว ตรงส่วนเมธอด showData()
ที่ตอนนี้ทำได้คือ แสดง Toast เป็นรายชื่อบทความ ต่อไป เราจะเปลี่ยนให้แสดง ListView เริ่มแรก ก็ต้องสร้างตัวแปร 2 ตัวคือ ListView และ CustomAdapter ประกาศเป็น global ไว้ภายในคลาส
จากนั้นเชื่อม ListView กับ Layout ซะ ที่เมธอด onCreate()
ต่อมาที่เมธอด showData()
ก็ทำการ new CustomAdapter
มาใหม่ พร้อมกับส่ง argument เป็น Activity และ List<Post>
ไป และให้ setAdapter
ให้กับ ListView
สุดท้ายเมธอด showData()
จะเหลือแค่นี้ (ลบ Toast ทิ้งแล้ว)
ทดสอบรันโปรแกรม
จะได้ดังนี้ แต่ว่าทำไมไม่มีข้อมูลเลย มีแต่ View ที่เป็น Default จากไฟล์ xml?
ก็เพราะว่าเรายังไม่ได้เซตค่าให้มันที่ CustomAdapter เลย ตรงที่ผมละไว้ก่อนหน้านี้ ฉะนั้นกลับไปที่ CustomAdapter
ที่เมธอด getView()
เพิ่มนี้ลงไป
ปกติเราส่งข้อมูลมาแบบ List แต่ว่าที่เมธอด getView จะมี position อยู่ซึ่งทำให้เรารู้ว่า List ที่ตำแหน่งนี้จะเป้นค่าอะไร เราก็ get จาก ตำแหน่ง Position เก็บค่าไว้ที่คลาส Post ส่วน View ต่างๆ ก็ setText
จากค่าใน post ได้เลย
ส่วน ImageView ผมใช้วิธีนี้ เพิ่ม Bitmap และเปลียน Post เป็นตัวแปร global ภายในคลาส
จริงๆ วิธีการแสดงข้อมูล ImageView จาก URL วิธีนี้ ไม่ค่อยดีเท่าไหร่นะครับ เพราะไม่มี Perfomance อะไรเลย ไม่มีการเก็บ cache ตัวอย่างผมแค่แสดง ImageView จาก URL ได้เท่านั้น การใช้งานจริงๆแนะนำ Library สำหรับจัดการเรื่อง ImageView ดีกว่าครับ เช่น Universal Image Loader, Picasso, ion หรือ Volley
สุดท้าย getView()
ได้แบบนี้
ทดสอบรัน ผลลัพธ์ก็จะได้แบบนี้
จะเห็นว่า มีปัญหาที่ ImageView ฉะนั้นใช้ Library มาช่วยจะดีมากเลยครับ ปกติผมใช้ Universal Image Loader กับ ion ครับ แนะนำเลยครับ :D
ส่วนโค๊ดทั้งหมด ก็ดูได้จาก gist นี้ครับ
ที่ไม่อัพเป็น .git เนื่องจากว่า โปรเจ็คมันไม่ใหญ่มาก มีไม่กี่ไฟล์ ค่อยๆทำ แล้วดูไปทีละไฟล์ น่าจะช่วยให้เข้าใจง่ายกว่าการ import แล้วไปเปิดเทสเลย
- Authors
-
Chai Phonbopit
เป็น Web Dev ในบริษัทแห่งหนึ่ง ทำงานมา 10 ปีกว่าๆ ด้วยภาษาและเทคโนโลยี เช่น JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจในเรื่องของ Blockchain และ Crypto กำลังหัดเรียนภาษา Rust