บทความนี้เราจะมาเล่นกับ JSON บน Android กันครับ
จุดประสงค์หลักเราจะทำ Http request ไปที่ api และนำข้อมูลกลับมาใช้งานกันครับ
เริ่มจาก format ง่ายๆก่อน
Permission required
<uses-permission android:name="android.permission.INTERNET"/>
Dependency
//RETROFIT ... NETWORK LIBRARY
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
// RETROFIT .. CONVERTER & ADAPTER
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
// RX-ANDROID
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
// RECYCLERVIEW
implementation 'com.android.support:recyclerview-v7:25.3.1'
INetworkAPI.kt
import io.reactivex.Observable
import retrofit2.http.GET
interface INetworkAPI {
@GET("posts/")
fun getAllPosts(): Observable<List<Post>>
}
// Observable เป็นความสามารถของ RxAndroid
ถ้าใครใช้ Retrofit แบบไม่มี RxAndroid ตามที่เห็นส่วนมากจะใช้ Call
// List<Post> จะเป็นข้อมูลที่จะถูก return ไปที่ MainActivity.kt
เพื่อส่งเข้าไปใช้ใน Adapter ของ RecyclerView ตรง >>> PostItemAdapter(it, this)
Post.kt
import com.google.gson.annotations.SerializedName
data class Post(
@SerializedName("userId") val userId: Int,
@SerializedName("id") val id: Int,
@SerializedName("title") val title: String,
@SerializedName("body") val body: String
)
// SerializedName พวกนี้จะหมายถึง key ของข้อมูลที่ส่งกลับมาในรูปแบบ JSON
เราแค่ตั้งมันให้ตรงกับ https://jsonplaceholder.typicode.com/posts/ ทำให้ง่ายมาก
ต่อการ ดึงมันออกมาใช้งานใน onBindViewHolder ของ Recycler View
หรือจะ setให้ View ตรงๆเลยใน MainActivity กรณีที่ไม่ได้ใช้ RecyclerView
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_list_posts"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:listitem="@layout/item_list"
app:spanCount="2"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" />
// ถ้าจะใช้เป็น ListView ให้ใช้
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
</LinearLayout>
item_list.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/txtPostTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:textSize="16sp" />
<TextView
android:id="@+id/txtPostBody"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/colorPrimaryDark"
android:textSize="22sp"
android:textStyle="bold" />
</LinearLayout>
PostItemAdapter.kt
class PostItemAdapter(val postList: List<Post>, val context: Context) :
RecyclerView.Adapter<PostItemAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_list, parent, false))
}
override fun getItemCount(): Int {
return 10
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemView.txtPostTitle.text = postList[position].title
holder.itemView.txtPostBody.text = postList[position].body
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view)
}
MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// กรณีจะใช้เป็น GridView 2 column
rv_list_posts.layoutManager = GridLayoutManager(this,2)
// กรณีใช้เป็น ListView
rv_list_posts.layoutManager = LinearLayoutManager(this)
val retrofit = Retrofit.Builder().addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl("https://jsonplaceholder.typicode.com/").build()
val postsApi = retrofit.create(INetworkAPI::class.java)
บรรทัดต่อจากนี้ ลองหลับตานึกถึงหน้า 1 หน้า
ที่มี ListView แนวนอนหรือแนวตั้งอยุ่ในหน้าเดียวกันดูครับ บลาๆ
val response = postsApi.getAllPosts()
// observeOn ระบุว่าให้ส่งผลลัพธ์ไปบน foreground (mainThread) ด้วย
"AndroidSchedulers.mainThread()" คือความสามารถของ RxAndroid
ส่วน subscribeOn ใช้ระบุว่า ให้รอการ load ใน background และส่งผลลัพธ์กลับมาในตัวแปร it
คือ Object ของข้อมูลที่จะถูกส่งกลับมาตามที่ระบุไว้ใน Interface ของ Retrofit
(ดูที่ INetworkAPI.kt )
response.observeOn(AndroidSchedulers.mainThread()).subscribeOn(IoScheduler()).subscribe {
rv_list_posts.adapter = PostItemAdapter(it, this)
}
}
ท้ายสุด
จบ Tutorial นี้แนะนำให้ไปดูกันต่อในเรื่อง getAllUsers , getUserWithID , postData กันต่อเลยครับ
โดยเฉพาะ getUserWithID เราจะได้เจอกับ JSON ที่มี child ซ้อนกันถึง 2 ชั้น
แบบนี้
แต่ยังไงถ้าทำตาม Tutorial นี้จบ ก็ไปต่อกันไม่ยากแล้วครับ ... Goog Luck :D
เพิ่มเติมการใช้ notify แบบต่างๆ
เช่น เมื่อผู้ใช้เลื่อนถึง item ล่างสุดของ Recycler view จะทำการ Request ไปที่ API
เพื่อขอ Data มาเพิ่ม และสั่ง notify ให้เกิดการเปลี่ยนแปลง
คีย์เวิร์ด
notifyItemChanged(int pos) // แจ้งเตือนรายการที่ตำแหน่งมีการเปลี่ยนแปลง
notifyItemInserted(int pos) // แจ้งว่ารายการที่สะท้อนอยู่ในตำแหน่งถูกแทรกใหม่
notifyItemRemoved(int pos) //แจ้งเตือนว่ารายการที่เคยอยู่ที่ตำแหน่งก่อนหน้านี้ถูกลบออกจากชุดข้อมูล
notifyDataSetChanged() // แจ้งว่าชุดข้อมูลนั้นมีการเปลี่ยนแปลง ใช้เป็นทางเลือกสุดท้ายเท่านั้น
Ref :
http://developine.com/kotlin-android-json-parsing-tutorial-retrofit/
https://stackoverflow.com/questions/35679776/how-to-set-recyclerview-applayoutmanager-from-xml
http://www.akexorcist.com/2016/08/introduction-to-the-rxjava-and-rxandroid-part-2.html
https://blog.nextzy.me/%E0%B9%80%E0%B8%A3%E0%B8%B5%E0%B8%A2%E0%B8%81-rx-%E0%B8%A7%E0%B9%88%E0%B8%B2%E0%B8%A3%E0%B8%AD-rxandroid-54dda8de108