ViewBinding๊ณผ Room, Retrofit2๋ฅผ ์ด์ฉํ๊ธฐ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ถ๊ฐํด ์ค๋๋ค.
๊ทธ๋ฆฌ๊ณ ์ฑ ์ ์ด๋ฏธ์ง๋ url๋ก ๋ค์ด์ค๋๋ฐ ์ด url์์ ์ด๋ฏธ์ง๋ฅผ ๊ฐ์ ธ์ค๋ ค๋ฉด Glide๋ผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
build.gradle (Module)
plugins {
...
id 'kotlin-kapt'
}
android {
...
buildFeatures {
viewBinding = true
}
}
dependencies {
...
// Retrofit2
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
// Glide
implementation 'com.github.bumptech.glide:glide:4.12.0'
// Room
def roomVersion = "2.4.2"
implementation "androidx.room:room-ktx:$roomVersion"
kapt "androidx.room:room-compiler:$roomVersion"
}
Retrofit2๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ์ธํฐ๋ท ๊ถํ์ ํ์ฉํด์ค์ผ ํ๋ค.
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.ta2gi.searchbook">
<!-- ์ธํฐ๋ท ๊ถํ -->
<uses-permission android:name="android.permission.INTERNET"/>
<application
...
</application>
</manifest>
MainActivity.kt
package com.ta2gi.searchbook
import android.app.Activity
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.inputmethod.InputMethodManager
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import com.google.android.material.snackbar.Snackbar
import com.ta2gi.searchbook.adapter.HistoryAdapter
import com.ta2gi.searchbook.adapter.SearchAdapter
import com.ta2gi.searchbook.databinding.ActivityMainBinding
import com.ta2gi.searchbook.fragment.HomeFragment
import com.ta2gi.searchbook.fragment.SearchFragment
import com.ta2gi.searchbook.retrofit2.BookDto
import com.ta2gi.searchbook.retrofit2.KakaoData
import com.ta2gi.searchbook.retrofit2.KakaoInfo.Companion.API_KEY
import com.ta2gi.searchbook.retrofit2.RetrofitService.kakaoService
import com.ta2gi.searchbook.room.HistoryDatabase
import com.ta2gi.searchbook.room.HistoryEntity
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class MainActivity : AppCompatActivity() {
// ๋ทฐ๋ฐ์ธ๋ฉ
lateinit var mainBinding : ActivityMainBinding
// ํ๋๊ทธ๋จผํธ๋ฅผ ๋ด์ ๋ณ์
lateinit var currentFragment : Fragment
// ๊ฒ์์ด ์ ์ฅ ์ฉ๋
var searchWord = ""
// ๊ฒ์ ๊ธฐ๋ก, ๊ฒ์ ๋ฆฌ์คํธ
var historyList = mutableListOf<HistoryEntity>()
var searchList = mutableListOf<BookDto>()
// ์ด๋ํฐ
val searchAdapter = SearchAdapter(this, searchList)
// ๋ฐ์ดํฐ๋ฒ ์ด์ค
lateinit var historyDatabase : HistoryDatabase
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ๋ทฐ๋ฐ์ธ๋ฉ ์ด๊ธฐํ
mainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mainBinding.root)
// ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ด๊ธฐํ
historyDatabase = HistoryDatabase.getInstance(this)!!
// ์คํ ์ ์ฒซ ํ๋ฉด HomeFragment
fragmentController("home", false)
}
// ํ๋๊ทธ๋จผํธ
fun fragmentController(view: String, add: Boolean) {
// ๋ค์ด์ค๋ view ๊ฐ์ ๋ฐ๋ผ ํ๋๊ทธ๋จผํธ ๋ณ๊ฒฝ
when (view) {
"home" -> currentFragment = HomeFragment(this)
"search" -> currentFragment = SearchFragment(this)
}
// ํ๋๊ทธ๋จผํธ ๊ต์ฒด ํด์ฃผ๊ธฐ
val fragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.act_main_screen, currentFragment)
// ๋ฐฑ์คํ
์ ์ถ๊ฐ
if (add) fragmentTransaction.addToBackStack(view)
// ์ ๋๋ฉ์ด์
ํจ๊ณผ ๋ฃ๊ธฐ
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
// ํ๋๊ทธ๋จผํธ ์คํ
fragmentTransaction.commit()
}
// ์ํ ๋ด๋ฆฌ๊ธฐ
fun hideKeyboard(act : Activity){
val imm = act.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(act.currentFocus?.windowToken, 0)
}
// ๋ ํธ๋กํ
fun searchResults(searchWord : String, fragment : SearchFragment) {
val book = kakaoService.getSearchBook(API_KEY, searchWord)
book.enqueue(object: Callback<KakaoData> {
override fun onResponse(call: Call<KakaoData>, response: Response<KakaoData>) {
// ํต์ ์ ์ฑ๊ณต ์ ๋ฆฌ์คํธ์ ์ฑ
๋ค ๋ด๊ณ ์ด๋ํฐ์ ๋ณ๊ฒฝ์ฌํญ ์๋ ค์ฃผ๊ธฐ
searchList.clear()
searchList.addAll(response.body()!!.documents)
searchAdapter.notifyDataSetChanged()
// ๋ฆฌ์คํธ๊ฐ ๋น์ด ์์ผ๋ฉด empty ๋ณด์ฌ์ฃผ๊ธฐ
if(searchList.isEmpty()) {
fragment.searchBinding.fraSeaEmptySearchList.visibility = View.VISIBLE
fragment.searchBinding.fraSeaSearchList.visibility = View.GONE
} else { // ๋ฆฌ์คํธ๊ฐ ์์ผ๋ฉด ๋ณด์ฌ์ฃผ๊ธฐ
fragment.searchBinding.fraSeaSearchList.visibility = View.VISIBLE
fragment.searchBinding.fraSeaEmptySearchList.visibility = View.GONE
}
}
override fun onFailure(call: Call<KakaoData>, t: Throwable) {
Log.d("๋ก๊ทธ", "ํต์ ์คํจ : ${t.message}")
}
})
}
}
HomeFragment.kt
package com.ta2gi.searchbook.fragment
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.android.material.snackbar.Snackbar
import com.ta2gi.searchbook.MainActivity
import com.ta2gi.searchbook.R
import com.ta2gi.searchbook.adapter.HistoryAdapter
import com.ta2gi.searchbook.databinding.FragmentHomeBinding
import com.ta2gi.searchbook.room.HistoryEntity
import kotlin.concurrent.thread
class HomeFragment(val mainActivity : MainActivity) : Fragment(), View.OnClickListener {
// ๋ทฐ๋ฐ์ธ๋ฉ
lateinit var homeBinding : FragmentHomeBinding
// ์ด๋ํฐ
val historyAdapter = HistoryAdapter(mainActivity, mainActivity.historyList, this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// ๋ทฐ๋ฐ์ธ๋ฉ ์ด๊ธฐํ
homeBinding = FragmentHomeBinding.inflate(inflater)
// ์คํ ์ ์ํ ์ฌ๋ผ์ค๋ ํ์ ๋ง๊ธฐ
homeBinding.fraHomSearchText.clearFocus()
mainActivity.hideKeyboard(mainActivity)
// ๊ฒ์ ๊ธฐ๋ก ๋์ฐ๊ธฐ
thread {
homeBinding.fraHomHistoryList.adapter = historyAdapter
mainActivity.historyList.clear()
mainActivity.historyList.addAll(mainActivity.historyDatabase.historyDao().getHistory())
historyAdapter.notifyDataSetChanged()
// ๋ฆฌ์คํธ๊ฐ ๋น์ด ์์ผ๋ฉด empty ๋ณด์ฌ์ฃผ๊ธฐ
if(mainActivity.historyList.isEmpty()) {
mainActivity.runOnUiThread {
homeBinding.fraHomEmptyHistory.visibility = View.VISIBLE
homeBinding.fraHomHistory.visibility = View.GONE
}
} else { // ๋ฆฌ์คํธ๊ฐ ์์ผ๋ฉด ๋ณด์ฌ์ฃผ๊ธฐ
mainActivity.runOnUiThread {
homeBinding.fraHomHistory.visibility = View.VISIBLE
homeBinding.fraHomEmptyHistory.visibility = View.GONE
}
}
}
homeBinding.fraHomSearchButton.setOnClickListener(this)
homeBinding.fraHomHistoryDeleteAll.setOnClickListener(this)
return homeBinding.root
}
override fun onClick(view : View?) {
when(view) {
// ๊ฒ์ ๋ฒํผ
homeBinding.fraHomSearchButton -> {
// ๊ฒ์์ด ๋ณ์์ ๋ด๊ธฐ
val searchText = homeBinding.fraHomSearchText.text.toString().trim()
// ๊ณต๋ฐฑ ์
๋ ฅ ์ ๋ฆฌํด
if(searchText.isEmpty()) {
Snackbar.make(homeBinding.root, "โ๊ฒ์์ด๋ฅผ ์
๋ ฅํด์ฃผ์ธ์", Snackbar.LENGTH_SHORT).show()
return
}
// ๋ฉ์ธ์ ๊ฒ์์ด ๋ด์๋๊ธฐ
mainActivity.searchWord = searchText
// ๋์์์ ๊ฒฝ์ฐ ๊ฒ์์ด ๋น์๋๊ธฐ
homeBinding.fraHomSearchText.setText("")
// ์ํ ๋ด๋ฆฌ๊ธฐ
mainActivity.hideKeyboard(mainActivity)
thread {
val historyEntity = HistoryEntity(null, searchText)
mainActivity.historyDatabase.historyDao().insertHistory(historyEntity)
// ํ๋๊ทธ๋จผํธ ์ ํ
mainActivity.fragmentController("search", true)
}
}
// ๊ฒ์ ๊ธฐ๋ก ๋น์ฐ๊ธฐ
homeBinding.fraHomHistoryDeleteAll -> {
thread {
mainActivity.historyDatabase.historyDao().deleteAllHistory()
mainActivity.runOnUiThread {
mainActivity.historyList.clear()
historyAdapter.notifyDataSetChanged()
homeBinding.fraHomEmptyHistory.visibility = View.VISIBLE
homeBinding.fraHomHistory.visibility = View.GONE
}
Snackbar.make(homeBinding.root, "โ๊ฒ์ ๊ธฐ๋ก์ด ๋น์์ก์ต๋๋ค", Snackbar.LENGTH_SHORT).show()
}
}
}
}
}
SearchFragment.kt
package com.ta2gi.searchbook.fragment
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.android.material.snackbar.Snackbar
import com.ta2gi.searchbook.MainActivity
import com.ta2gi.searchbook.R
import com.ta2gi.searchbook.databinding.FragmentSearchBinding
import com.ta2gi.searchbook.room.HistoryEntity
import kotlin.concurrent.thread
class SearchFragment(val mainActivity : MainActivity) : Fragment(), View.OnClickListener {
lateinit var searchBinding : FragmentSearchBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
searchBinding = FragmentSearchBinding.inflate(inflater)
// ๊ฒ์์ด ์ฝ์
searchBinding.fraSeaSearchText.setText(mainActivity.searchWord)
// ์ด๋ํฐ ์ฅ์ฐฉ
searchBinding.fraSeaSearchList.adapter = mainActivity.searchAdapter
// ์ฝ์
๋ ๊ฒ์์ด๋ก ๊ฒ์
mainActivity.searchResults(mainActivity.searchWord, this)
// ํด๋ฆญ ๋ฆฌ์ค๋
searchBinding.fraSeaBack.setOnClickListener(this)
searchBinding.fraSeaSearchButton.setOnClickListener(this)
return searchBinding.root
}
override fun onClick(view : View?) {
when(view) {
// ๋์๊ฐ๊ธฐ
searchBinding.fraSeaBack -> mainActivity.supportFragmentManager.popBackStack()
// ๊ฒ์
searchBinding.fraSeaSearchButton -> {
mainActivity.hideKeyboard(mainActivity)
val searchText = searchBinding.fraSeaSearchText.text.toString().trim()
// ๊ณต๋ฐฑ ์
๋ ฅ ์ ๋ฆฌํด
if(searchText.isEmpty()) {
Snackbar.make(searchBinding.root, "โ๊ฒ์์ด๋ฅผ ์
๋ ฅํด์ฃผ์ธ์", Snackbar.LENGTH_SHORT).show()
return
}
// ๊ฒ์ ๊ธฐ๋ก์ ์ถ๊ฐ
thread {
val historyEntity = HistoryEntity(null, searchText)
mainActivity.historyDatabase.historyDao().insertHistory(historyEntity)
}
// ์ํ ๋ด๋ฆฌ๊ณ ๊ฒ์ ๊ฒฐ๊ณผ ๋์ฐ๊ธฐ
mainActivity.searchResults(searchText, this)
}
}
}
}
HistoryAdapter.kt
package com.ta2gi.searchbook.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import com.ta2gi.searchbook.MainActivity
import com.ta2gi.searchbook.R
import com.ta2gi.searchbook.fragment.HomeFragment
import com.ta2gi.searchbook.room.HistoryEntity
import kotlin.concurrent.thread
class HistoryAdapter(val mainActivity: MainActivity, val historyList : MutableList<HistoryEntity>, val fragment : HomeFragment) : RecyclerView.Adapter<HistoryAdapter.ViewHolderClass>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HistoryAdapter.ViewHolderClass {
val view = LayoutInflater.from(parent.context).inflate(R.layout.history_list_item, parent, false)
return ViewHolderClass(view)
}
override fun onBindViewHolder(holder: ViewHolderClass, position: Int) {
holder.searchWord.text = historyList[position].searchWord
// ๊ฒ์ ๊ธฐ๋ก ์ญ์
holder.searchWordDelete.setOnClickListener {
thread {
val historyEntity = HistoryEntity(historyList[position].bookUid, holder.searchWord.text.toString())
mainActivity.historyDatabase.historyDao().deleteHistory(historyEntity)
mainActivity.historyList.clear()
mainActivity.historyList.addAll(mainActivity.historyDatabase.historyDao().getHistory())
mainActivity.runOnUiThread {
notifyDataSetChanged()
// ๋ฆฌ์คํธ๊ฐ ๋น์ด ์์ผ๋ฉด empty ๋ณด์ฌ์ฃผ๊ธฐ
if(mainActivity.historyList.isEmpty()) {
mainActivity.runOnUiThread {
fragment.homeBinding.fraHomEmptyHistory.visibility = View.VISIBLE
fragment.homeBinding.fraHomHistory.visibility = View.GONE
}
}
}
Snackbar.make(mainActivity.mainBinding.root, "โ๊ฒ์ ๊ธฐ๋ก์ ์ง์ ์ต๋๋ค", Snackbar.LENGTH_SHORT).show()
}
}
}
override fun getItemCount(): Int {
return historyList.size
}
inner class ViewHolderClass(view : View) : RecyclerView.ViewHolder(view) {
val searchWord = view.findViewById<TextView>(R.id.history_list_item_search_word)
val searchWordDelete = view.findViewById<ImageView>(R.id.history_list_item_delete)
}
}
SearchAdapter.kt
package com.ta2gi.searchbook.adapter
import android.content.Intent
import android.net.Uri
import android.text.method.ScrollingMovementMethod
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.google.android.material.snackbar.Snackbar
import com.ta2gi.searchbook.MainActivity
import com.ta2gi.searchbook.R
import com.ta2gi.searchbook.retrofit2.BookDto
import kotlin.concurrent.thread
class SearchAdapter(val mainActivity: MainActivity, val searchList : MutableList<BookDto>) : RecyclerView.Adapter<SearchAdapter.ViewHolderClass>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchAdapter.ViewHolderClass {
val view = LayoutInflater.from(parent.context).inflate(R.layout.search_list_item, parent, false)
return ViewHolderClass(view)
}
override fun onBindViewHolder(holder: SearchAdapter.ViewHolderClass, position: Int) {
// ์ด๋ฏธ์ง, ์ ๋ชฉ, ์ ์, ์ค๊ฑฐ๋ฆฌ, ์ ๊ฐ, ํ๋งค์ฌ๋ถ, ๋งํฌ
Glide.with(mainActivity).load(searchList[position].thumbnail).into(holder.thumbnail)
holder.title.text = searchList[position].title
holder.authors.text = searchList[position].authors.toString()
holder.contents.text = searchList[position].contents
holder.price.text = "${searchList[position].price}์"
holder.status.text = searchList[position].status
holder.url.setOnClickListener {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(searchList[position].url))
mainActivity.startActivity(intent)
}
}
override fun getItemCount(): Int {
return searchList.size
}
inner class ViewHolderClass(view : View) : RecyclerView.ViewHolder(view) {
val thumbnail = view.findViewById<ImageView>(R.id.search_list_item_thumbnail)
val title = view.findViewById<TextView>(R.id.search_list_item_title)
val authors = view.findViewById<TextView>(R.id.search_list_item_authors)
val contents = view.findViewById<TextView>(R.id.search_list_item_contents)
val price = view.findViewById<TextView>(R.id.search_list_item_price)
val status = view.findViewById<TextView>(R.id.search_list_item_status)
val url = view.findViewById<TextView>(R.id.search_list_item_url)
}
}
์ด์ Room์ ๊ด๋ จ๋ ํ์ผ๋ค์ ๋๋ค.
HistoryEntity.kt
package com.ta2gi.searchbook.room
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "HistoryTable")
data class HistoryEntity(
@PrimaryKey(autoGenerate = true)
val bookUid : Int?,
@ColumnInfo
val searchWord : String
)
HistoryDao.kt
package com.ta2gi.searchbook.room
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
@Dao
interface HistoryDao {
// ์ฐ ๋ชฉ๋ก ๊ฐ์ ธ์ค๊ธฐ
@Query("SELECT * FROM HistoryTable")
fun getHistory() : MutableList<HistoryEntity>
// ์ฐ ๋ชฉ๋ก์ ์ฝ์
@Insert
fun insertHistory(book : HistoryEntity)
// ์ฐ ๋ชฉ๋ก์์ ์ ๊ฑฐ
@Delete
fun deleteHistory(book : HistoryEntity)
@Query("DELETE from HistoryTable")
fun deleteAllHistory()
}
HistoryDatabase.kt
package com.ta2gi.searchbook.room
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(entities = [HistoryEntity::class], version = 1)
abstract class HistoryDatabase : RoomDatabase() {
abstract fun historyDao() : HistoryDao
companion object {
private var instance : HistoryDatabase? = null
@Synchronized
fun getInstance(context: Context): HistoryDatabase? {
if (instance == null) {
synchronized(HistoryDatabase::class) {
instance = Room.databaseBuilder(
context.applicationContext,
HistoryDatabase::class.java,
"history.db"
).build()
}
}
return instance
}
}
}
๋ค์์ผ๋ก Retrofit2๋ฅผ ์ฐ๊ธฐ ์ํ ๋ฐฉ๋ฒ๊ณผ ํ์ผ๋ค์ ๋๋ค.
Kakao Developers
์นด์นด์ค API๋ฅผ ํ์ฉํ์ฌ ๋ค์ํ ์ดํ๋ฆฌ์ผ์ด์ ์ ๊ฐ๋ฐํด๋ณด์ธ์. ์นด์นด์ค ๋ก๊ทธ์ธ, ๋ฉ์์ง ๋ณด๋ด๊ธฐ, ์น๊ตฌ API, ์ธ๊ณต์ง๋ฅ API ๋ฑ์ ์ ๊ณตํฉ๋๋ค.
developers.kakao.com
๋งํฌ๋ก ์ด๋ํด ์นด์นด์ค์ ๋ก๊ทธ์ธ์ ํด์ฃผ๊ณ ์์ํ๊ธฐ๋ฅผ ๋๋ฌ์ค๋๋ค.
์ ํ๋ฆฌ์ผ์ด์ ์ถ๊ฐํ๊ธฐ๋ฅผ ํด๋ฆญ ํ ์ฑ ์ด๋ฆ๊ณผ ์ฌ์ ์๋ช (์๋ฌด๋ ๊ฒ๋ ์ ๋ ฅ ๊ฐ๋ฅ)์ ์ ๋ ฅํ๊ณ ์ ์ฅ์ ๋๋ฅด๋ฉด ์ถ๊ฐ๊ฐ ๋๊ณ
ํด๋น ์ ํ๋ฆฌ์ผ์ด์ ์ ๋๋ฌ๋ณด๋ฉด ๊ฐ์ข ํค๊ฐ ๋ฐ๊ธ์ด ๋ฉ๋๋ค.
์ฐ๋ฆฌ์๊ฒ ํ์ํ API๋ ์ฑ ๊ฒ์ API์ ๋๋ค.
์๋ ๋งํฌ๋ก ์ด๋ ํ ์ฐ์ธก์ ์ฑ ๊ฒ์์ ํด๋ฆญํด ๋ณด๋ฉด ์ฐ๋ฆฌ์๊ฒ ํ์ํ ์ ๋ณด๋ค์ด ์ ์ ํ์์ต๋๋ค.
https://developers.kakao.com/docs/latest/ko/daum-search/dev-guide
Kakao Developers
์นด์นด์ค API๋ฅผ ํ์ฉํ์ฌ ๋ค์ํ ์ดํ๋ฆฌ์ผ์ด์ ์ ๊ฐ๋ฐํด๋ณด์ธ์. ์นด์นด์ค ๋ก๊ทธ์ธ, ๋ฉ์์ง ๋ณด๋ด๊ธฐ, ์น๊ตฌ API, ์ธ๊ณต์ง๋ฅ API ๋ฑ์ ์ ๊ณตํฉ๋๋ค.
developers.kakao.com
Response๋ฅผ ๋ณด๋ฉด ๋ฐ์ดํฐ๊ฐ ์ด๋ค ํ์์ผ๋ก ๋ค์ด์ค๋์ง ์ ์ ์์ต๋๋ค.
์ ๋ ์ด๋ฏธ์ง, ์ ๋ชฉ, ์ ์, ๊ฐ๊ฒฉ, ํ๋งค ์ํ, ๋์ ์๊ฐ, ์์ธ์ฃผ์๋ง ํ์ํ๋ฏ๋ก ์ด๊ฒ๋ค๋ง ๊ฐ์ ธ์ ๋ณด๊ฒ ์ต๋๋ค.
์ฑ ์ ์ ๋ณด๋ฅผ ๋ด์ Data Class์ ๋๋ค.
BookDto.kt
package com.ta2gi.searchbook.retrofit2
import com.google.gson.annotations.SerializedName
data class BookDto(
@SerializedName("thumbnail") val thumbnail : String, // ์ด๋ฏธ์ง
@SerializedName("title") val title : String, // ์ ๋ชฉ
@SerializedName("authors") val authors : ArrayList<String>, // ์ ์
@SerializedName("price") val price : Int, // ์ ๊ฐ
@SerializedName("contents") val contents : String, // ์๊ฐ
@SerializedName("status") val status : String, // ํ๋งค ์ํ
@SerializedName("url") val url : String // ์์ธ์ฃผ์
)
@SerializedName์ Response์์ ๋์ด์จ ๋ฐ์ดํฐ ์ค ๋๊ฐ์ ์ด๋ฆ์ ์ฐพ์ ์ ๋ณด๋ฅผ ๋นผ์ค๋๋ค.
๋จ, ๋ณ์๋ช ์ด Response์์ ๋์ด์จ ์ด๋ฆ๊ณผ ๊ฐ๋ค๋ฉด @SerializedName๋ฅผ ์จ์ฃผ์ง ์์๋ ๋ฉ๋๋ค.
๋ค์์ผ๋ก BookDto์ ๋์ด์จ ์ฑ ๋ค์ ๋๊ฒจ๋ฐ์ Data Class๋ฅผ ๋ง๋ค์ด์ค๋๋ค.
KakaoData.kt
package com.ta2gi.searchbook.retrofit2
import com.google.gson.annotations.SerializedName
data class KakaoData(
@SerializedName("documents")
var documents : List<BookDto>
)
๋ฌธ์๋ฅผ ๋ณด๋ฉด Response์๋ meta์ documents๋ก ๋ฐ์ดํฐ๋ค์ด ๋ค์ด์ค๋๋ฐ ์ ๋ documents๋ง ์์ผ๋ฉด ๋ฉ๋๋ค.

