Plugin
인 Json To Kotlin
을 활용하면 JSON
형식에 맞춰서 자동으로 변환하고 Model
을 생성해준다.
Retrofit
의 요청에 대한 interface
를 선언한다.
package com.example.imageviewer.data.service
import com.example.imageviewer.BuildConfig
import com.example.imageviewer.data.models.PhotoResponse
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Query
interface UnsplashApiService {
@GET("photos/random?" + "client_id=${BuildConfig.UNSPLASH_ACCESS_KEY}" + "&count=30")
suspend fun getRandomPhotos(
@Query("query") query : String?
): Response<List<PhotoResponse>>
}
object
를 활용하면 Singleton
으로 손쉽게 구현할 수 있다. 우선 interface
를 정의한다. 이 때 HTTP
요청에 대한 로깅을 출력하기 위해 OkHttp
의 HttpLoggingInterceptor
를 구현하여 인자로 전달했다.
package com.example.imageviewer.data.util
import com.example.imageviewer.BuildConfig
import com.example.imageviewer.data.Url
import com.example.imageviewer.data.models.PhotoResponse
import com.example.imageviewer.data.service.UnsplashApiService
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.create
object RetrofitUtil {
private val unsplashApiService: UnsplashApiService by lazy {
Retrofit.Builder()
.baseUrl(Url.UNSPLASH_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(buildOkHttpClient())
.build()
.create()
}
suspend fun getRandomPhotos(query: String?): List<PhotoResponse>? =
unsplashApiService.getRandomPhotos(query).body()
private fun buildOkHttpClient(): OkHttpClient =
OkHttpClient.Builder()
.addInterceptor(
HttpLoggingInterceptor().apply {
level = if (BuildConfig.DEBUG) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.NONE
}
}
)
.build()
}
API
를 통해 사진을 불러오는 것을 구현했으니 이제 사진을 보여줄 RecyclerView
를 구현해야 한다. 지금까지 구현했던 방식과 동일하다. ListAdapter
를 상속하는 클래스와 RecyclerView.ViewHolder
를 상속하는 이너 클래스를 정의한다. 또한 ClickListener
를 통해 Photo
객체가 전달될 수 있도록 한다.
package com.example.imageviewer
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.view.isGone
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.example.imageviewer.data.models.PhotoResponse
import com.example.imageviewer.databinding.ItemPhotoBinding
class PhotoAdapter(val itemClickListener : (PhotoResponse) -> Unit) : ListAdapter<PhotoResponse, PhotoAdapter.ViewHolder>(diffUtil) {
inner class ViewHolder(private val binding: ItemPhotoBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bindViews(photo: PhotoResponse) {
val dimensionRatio = photo?.height!! / photo?.width?.toFloat()!!
val targetWidth =
binding.root.resources.displayMetrics.widthPixels - (binding.root.paddingStart + binding.root.paddingEnd)
val targetHeight = (targetWidth * dimensionRatio).toInt()
binding.contentsContainer.layoutParams = binding.contentsContainer.layoutParams.apply {
height = targetHeight
}
Glide.with(binding.root)
.load(photo.urls?.regular)
.thumbnail(
Glide.with(binding.root)
.load(photo.urls?.thumb)
.transition(DrawableTransitionOptions.withCrossFade())
)
.override(targetWidth, targetHeight)
.into(binding.photoImageView)
Glide.with(binding.root)
.load(photo.user?.profileImageUrls?.small)
.placeholder(R.drawable.shape_profile_placeholder)
.circleCrop()
.into(binding.profileImageView)
if (photo.user?.name.isNullOrBlank()) {
binding.authorTextView.isGone = true
} else {
binding.authorTextView.isGone = false
binding.authorTextView.text = photo.user?.name
}
if (photo.description.isNullOrBlank()) {
binding.descriptionTextView.isGone = true
} else {
binding.descriptionTextView.isGone = false
binding.descriptionTextView.text = photo.description
}
binding.root.setOnClickListener {
itemClickListener(photo)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
ItemPhotoBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
return holder.bindViews(currentList[position])
}
companion object {
private val diffUtil = object : DiffUtil.ItemCallback<PhotoResponse>() {
override fun areItemsTheSame(oldItem: PhotoResponse, newItem: PhotoResponse): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(
oldItem: PhotoResponse,
newItem: PhotoResponse
): Boolean {
return oldItem == newItem
}
}
}
}