Retrofit 실습 예제 (2) Youtube API 가져오기
2024. 7. 31. 21:54ㆍ[개발]/Kotlin 활용 앱 개발
# Retrofit 활용 순서 복기
1) 라이브러리 추가
dependencies {
// Retrofit
implementation("com.squareup.retrofit2:retrofit:2.9.0")
// Gson Converter
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
// OKHttp for 통신 로그
implementation("com.squareup.okhttp3:logging-interceptor:4.9.0")
2) API 인터페이스 정의: 서비스의 각 HTTP 엔드포인트에 대해 메서드를 정의하는 인터페이스 생성
interface YoutubeAPI {
// 예시) https://teamsparta.notion.site/Retrofit-41cdf5459d2c4fe2a14264121aad2dd8
// baseURL: https://teamsparta.notion.site/
@GET("videos")
suspend fun getTrendingVideos(
@Query("part") part: String = "snippet", // 필수 요청 매개변수
@Query("chart") chart: String = "mostPopular", // 이하 선택 요청 매개변수
@Query("maxResults") maxResults: Int = 100,
@Query("regionCode") regionCode: String = "US",
@Query("key") apiKey: String = API_KEY
): VideoResponse
}
참고: 유튜브 API 발급 사이트(https://developers.google.com/youtube/v3/docs/videos/list?hl=ko)
3) Retrofit 인스턴스 생성
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.create
// 싱글톤 패턴 위해 object로 선언
object RetrofitClient {
// baseURL 넣는 부분 외에는 항상 동일
private const val BASE_URL = "https://www.googleapis.com/youtube/v3/"
private val retrofit by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
// 가져올 때 로그 찍어줌 -> 오류가 났을 때 쉽게 확인 가능
.client(
OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}).build()
)
// JSON 파일을 GSON으로 컨버팅
.addConverterFactory(GsonConverterFactory.create())
.build()
}
val youtubeAPI: YoutubeAPI by lazy { retrofit.create() }
}
4) API 호출
override suspend fun getTrendingVideos(region: String): VideoResponse {
return RetrofitClient.youtubeAPI.getTrendingVideos(regionCode = region)
}
}
5) 인터넷 권한 추가
<uses-permission android:name="android.permission.INTERNET" />
android:usesCleartextTraffic="true"
※ 'Kotlin data class file from Json' 플러그인 활용해서 쉽게 data class 만들기
1) File → Settings → Plugins에 json 검색
2) JSON To Kotlin Class 플러그인 install
3) data class를 생성하고 싶은 폴더에서 New → Kotlin data class file from Json
4) 원본 데이터 JSON 파일 복붙, class name 지정
5) Advanced에서 Property: val, nullable로 설정, Annotation: Gson으로 설정 후 Generate
6) 폴더로 가면 data class 파일이 알아서 생성되어 있음!
* 서버에서 모든 데이터를 다 가져와서 파일이 너무 많으면 보기가 힘들다! 내가 쓸 것들만 따로 모아두는 걸 추천
fun List<Item>.toVideoItem(): List<ListItem.VideoItem> {
return this.map {
ListItem.VideoItem(
channelTitle = it.snippet?.channelTitle ?: "",
title = it.snippet?.title ?: "",
thumbnail = it.snippet?.thumbnails.high?.uri ?: "",
description = it.snippet?.description ?: ""
)
}
}
# 실제 예제
// 1. API 인터페이스 정의
import retrofit2.http.GET
import retrofit2.http.Query
private const val API_MAX_RESULT = 20
private const val API_REGION = "US"
private const val API_KEY = BuildConfig.YOUTUBE_API_KEY
interface YoutubeAPI {
@GET("videos")
suspend fun getTrendingVideos(
@Query("part") part: String = "snippet",
@Query("chart") chart: String = "mostPopular",
@Query("maxResults") maxResults: Int = API_MAX_RESULT,
@Query("regionCode") regionCode: String = API_REGION,
@Query("key") apiKey: String = API_KEY
): VideoResponse
}
// 2. Retrofit 인터페이스 생성
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.create
object RetrofitClient {
private const val BASE_URL = "https://www.googleapis.com/youtube/v3/"
private val retrofit by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
// 디버깅용
.client(
OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}).build()
)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
val youtubeAPI: YoutubeAPI by lazy { retrofit.create() }
}
// 3. 필요한 데이터 클래스만 모아서 데이터 클래스 생성
import com.google.gson.annotations.SerializedName
data class VideoResponse(
@SerializedName("etag")
val etag: String?,
@SerializedName("items")
val items: List<Item>?,
@SerializedName("kind")
val kind: String?,
@SerializedName("nextPageToken")
val nextPageToken: String?,
@SerializedName("pageInfo")
val pageInfo: PageInfo?
)
data class Item(
@SerializedName("etag")
val etag: String?,
@SerializedName("id")
val id: String?,
@SerializedName("kind")
val kind: String?,
@SerializedName("snippet")
val snippet: Snippet?
)
data class Snippet(
@SerializedName("categoryId")
val categoryId: String?,
@SerializedName("channelId")
val channelId: String?,
@SerializedName("channelTitle")
val channelTitle: String?,
@SerializedName("description")
val description: String?,
@SerializedName("liveBroadcastContent")
val liveBroadcastContent: String?,
@SerializedName("localized")
val localized: Localized?,
@SerializedName("publishedAt")
val publishedAt: String?,
@SerializedName("tags")
val tags: List<String?>?,
@SerializedName("thumbnails")
val thumbnails: Thumbnails?,
@SerializedName("title")
val title: String?
)
// 4. 생성한 파일들을 연결하는 작업 in repository
package com.example.standardcloneui.data.repository
import ...
class YoutubeRepositoryImpl : VideoRepository {
override suspend fun getTrendingVideos(region: String): VideoResponse {
return RetrofitClient.youtubeAPI.getTrendingVideos(regionCode = region)
}
}
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import retrofit2.HttpException
import java.io.IOException
private const val TAG = "HomeViewModel"
class HomeViewModel(private val repository: VideoRepository = YoutubeRepositoryImpl()) :
ViewModel() {
private val _trendingVideos = MutableLiveData<List<ListItem.VideoItem>?>()
val trendingVideos: LiveData<List<ListItem.VideoItem>?> = _trendingVideos
fun fetchTrendingVideos(region: String = "US") {
viewModelScope.launch {
runCatching {
val videos = repository.getTrendingVideos(region).items?.toVideoItem()
_trendingVideos.value = videos
}.onFailure {
Log.e(TAG, "fetchTrendingVideos() failed! : ${it.message}")
handleException(it)
}
}
}
private fun handleException(e: Throwable) {
when (e) {
is HttpException -> {
val errorJsonString = e.response()?.errorBody()?.string()
Log.e(TAG, "HTTP error: $errorJsonString")
}
is IOException -> Log.e(TAG, "Network error: $e")
else -> Log.e(TAG, "Unexpected error: $e")
}
}
}
Viewmodel 안 쓰고 그냥 Fragment에서 쓰고 싶으면 ↓
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
private val viewModel by viewModels<HomeViewModel>()
private val videoAdapter by lazy {
VideoListAdapter { video ->
if (video !is ListItem.VideoItem) return@VideoListAdapter
(activity as? MainActivity)?.showDetailFragment(video)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
initViewModel()
}
private fun initView() = with(binding) {
recyclerView.adapter = videoAdapter
chipKorea.setOnClickListener {
viewModel.fetchTrendingVideos("KR")
}
chipUs.setOnClickListener {
viewModel.fetchTrendingVideos("US")
}
}
private fun initViewModel() = with(viewModel) {
trendingVideos.observe(viewLifecycleOwner) {
videoAdapter.submitList(it)
}
fetchTrendingVideos("US")
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
'[개발] > Kotlin 활용 앱 개발' 카테고리의 다른 글
구글 AdMob(애드몹) 광고 연동하기 - 배너 광고 (1) | 2024.09.24 |
---|---|
Retrofit 실습 예제 (1) 시도별 미세먼지 현황 앱 (0) | 2024.07.31 |
Retrofit 개념 (0) | 2024.07.31 |
Google Map 가져오기 (0) | 2024.07.31 |
사용자 위치 얻기 (0) | 2024.07.31 |