KakaoInfo.kt
package com.ta2gi.searchbook.retrofit2
class KakaoInfo {
companion object {
const val BASE_URL = "https://dapi.kakao.com"
const val API_KEY = "KakaoAK 390b0ab961f1097083f1ef99093867a7"
}
}
๊ณตํต์ ์ผ๋ก ์ฐ์ด๋ ์น๊ตฌ๋ค์ companion object์ ์ ์ธํด ์คฌ์ต๋๋ค.
์์ฒญ ์ Host ์ฃผ์๋ BASE_URL์ ๋ด์๊ณ
API_KEY๋ ์ฌ์ดํธ์์ ์ถ๊ฐํ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ํด๋ฆญํด ๋ค์ด๊ฐ์ Rest API ํค๋ฅผ ๋ณต๋ถ ํด์ฃผ๋ฉด ๋ฉ๋๋ค.
๋ฐ๋์ ๋งจ ์์ "KakaoAK "๋ฅผ ๋ถ์ฌ์ค์ผ ํฉ๋๋ค!
KakaoService.kt
package com.ta2gi.searchbook.retrofit2
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Query
interface KakaoService {
@GET("/v3/search/book")
fun getSearchBook(
@Header("Authorization")
key : String,
@Query("query")
query : String
): Call<KakaoData>
}
@GET("/v3/search/book")์ ๊ธฐ๋ณธ ์ ๋ณด๋ฅผ ๋ณด๋ฉด GET๋ฐฉ์์ผ๋ก ๊ฐ์ ธ์์ผ ํ๋ค๊ณ ๋ช ์๋์ด ์์ผ๋ฉฐ
๊ด๊ณ ์์ ์ฃผ์๋ BASE_URL์ ๋ค๋ก ์ด์ด์ง๋ ๋ถ๋ถ์ด๊ธฐ ๋๋ฌธ์ "/"๋ฅผ ์ ๋ง๊ฒ ์ ์ด์ค์ผ ํฉ๋๋ค.
@Header("Authorization")์๋ ๋ณธ์ธ์ Rest API ํค๋ฅผ ์ ์ด๋์ API_KEY๋ฅผ ๋ฃ์ด์ค ๊ฒ์ ๋๋ค.
@Query("query")๋ ๋ฌธ์๋ฅผ ๋ณด๋ฉด ํผ์์๋ง ํ์๋ก ๋ฃ์ด์ค์ผ ํ๋ ์น๊ตฌ์ด๋ ์ ๋ฃ์ด์ค๋๋ค.
"query"์๋ ๊ฒ์์ด๊ฐ ๋ค์ด๊ฐ ๊ฒ๋๋ค.

