Kotlin을 사용하여 JSON 데이터 가져오기
JSON 데이터 같은 경우는 assets 폴더 내에 있는 JSON 파일들만 인식할 수 있기 때문에 JSON 데이터를 다루려면 assets 폴더가 필요하다.
처음 생성한 프로젝트에는 assets 폴더가 없으니 생성해주어야 한다.
단순히 json 데이터를 가져와 사용하는 것은 쉽지만, json 내에서 단순히 object만 사용하는 게 아니라 array도 사용하고, object와 array를 함께 사용하는 경우도 있다.
개인적으로 object와 array를 혼합하여 사용하는 것 때문에 애를 먹었다.
폴더 생성
assets 폴더 내에 json 파일을 위치시키면 사용할 수 있다.
test.json이라는 파일을 아래와 같이 만들어 assets 폴더 안에 추가한다고 가정한다.
{
"code": 200,
"data": {
"store_list": [
{
"store_name": "서울지점",
"image_url": "//images.unsplash.com/photo-1546874177-9e664107314e?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1169&q=80"
},
{
"store_name": "인천지점",
"image_url": "//images.unsplash.com/photo-1645023756214-ed5802f6cd25?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80"
},
{
"store_name": "부산지점",
"image_url": "//images.unsplash.com/photo-1625899139925-57f71ba783b4?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1634&q=80"
},
{
"store_name": "경주지점",
"image_url": "//images.unsplash.com/photo-1633265637677-3b467d86cad3?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80"
},
{
"store_name": "전주지점",
"image_url": "//images.unsplash.com/photo-1548115184-bc6544d06a58?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80"
}
]
}
}
json 파일 내 데이터를 text 형태로 모두 불러올 것이다. 즉 JSON을 String으로 변환하는 것(JSON to String)이다.
assets 폴더에 접근할 수 있는 곳은 Activity뿐이다.
따라서 데이터 처리를 위한 Adapter를 생성하였다 해도 그 안에서 json 파일을 가져올 수는 없다.
Activity
package com.example.myapplication
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.recyclerstickyapp.adapter.CustomAdapter
import com.example.recyclerstickyapp.databinding.ActivityMainBinding
import org.json.JSONObject
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var sAdapter: StoreAdapter
private lateinit var horizonManager: LinearLayoutManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val json = assets.open("test.json").reader().readText()
val data = JSONObject(json).getJSONObject("data")
cAdapter = CustomAdapter(data)
horizonManager = LinearLayoutManager(this@MainActivity)
horizonManager.orientation = LinearLayoutManager.HORIZONTAL
binding.recyclerStore.apply {
layoutManager = horizonManager
adapter = cAdapter
}
}
}
RecyclerView로 구현하였다.
[Android][Kotlin] Binding 사용하여 RecyclerView 만들기
Binding 사용하여 RecyclerView 만들기 Binding을 사용하여 생각보다 뚝딱뚝딱 RecyclerView를 만들어볼 수 있다. 안드로이드는 (아마도) 앞으로 할 예정이 없고, 할 마음도 없기 때문에 어떻게 구현되는
mimah.tistory.com
import org.json.JSONObject
val json = assets.open("test.json").reader().readText()
val data = JSONObject(json).getJSONObject("data")
Activity에서 json 데이터를 모두 가져온다.
open() 인자에는 가져올 json 파일의 파일명이 들어가고, reader().readText()를 사용해 json 파일 내에 있는 데이터를 String 형태로 변환하여 가져온다.
데이터를 가져오고 나서 접근할 때 JSONObject인지, JSONArray인지 잘 살펴보아야 한다.
현재 test.json은 {}로 감싸져 있으니 Object 형태인 것이다. 따라서 처음에는 JSONObject로 접근한다.
{} 내 키 값인 "data" Object를 가져오고, 해당 Object를 ViewHolder인 Adapter로 넘겨줄 것이다.
CustomAdapter라는 kt 파일을 생성해준다.
package com.example.myapplication.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.recyclerstickyapp.R
import android.widget.ImageView
import com.bumptech.glide.Glide
import org.json.JSONArray
import org.json.JSONObject
class CustomAdapter(private val datas: JSONObject) :
RecyclerView.Adapter<CustomAdapter.CustomViewHolder>() {
private val listStore = datas.getJSONArray("store_list")
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
return CustomViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_list, parent, false)
)
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
holder.bind(listStore)
}
override fun getItemCount(): Int {
return listStore.length()
}
class CustomViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val tvStoreNm: TextView = view.findViewById(R.id.store_name)
val tvStoreImg: ImageView = view.findViewById(R.id.img_url)
fun bind(listStore: JSONArray) {
val iObj = listStore.getJSONObject("$position".toInt())
val name = iObj.getString("store_name")
var imgUrl = iObj.getString("image_url")
tvStoreNm.text = name
Glide.with(itemView)
.load("http:"+ imgUrl)
.circleCrop()
.into(tvStoreImg)
}
}
}
MainActivity에서 가져온 json 데이터 값을 CustomAdapter의 인자로 넣는다. 이렇게 하면 ViewHolder에서도 json 데이터를 사용할 수 있다.
"data" object 안에 있는 "store_list"의 value는 object 형태가 아닌 array 형태이기 때문에 getJSONArray()로 가져와야 한다.
처음에는 어디 부분이 object고 어느 부분이 array로 가져와야 하는지 헷갈릴 수 있는데 몇 번만 쓰다 보면 금방 익숙해진다. 익숙해지면 json 데이터 내 object와 array가 혼용되어 사용되어도 쉽게 데이터 값을 뽑아낼 수 있다.
"store_list" array 안에 들어있는 값을 한 개씩 차례대로 꺼내기 위해 "$position"을 사용한다.
("$position".toInt() 값을 사용하지 않고 반복문 사용도 가능하다.)
array 안에 들어있는 값은 이번엔 object 형태이기 때문에 getJSONObject()로 가져온다.
가져온 object의 value 값을 getString(Key)으로 꺼내어 사용하면 된다.
참고로 ImageView의 src 값에 주소 값을 넣는 것은 Glide 라이브러리를 사용하여 할 수 있다. Gilde 라이브러리 사용법은 잘 정리된 다른 블로그들이 많기 때문에 따로 참조하는 것을 추천한다.
json 데이터를 보면 image_url 앞부분에 http:를 작성하지 않았기 때문에 load에서 바로 "http:" 문자열을 image_url 앞에 추가해주었다.
circleCrop()을 추가하면 가져온 이미지를 원 모양으로 예쁘게 만들 수 있다.
정말정말 간단하게만 살펴보는 Glide 라이브러리 사용법
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:padding="16dp">
<TextView
android:id="@+id/title_store"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="추천 지점"
android:textSize="27dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_store"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:orientation="horizontal"
android:scrollbars="horizontal"
app:layout_constraintTop_toBottomOf="@+id/title_store"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
RecyclerView로 구현하였다.
item_list.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="100dp"
android:background="@drawable/imgview_style">
<ImageView
android:id="@+id/img_url"
android:layout_width="50dp"
android:layout_height="50dp"
app:layout_constraintBottom_toTopOf="@id/store_name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.044"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/store_name"
android:layout_width="80dp"
android:layout_height="20dp"
android:text="Store"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.044"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/img_url" />
</androidx.constraintlayout.widget.ConstraintLayout>
앞서 CustomAdapter에서 설정해준 데이터 값들이 들어간다.