저번시간에 Retrofit2과 RecyclerView를 이용하여 만든 앱을 더욱 응용하여 https://www.foodsafetykorea.go.kr/api/newDatasetDetail.do 여기에 있는 공공API를 활용하여 레시피검색 앱을 만들어 보았다.
https://www.foodsafetykorea.go.kr
https://www.foodsafetykorea.go.kr
www.foodsafetykorea.go.kr
공공데이터 포털을 들어가보면 많은 API들이 있다 이곳에 로그인을 하고 API KEY 인증 신청을하면 대부분 하루만에 API를 쓸 수 있는 권한을 부여받을 수 있었다!!
나는 레시피 DB가 있는 아래와 같은 API 사용을 신청했다
그러면 인증 key를 받으면 아래의 주소에 api에 접근 가능한 권한을 부여 받는다
1. Layout 설정
먼저 Layout은 MainLayout에 두개의 Recyclerview를 넣어준다.
첫번쨰 Recyclerview은 메뉴 검색 시 메뉴가 뜨게 되는 Recyclerview이다.
두번째 Recyclerview는 메뉴 검색 후 메뉴 클릭시 레시피가 뜨게 되는 Recyclerview이다.
visiblity를 설정하여 클릭시 하나의 Recyclerview가 보이면 다른 하나는 안보이도록 설정을 해준다
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyClerView_1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:paddingTop="20px"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/editText"
tools:listitem="@layout/recycler_view"
tools:visibility="visible" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyClerView_2"
android:layout_width="match_parent"
android:layout_height="0dp"
android:paddingTop="20px"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/editText" />
ㅁ recyclerview Layout은 FrameLayout에 cardview를 넣는 형식으로 만들어주었다.
2. recyclerViewAdapter
데이터는 recyclerViewadapter를 통해 우리가 볼 수 있는 xml 화면에 표시가 된다. 여기서 recyclerView는 총 두개가 있으므로 두개의 Adapter를 만들어준다.
package com.example.retrofitpractice.adapter
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.retrofitpractice.R
import com.example.retrofitpractice.databinding.RecyclerViewBinding
import com.example.retrofitpractice.model.Row
import com.jakewharton.picasso.OkHttp3Downloader
import com.squareup.picasso.Picasso
class RecyclerViewAdapter(val clickListener : (Row) -> Unit) : ListAdapter<Row, RecyclerViewAdapter.ViewHolder>(diffUtil) {
inner class ViewHolder(private val binding: RecyclerViewBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(row: Row) {
binding.RCPNM.text = row.rCPNM
binding.INFOENG.text = "열량: ${row.iNFOENG}Kcal"
putPoster(row)
binding.root.setOnClickListener {
clickListener(row)
}
}
private fun putPoster(row : Row) {
val builder = Picasso.Builder(binding.ivPoster.context)
builder.downloader(OkHttp3Downloader(binding.ivPoster.context))
builder.build().load(row.aTTFILENOMAIN)
.placeholder((R.drawable.ic_launcher_background))
.error(R.drawable.ic_launcher_background)
.into(binding.ivPoster)
binding.progressBar.setVisibility(View.GONE)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = RecyclerViewBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(currentList[position])
}
companion object {
val diffUtil = object : DiffUtil.ItemCallback<Row>() {
override fun areItemsTheSame(oldItem: Row, newItem: Row): Boolean {
return oldItem.rCPSEQ == newItem.rCPSEQ // 일련번호 같은지
}
override fun areContentsTheSame(oldItem: Row, newItem: Row): Boolean {
return oldItem == newItem
}
}
}
}
ㅁ 위의 첫번째 adpater는 메뉴 검색 시 메뉴들이 뜨게되는 recyclerViewAdapter이다. 매개변수로 Lambda 형식을 넣어주었으며 함수호출 시 아래와 같이 인자를 Lambda 식으로 넣을 수 있다.
adapter = RecyclerViewAdapter(
clickListener = {
row ->
val dataListRecipe = get_text_image(row)
binding.recyClerView2.isVisible = true
binding.backButton.isVisible = true
binding.recyClerView1.isVisible = false
adapter2 = RecyclerViewAdapter_2()
adapter2.submitList(dataListRecipe)
initRecyclerViewSecond()
}
)
package com.example.retrofitpractice.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.retrofitpractice.R
import com.example.retrofitpractice.databinding.RecipeBoxBinding
import com.example.retrofitpractice.model.Recipe
import com.jakewharton.picasso.OkHttp3Downloader
import com.squareup.picasso.Picasso
class RecyclerViewAdapter_2 : ListAdapter<Recipe, RecyclerViewAdapter_2.ViewHolder>(diffUtil) {
inner class ViewHolder(private val binding: RecipeBoxBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(recipe: Recipe) {
if (recipe.recipePhoto != "") {
putPoster(recipe)
}
val valueText = recipe.recipeData.dropLast(1)
binding.recipeText.text = valueText
}
private fun putPoster(recipe: Recipe) {
val builder = Picasso.Builder(binding.recipeImage.context)
builder.downloader(OkHttp3Downloader(binding.recipeImage.context))
builder.build().load(recipe.recipePhoto)
.placeholder((R.drawable.ic_launcher_background))
.error(R.drawable.ic_launcher_background)
.into(binding.recipeImage)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = RecipeBoxBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(currentList[position])
}
companion object {
val diffUtil = object : DiffUtil.ItemCallback<Recipe>() {
override fun areItemsTheSame(oldItem: Recipe, newItem: Recipe): Boolean {
return oldItem == newItem // 일련번호 같은지
}
override fun areContentsTheSame(oldItem: Recipe, newItem: Recipe): Boolean {
return oldItem == newItem
}
}
}
}
ㅁ 두번째 RecyclerviewAdapter
ㅁ retrofit을 이용한 공공데이터 API 불러오기
private fun getPhotosListFromServer(menu : String) {
val service = retrofitApi.retrofitService
service.getString("${KeyResult.API_KEY}","${menu}").enqueue(
object : Callback<RecipeEntity> {
override fun onResponse(
call: Call<RecipeEntity>,
response: Response<RecipeEntity>
) {
if(response.isSuccessful == true){
val result = response.body()?.cOOKRCP01?.row
adapter.submitList(result!!)
}
}
override fun onFailure(call: Call<RecipeEntity>, t: Throwable) {
Log.d("this", "실패 : ${t.toString()}")
}
}
)
}
package com.example.retrofitpractice.model
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Query
interface InterFaceRetrofit {
@GET("/api/{API_KEY}/COOKRCP01/json/1/5/RCP_NM={RCP_NM}")
fun getString(
@Path("API_KEY") API_KEY : String,
@Path("RCP_NM") RCP_NM: String
): Call<RecipeEntity>
}
ㅁ 먼저 김치찌개 레시피 데이터를 불러오기 위한 api 경로는 아래와 같다
https://openapi.foodsafetykorea.go.kr/api/인증받은키/json/1/5/RCP_NM=김치찌개
그렇다면 키와 메뉴만 @Path어노테이션을 사용해준다.
여기서 api에 활용되는 어노테이션이란 무엇인지 알아보았다.
@Path와 @Query의 차이점
@Path: path variable을 위함
api/member/{username}
@Query: query parameter 위함
api/member?username=
@Query:사용시 파라미터를 url 뒤에 붙여서 전달하는데, 유저들에게 쉽게 노출된다.
@Field: 보안을 위해 url 뒤에 붙이지 않고, 파라미터를 숨긴다. (@FormUrlEncoded 사용)
stackoverflow.com/questions/57848058/what-is-the-difference-between-field-and-query-in-retrofit
What is the difference between @Field and @Query in retrofit
In some POST requests, I don't know when to use @Field with FormUrlEncoded and when to use @Query For Example: @POST("list-products-for-sale") Call
stackoverflow.com
@Body와 @Field의 차이점
@FIeld: 인자를 @FormUrlEncoded를 사용해서 전달한다. (key=value&key=value의 형태)
@Body: Json형태의 하나의 객체만 전달. {key: value, key:value} 이런식으로
server에서 api를 불러오면 아래와 같이 선언된 Row라는 class object를 통해 넣어줄 수 있다. 각 object마다 value 값이 들어가 있게 된다.
package com.example.retrofitpractice.model
import com.google.gson.annotations.SerializedName
data class RecipeEntity(
@SerializedName("COOKRCP01")
val cOOKRCP01: COOKRCP01
)
data class COOKRCP01(
@SerializedName("RESULT")
val rESULT: RESULT?,
@SerializedName("row")
val row: List<Row>?,
@SerializedName("total_count")
val totalCount: String?
)
data class RESULT(
@SerializedName("CODE")
val cODE: String?,
@SerializedName("MSG")
val mSG: String?
)
data class Row(
@SerializedName("ATT_FILE_NO_MAIN")
val aTTFILENOMAIN: String?,
@SerializedName("ATT_FILE_NO_MK")
val aTTFILENOMK: String?,
@SerializedName("HASH_TAG")
val hASHTAG: String?,
@SerializedName("INFO_CAR")
val iNFOCAR: String?,
@SerializedName("INFO_ENG")
val iNFOENG: String?,
@SerializedName("INFO_FAT")
val iNFOFAT: String?,
@SerializedName("INFO_NA")
val iNFONA: String?,
@SerializedName("INFO_PRO")
val iNFOPRO: String?,
@SerializedName("INFO_WGT")
val iNFOWGT: String?,
@SerializedName("MANUAL01")
val mANUAL01: String?,
@SerializedName("MANUAL02")
val mANUAL02: String?,
@SerializedName("MANUAL03")
val mANUAL03: String?,
@SerializedName("MANUAL04")
val mANUAL04: String?,
@SerializedName("MANUAL05")
val mANUAL05: String?,
@SerializedName("MANUAL06")
val mANUAL06: String?,
@SerializedName("MANUAL07")
val mANUAL07: String?,
@SerializedName("MANUAL08")
val mANUAL08: String?,
@SerializedName("MANUAL09")
val mANUAL09: String?,
@SerializedName("MANUAL10")
val mANUAL10: String?,
@SerializedName("MANUAL11")
val mANUAL11: String?,
@SerializedName("MANUAL12")
val mANUAL12: String?,
@SerializedName("MANUAL13")
val mANUAL13: String?,
@SerializedName("MANUAL14")
val mANUAL14: String?,
@SerializedName("MANUAL15")
val mANUAL15: String?,
@SerializedName("MANUAL16")
val mANUAL16: String?,
@SerializedName("MANUAL17")
val mANUAL17: String?,
@SerializedName("MANUAL18")
val mANUAL18: String?,
@SerializedName("MANUAL19")
val mANUAL19: String?,
@SerializedName("MANUAL20")
val mANUAL20: String?,
@SerializedName("MANUAL_IMG01")
val mANUALIMG01: String?,
@SerializedName("MANUAL_IMG02")
val mANUALIMG02: String?,
@SerializedName("MANUAL_IMG03")
val mANUALIMG03: String?,
@SerializedName("MANUAL_IMG04")
val mANUALIMG04: String?,
@SerializedName("MANUAL_IMG05")
val mANUALIMG05: String?,
@SerializedName("MANUAL_IMG06")
val mANUALIMG06: String?,
@SerializedName("MANUAL_IMG07")
val mANUALIMG07: String?,
@SerializedName("MANUAL_IMG08")
val mANUALIMG08: String?,
@SerializedName("MANUAL_IMG09")
val mANUALIMG09: String?,
@SerializedName("MANUAL_IMG10")
val mANUALIMG10: String?,
@SerializedName("MANUAL_IMG11")
val mANUALIMG11: String?,
@SerializedName("MANUAL_IMG12")
val mANUALIMG12: String?,
@SerializedName("MANUAL_IMG13")
val mANUALIMG13: String?,
@SerializedName("MANUAL_IMG14")
val mANUALIMG14: String?,
@SerializedName("MANUAL_IMG15")
val mANUALIMG15: String?,
@SerializedName("MANUAL_IMG16")
val mANUALIMG16: String?,
@SerializedName("MANUAL_IMG17")
val mANUALIMG17: String?,
@SerializedName("MANUAL_IMG18")
val mANUALIMG18: String?,
@SerializedName("MANUAL_IMG19")
val mANUALIMG19: String?,
@SerializedName("MANUAL_IMG20")
val mANUALIMG20: String?,
@SerializedName("RCP_NM")
val rCPNM: String?,
@SerializedName("RCP_PARTS_DTLS")
val rCPPARTSDTLS: String?,
@SerializedName("RCP_PAT2")
val rCPPAT2: String?,
@SerializedName("RCP_SEQ")
val rCPSEQ: String?,
@SerializedName("RCP_WAY2")
val rCPWAY2: String?
)
최종 MainActivity 코드
package com.example.retrofitpractice
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.retrofitpractice.adapter.RecyclerViewAdapter
import com.example.retrofitpractice.adapter.RecyclerViewAdapter_2
import com.example.retrofitpractice.databinding.ActivityMainBinding
import com.example.retrofitpractice.model.Recipe
import com.example.retrofitpractice.model.RecipeEntity
import com.example.retrofitpractice.model.Row
import com.example.retrofitpractice.model.retrofitApi
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class MainActivity : AppCompatActivity() {
lateinit var adapter: RecyclerViewAdapter
lateinit var adapter2 : RecyclerViewAdapter_2
lateinit var binding: ActivityMainBinding
private var menuname : String ?= null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
adapter = RecyclerViewAdapter(
clickListener = {
row ->
val dataListRecipe = get_text_image(row)
binding.recyClerView2.isVisible = true
binding.backButton.isVisible = true
binding.recyClerView1.isVisible = false
adapter2 = RecyclerViewAdapter_2()
adapter2.submitList(dataListRecipe)
initRecyclerViewSecond()
}
)
binding.searchButton.setOnClickListener {
menuname = binding.editText.text.toString()
binding.editText.setText("")
binding.recyClerView2.isVisible = false
binding.backButton.isVisible = false
binding.recyClerView1.isVisible = true
getPhotosListFromServer(menuname!!)
initRecyclerView()
}
binding.backButton.setOnClickListener {
binding.recyClerView2.isVisible = false
binding.backButton.isVisible = false
binding.recyClerView1.isVisible = true
}
}
private fun get_text_image(row : Row): MutableList<Recipe> {
val dataList = mutableListOf<Recipe>()
if (row.mANUAL01 == "" && row.mANUALIMG01 == ""){
return dataList
}
dataList.add(Recipe("${row.mANUAL01}", "${row.mANUALIMG01}"))
if (row.mANUAL02 == "" && row.mANUALIMG02 == ""){
return dataList
}
dataList.add(Recipe("${row.mANUAL02}", "${row.mANUALIMG02}"))
if (row.mANUAL03 == "" && row.mANUALIMG03 == ""){
return dataList
}
dataList.add(Recipe("${row.mANUAL03}", "${row.mANUALIMG03}"))
if (row.mANUAL04 == "" && row.mANUALIMG04 == ""){
return dataList
}
dataList.add(Recipe("${row.mANUAL04}", "${row.mANUALIMG04}"))
if (row.mANUAL05 == "" && row.mANUALIMG05 == ""){
return dataList
}
dataList.add(Recipe("${row.mANUAL05}", "${row.mANUALIMG05}"))
if (row.mANUAL06 == "" && row.mANUALIMG06 == ""){
return dataList
}
dataList.add(Recipe("${row.mANUAL06}", "${row.mANUALIMG06}"))
if (row.mANUAL07 == "" && row.mANUALIMG07 == ""){
return dataList
}
dataList.add(Recipe("${row.mANUAL07}", "${row.mANUALIMG07}"))
if (row.mANUAL08 == "" && row.mANUALIMG08 == ""){
return dataList
}
dataList.add(Recipe("${row.mANUAL08}", "${row.mANUALIMG08}"))
if (row.mANUAL09 == "" && row.mANUALIMG09 == ""){
return dataList
}
dataList.add(Recipe("${row.mANUAL10}", "${row.mANUALIMG10}"))
if (row.mANUAL11 == "" && row.mANUALIMG11 == ""){
return dataList
}
dataList.add(Recipe("${row.mANUAL11}", "${row.mANUALIMG11}"))
return dataList
}
private fun initRecyclerView() {
binding.recyClerView1.adapter = adapter
binding.recyClerView1.layoutManager = LinearLayoutManager(this)
}
private fun initRecyclerViewSecond() {
binding.recyClerView2.adapter = adapter2
binding.recyClerView2.layoutManager = LinearLayoutManager(this)
}
private fun getPhotosListFromServer(menu : String) {
val service = retrofitApi.retrofitService
service.getString("${KeyResult.API_KEY}","${menu}").enqueue(
object : Callback<RecipeEntity> {
override fun onResponse(
call: Call<RecipeEntity>,
response: Response<RecipeEntity>
) {
if(response.isSuccessful == true){
val result = response.body()?.cOOKRCP01?.row
adapter.submitList(result!!)
}
}
override fun onFailure(call: Call<RecipeEntity>, t: Throwable) {
Log.d("this", "실패 : ${t.toString()}")
}
}
)
}
}
Recipe는 따로 class object를 만들어 선언해주고 데이터를 넣어주어 또 recyclerveiw에 만들어주게 되면 끝!
'안드로이드 > 정리(Android)' 카테고리의 다른 글
코틀린 문법 공부 (0) | 2022.03.18 |
---|---|
Android - MotionLayout splash 앱 만들어보기 (0) | 2022.03.11 |
Android - RecyclerView와 Retrofit을 사용한 Api 불러오기 (0) | 2022.03.04 |
Android - ViewPager2, RecyclerView , NavigationLayout 사용하기 (0) | 2022.03.03 |
Android - firebase 채팅기능 사용 (0) | 2022.02.24 |