์ด์ ๋ง์ง๋ง์ผ๋ก Kakao API URL๊ณผ ํต์ ํ๋ Retrofit2๋ฅผ ์ฌ์ฉํ๊ฒ ๋ค๋ ํ์ผ์ ๋๋ค.
object๋ก ๋ง๋ค๋ฉด singletonํ์์ผ๋ก ๋ถ๋ฌ์ค๊ธฐ ๋๋ฌธ์ ์ธ์คํด์ค๊ฐ ์ค๋ณต์ผ๋ก ์์ฑ๋์ง ์์ต๋๋ค.
RetrofitService.kt
package com.ta2gi.searchbook.retrofit2
import com.ta2gi.searchbook.retrofit2.KakaoInfo.Companion.BASE_URL
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object RetrofitService {
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
val kakaoService = retrofit.create(KakaoService::class.java)
}
์ ํ์ผ์ ๋์ถฉ ์ค๋ช ํ์๋ฉด ๊ธฐ๋ณธ url์ BASE_URL๋ก ์ฐ๊ณ ๋ด์ฉ์ GSON์ผ๋ก ๋ฐ๊ฟ์ค๋ค๋ ์๋ฏธ๋ก ํด์ํฉ์๋ค!
์ฌ๊ธฐ๊น์ง ํ์ผ๋ค์ ์์ฑ ํ ์คํ ํด๋ณด๋ฉด ์ํ๋ ์ฑ ๊ฒ์ ์ ์ ๋จ๋ ๊ฑธ ๋ณผ ์ ์์ต๋๋ค.
The End..๐ต
'๐ฑ > ๐Project' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Kotlin] ๋์ ๊ฒ์ ์ฑ ๋ง๋ค๊ธฐ๏ผ (0) | 2023.01.26 |
---|---|
[Kotlin] Note ์ฑ ๋ง๋ค๊ธฐ๏ผ (0) | 2023.01.11 |
[Kotlin] Note ์ฑ ๋ง๋ค๊ธฐ๏ผ (1) | 2023.01.11 |
[Kotlin] Note ์ฑ ๋ง๋ค๊ธฐ๏ผ (0) | 2023.01.10 |
[Kotlin] Note ์ฑ ๋ง๋ค๊ธฐ๏ผ (0) | 2023.01.09 |