네이버 키 발급 받기
https://developers.naver.com/products/service-api/search/search.md
헤더에 키값 숨겨 보내기
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<RelativeLayout
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/et"
android:inputType="text"
android:layout_toLeftOf="@+id/btn"
android:layout_marginRight="10dp"
android:hint="검색어"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btn"
android:layout_alignParentRight="true"
android:text="검색"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
app:spanCount="2"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
🧨 화면 주요 내용
- 바인딩을 이용한 view 연결
- Retrofit를 이용해 네트워크 연결해 네이버에서 제공하는 쇼핑검색 open API 불러오기
- 소프트 키보드 없애기
- 네트워크 응답 받았을 때 리사이클러뷰에 아답터 연결해 보이기
package com.bsj0420.ex03kotlinopenapinaversearch
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import com.bsj0420.ex03kotlinopenapinaversearch.databinding.ActivityMainBinding
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.converter.scalars.ScalarsConverterFactory
import retrofit2.create
class MainActivity : AppCompatActivity() {
private val binding:ActivityMainBinding by lazy { ActivityMainBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root) //화면 연결
binding.btn.setOnClickListener{ serchData() }
}
private fun serchData() {
// 소프트 키보드 없애기
val imm:InputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(currentFocus?.windowToken, 0)
//hideSoftInputFromWindow의 매개변수로 포커스(토큰) 넣어줘야함 , flags : 즉시 0
//네이버에서 제공하는 쇼핑검색 open API 사용해보기
//1. Retrofit 생성
//네트워크작업을 대신 작성해주는 라이브러리 활용 : Retrofit
val retrofit:Retrofit = Retrofit.Builder()
.baseUrl("https://openapi.naver.com")
.addConverterFactory(ScalarsConverterFactory.create()) //스칼라가 먼저여야함
.addConverterFactory(GsonConverterFactory.create())
.build()
//2. Retrofit이 해줄 작업에 대한 요구 명세 (인터페이스 설계 & 추상 메소드로 정의)
// RetrofitService.kt 인터페이스 생성
//3. RetrofitService 객체 생성
val retrofitService:RetrofitService = retrofit.create(RetrofitService::class.java)
//4. 원하는 작업 요청 하여 네트워크 작업 실행하는 객체 실행
// val call:Call<String> = retrofitService.searchDataByString("Client ID",
// "Client Secret",
// binding.et.text.toString());
//
// //5. 작업 시작
// call.enqueue( object : Callback<String>{ //인터페이스의 익명 클래스 부르는 것 object
// override fun onResponse(call: Call<String>, response: Response<String>) {
// var s:String? = response.body()
//
// AlertDialog.Builder(this@MainActivity).setMessage(s).show()
// }
//
// override fun onFailure(call: Call<String>, t: Throwable) {
// Toast.makeText(this@MainActivity, "error", Toast.LENGTH_SHORT).show()
// }
//
// })
// 위 4,5번은 확인을 위해 Sring 으로 받았기 떄문에 주석처리
// json으로 파싱 해보자
//4. 원하는 작업 요청 하여 네트워크 작업 실행하는 객체 실행 리턴 받기
val call:Call<NaverSearchApiResponse> = retrofitService.searchDataByJson(binding.et.text.toString())
//5. 네트워크 작업 시작
call.enqueue(object : Callback<NaverSearchApiResponse>{
override fun onResponse(
call: Call<NaverSearchApiResponse>,
response: Response<NaverSearchApiResponse>
) {
val naverResponse:NaverSearchApiResponse? = response.body()
//확인용 토스트
//Toast.makeText(this@MainActivity, "아이템 개수 : ${naverResponse?.items?.size}", Toast.LENGTH_SHORT).show()
//응답받은 객체의 items 리스트를 리사이클러뷰에 보이기
binding.recycler.adapter = MyAdapter(this@MainActivity, naverResponse!!.items) //nullable
//setAdapter 하면 그게 Notify임 따로 안해도 된다
}
override fun onFailure(call: Call<NaverSearchApiResponse>, t: Throwable) {
Toast.makeText(this@MainActivity, "에러 : " + t.message, Toast.LENGTH_SHORT).show()
}
})
}
}
헤더를 매개변수로 받을 땐 어노테이션 @Header
헤더값을 함수 위에 어노테이션으로 받을 땐 @Headers
package com.bsj0420.ex03kotlinopenapinaversearch
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Headers
import retrofit2.http.Query
interface RetrofitService {
//고정 값 쿼리는 display=100 넣을 수 있음 , 호출할 때마다 값 전달하기 귀찮아서
@GET("/v1/search/shop.json?display=100") //여기 만 바꿔서 여러 행동 하게 할수 도 있음
fun searchDataByString(@Header("X-Naver-Client-Id") clientId:String,
@Header("X-Naver-Client-Secret") clientSecret:String ,
@Query("query") query:String):Call<String> // 주소 뒤에 붙어갈 것
//fun searchDataByString(@Query("query") query:String, @Query("display") display:Int ) // 주소 뒤에 붙어갈 것
//리턴은 Call <> 제네릭 안에 는 쿼리에서 받을 것
//네이버 검색 API를 요청할 때 다음 예와 같이 HTTP 요청 헤더에 클라이언트 아이디와 클라이언트 시크릿을 추가해야 합니다.
//헤더 값들도 안바뀌는 글자이기 때문에 고정하기
@Headers("X-Naver-Client-Id: 키아이디","X-Naver-Client-Secret: 키시크릿")
@GET("/v1/search/shop.json?display=100")
fun searchDataByJson(@Query("query") query:String):Call<NaverSearchApiResponse>
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:cardElevation="4dp"
app:cardCornerRadius="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="4dp"
android:layout_marginTop="4dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv"
android:layout_width="match_parent"
android:layout_height="160dp"
android:scaleType="centerCrop"
android:src="@drawable/penguins"
/>
<TextView
android:id="@+id/tv_title"
android:text="Title"
android:textStyle="bold"
android:textColor="@color/black"
android:textSize="18sp"
android:layout_below="@+id/iv"
android:layout_marginTop="8dp"
android:layout_marginLeft="12dp"
android:padding="6dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv_low_price"
android:layout_below="@+id/tv_title"
android:layout_alignLeft="@id/tv_title"
android:textColor="@color/purple_700"
android:text="20000"
android:textStyle="bold"
android:padding="4dp"
android:layout_marginBottom="6dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv_mall"
android:text="판매처"
android:layout_alignParentRight="true"
android:layout_alignTop="@+id/tv_low_price"
android:padding="4dp"
android:layout_marginBottom="6dp"
android:textColor="@color/black"
android:layout_marginRight="12dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
</androidx.cardview.widget.CardView>
🧨 화면 주요 내용
API Response 데이터들 받아오는 item class 생성
데이터 클래스에는 class앞에 data 키워드 필수!
데이터 받아올 때 String으로 받는 게 안전하다
(그래야 빈 값이 올 때 오류가 안난다)같은 클래스 안에 data 클래스 2개 다 만듦!
package com.bsj0420.ex03kotlinopenapinaversearch
//데이터 클래스 - 생성자 필수, {} 삭제 가능
//데이터 받아올 때 String으로 받는 게 안전하다
data class NaverSearchApiResponse(var total:Int, var display:Int, var items:MutableList<ShoppingItem>)
//아이템 1개의 클래스 (같은 class에 만들어도 됨)
data class ShoppingItem(
var title:String,
var link: String,
var image: String,
var lprice: String, //읽어올 Integer값이 빈값으로 오면 error 날 수 있기에 타입을 그냥 String으로 받자
var hprice: String,
var mallName: String,
)
🧨 화면 주요 내용
- binding을 활용한 view불러오기
- 뷰홀더는 아이템뷰 하나짜리 받도록 되어 있음 그래서 ViewHolder 매개변수로 binding.root 를 줘야됨- 제목글씨중에 html 태그문이 포함되어 있는걸 안보이도록 제거 : HtmlCompat.FROM_HTML_MODE_COMPACT
- 선택한 아이템 상세 링크 핸드폰 디바이스의 인터넷 앱으로 실행
package com.bsj0420.ex03kotlinopenapinaversearch
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.text.HtmlCompat
import androidx.recyclerview.widget.RecyclerView.Adapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.bsj0420.ex03kotlinopenapinaversearch.databinding.RecyclerItemBinding
import com.bumptech.glide.Glide
class MyAdapter(var context: Context, var items:MutableList<ShoppingItem>):Adapter<MyAdapter.VH>() {
//뷰홀더는 아이템뷰 하나짜리 받도록 되어 있음 그래서 binding.root 를 줘야됨
inner class VH(var binding: RecyclerItemBinding) : ViewHolder(binding.root){
}
//inflate
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
return VH(RecyclerItemBinding.inflate(LayoutInflater.from(context), parent, false))
}
override fun getItemCount(): Int = items.size
//hoder에 연결
override fun onBindViewHolder(holder: VH, position: Int) {
val item:ShoppingItem = items[position] //배열처럼 쓰는걸 권장
//제목글씨중에 html 태그문이 포함되어 있는걸 안보이도록 제거 : HtmlCompat.FROM_HTML_MODE_COMPACT
var title:String = HtmlCompat.fromHtml(item.title, HtmlCompat.FROM_HTML_MODE_COMPACT).toString()
holder.binding.tvTitle.text = title
holder.binding.tvLowPrice.text = "${item.lprice} 원"
holder.binding.tvMall.text = item.mallName
Glide.with(context).load(item.image).into(holder.binding.iv)
//링크 열기
holder.binding.root.setOnClickListener {
//핸드폰 디바이스의 인터넷 앱을 실행
val intent = Intent(Intent.ACTION_VIEW) //인터넷을 보여주는 것
intent.data = Uri.parse(item.link)
context.startActivity(intent)
}
}
}