후......
서버랑 api 연결을 시작했다..
api 명세서도 볼 줄 몰랐던 나는 이틀을 api에 대해 공부하고 서버분에게 질문했다
다행히 서버분이 안드로이드도 다뤄본 경험이 있으셔서 정말 많은 도움을 받았다(마이프로필 api 담당자님.. 정말 감사합니다..!)
그리고 지금 어제?? 아니 오늘 새벽 화면에 서버에서 내가 얻고 싶은 데이터만 가져와 화면에 뿌리는 것에 성공함!! 진짜 너무 뿌듯했다~~
Path Variable, Query String, Request Header, Request Body 로 이루어짐!!
Request Header
클라이언트 자체에 대한 자세한 정보를 포함하는 헤더
우리는 Authorization(인증 토큰을 서버로 보낼 때 쓰이는 헤더)를 씀
아직.. 로그인 토큰이 발급되지 않아 우선은 하드코딩해서 넣음
발급되면 새로 업로드하겠슴!
Path Variable
경로를 변수로서 사용하는 것
어떤 resource를 식별하고 싶을때 사용
우리는 프로필 수정하거나 삭제할때 어떤 프로필을 수정하거나 삭제할지 정해줌!!
/users/123 # 아이디가 123인 사용자를 가져온다.
@Path 쓰고 경로 변수 넣어줌
@PATCH("/myprofiles/{profile-id}")
suspend fun patchProfile(@Path(value = "profile-id") profile_id : Long, @Body patchData: RequestPatchProfile) : Response<PatchMyprofile>
}
/users?id=123 # 아이디가 123인 사용자를 가져온다.
``
4.Request Body
서버에 요청 보내는 메세지???같은거
@Body를 쓰고 뒤에 요청 바디의 변수들 넣어준 데이터 클래스 넣어주기
@PATCH("/myprofiles/{profile-id}")
suspend fun patchProfile(@Path(value = "profile-id") profile_id : Long, @Body patchData: RequestPatchProfile) : Response<PatchMyprofile>
}
<uses-permission android:name="android.permission.INTERNET"/>
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation ("com.squareup.retrofit2:converter-gson:2.9.0")
사용할 HTTP메서드 정의함
interface MainProfile {
//endpoint: 베이스url 뒤에 붙음
@GET("/myprofiles")
//임시로 넣어준 값
@Headers("member-id: 1")
//메서드 getData()
//Call<클래스>: API 요청 응답에 성공 할 경우, 전달 받을 데이터를 저장 할 데이터클래스
//여기서 가장 상위클래인 데이터 클래스 넣어주기!!!!!!!!
fun getData(): Call<MainProfileData>
}
앱에서 서버 호출이 필요한 곳마다 인터페이스를 사용해야 하는데, 인터페이스를 여러 번 구현하지 않고, 한 번만 구현해 놓고 필요한 곳에서 사용하기위해
-> 코틀린의 object 사용
-> object는 singleton 이기 때문에 전역 변수처럼 앱의 모든 곳에서 접근 가능
object RetrofitClient {
//baseUrl은 꼭 / 로 끝나야 함
private const val BASE_URL = "base url 넣어주기"
private const val MEMBER_ID_HEADER = "member-id"
private const val TEMP_MEMBER_ID = "1"
// Retrofit 인스턴스 생성
private val retrofit: Retrofit by lazy {
val client = OkHttpClient.Builder()
.addInterceptor { chain ->
val originalRequest: Request = chain.request()
val requestWithHeaders: Request = originalRequest.newBuilder()
.header(MEMBER_ID_HEADER, TEMP_MEMBER_ID)
.build()
chain.proceed(requestWithHeaders)
}
.build()
// Retrofit 빌더를 사용하여 Retrofit 인스턴스 생성
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
//addConverterFactory() 함수로 데이터 파싱 역할자를 지정
.addConverterFactory(GsonConverterFactory.create())
.build()
}
//Retrofit 인스턴스를 사용하여 MainProfile 서비스 인터페이스 생성
// 실제 HTTP 요청을 정의하는 메서드를 포함
val mainProfile: MainProfile by lazy {
retrofit.create(MainProfile::class.java)
}
}
우린 응답이 좀 길고 변수가 많았기 때문에
안드로이드 스튜디오에서 자동으로 변환해주는 기능이용해서 변환하고 애노테이션추가.. 엄청많은 하위 데이터 클래스들이 생겼다
data class MainProfileData(
@SerializedName("code")
val code: String,
@SerializedName("isSuccess")
val isSuccess: Boolean,
@SerializedName("message")
val message: String,
@SerializedName("result")
val result: Result
)
여기서 중요한건!!!
가장 여러 데이터 클래스 중 가장 상위클래스인 데이터 클래스에 응답 넣어주기!! 이거때문에 계속 해맴 처음에 하위클래스를 넣어주니 변수들의 데이터가 널이 나와서 계속 해매다가 서버분이 깃허브에 올린 코드보시고 사진에 글까지 그려주시면서 알려주심...!!🫶
Retrofit을 사용하여 서버에서 데이터를 가져오는 과정
//RetrofitClient 객체의 mainProfile 서비스 인터페이스를 사용하여 데이터를 가져오는 HTTP 요청 생성
//MainProfileData는 서버에서 받은 응답 데이터를 담는 클래스
//콜백 함수는 서버 통신 성공 또는 실패 시에 호출
RetrofitClient.mainProfile.getData().enqueue(object : Callback<MainProfileData> {
// 서버 통신 실패 시의 작업
override fun onFailure(call: Call<MainProfileData>, t: Throwable) {
Log.e("실패", t.toString())
}
//서버에서 응답이 도착한 경우 호출되는 메서드
//response 객체에는 서버에서 받은 응답이 포함
//이 메서드에서는 응답을 분석하고 처리
override fun onResponse(call: Call<MainProfileData>, response: Response<MainProfileData>) {
//서버 응답에서 데이터를 추출
//response.body()는 서버에서 받은 응답 본문을 말함
//MainProfileData 클래스의 객체로 변환->하위클래스 데이터 이용하기위해
val repos: MainProfileData? = response.body()
if (repos != null) {
//MainProfileData 객체에서 프로필 데이터를 추출
val frontFeatures: List<FrontFeature>? = repos.result.myprofiles?.flatMap { profile ->
profile.frontFeatures
}
if (frontFeatures != null) {
multiList.clear()
//frontFeatures의 데이터 중 필요한 데이터만 추출해서 리스트에 넣어줌
frontFeatures?.forEach { frontFeature ->
if (frontFeature.featureId == 1) {
multiList.add(
MultiProfileData(R.drawable.myprofile_character, frontFeature.value, frontFeature.value)
)
Log.d("FrontFeature key", frontFeature.key ?: "Key is null")
Log.d("FrontFeature value", frontFeature.value ?: "Value is null")
}
}
binding.mainProfileVp.setCurrentItem(0, false)
// 어댑터에 업데이트된 multiList를 제출
vpadapter.submitList(multiList)
Log.d("성공티비","success")
Log.d("FrontFeature List", multiList.toString())
} else {
Log.e("실패", "front_features 데이터가 null입니다.")
}
} else {
Log.e("실패", "응답 데이터가 null입니다.")
Log.e("Response", "${response.code()}")
}
}
})
}