[DIP/FE] 노래 추천받기(2) - Polling 방식으로 서버 요청 보내기

2026. 3. 8. 01:15·TAVE-16th

Ⅰ. 추천받기 ViewModel

3단계를 거치며 수집된 데이터들을 저장하는 뷰모델이다. 이 뷰모델 덕분에 마지막에 ResultFragment에서 한 번에 서버로 요청 보낼 수 있다.

class RecommendationViewModel : ViewModel() {
    var place: String = ""
    var decibel: Float = 0.0f
    var goal: String = ""

    // 서버 전송용 영문명
    var englishPlace: String = ""
    var englishGoal: String = ""

    // 데이터 확인용 로그
    fun checkData() {
        Log.d("RecResultFragment", "현재 저장된 데이터 -> 장소: $place / 데시벨: $decibel / 목표: $goal")
    }

    // 서버에서 최종적으로 받은 플레이리스트를 담는 변수
    val currentPlaylist = MutableLiveData<RecommendationResponse>()
}



 


 

Ⅱ. 움직이는 구슬 구현

디자인팀에서 결과를 기다리는 화면에서 구슬에 움직이는 애니메이션이 있으면 좋을 것 같다고 하였다. 그래서 처음에 그냥 gif로 추출하여 넣으면 될 것 같다고 생각했는데 gif 파일 용량이 너무 크고 끊김 현상이 발생했다.

그래서 최종으로 WebP 형식을 선택했다. Glide 라이브러리를 사용하면 WedP 애니매이션을 로드할 수 있다.

        // 구슬 움직이는 animation
        com.bumptech.glide.Glide.with(this)
            .load(R.drawable.orb_animation)  // webp 파일 
            .into(binding.centerButton)



 


 

Ⅲ. Polling 방식✨

이번 프로젝트의 가장 큰 도전 과제 중 하나였다. 백엔드 AI Agent가 음악을 추천하는 로직은 평균 6초 이상 소요되는 비동기 작업이다.

일반적인 API 요청처럼 마냥 기다리기엔 연결이 끊길 위험이 있어, "작업 시작 요청 -> taskId 획득 -> 주기적으로 상태 확인" 순서로 진행되는 폴링 방식을 채택했다.

 

1) 비동기 작업 시작 및 taskId 추출

뷰모델에서 데이터를 꺼내 request 데이터를 만들어 서버로 보낸다. 그에 대한 응답으로 서버는 taskId를 보내는데 정규식으로 추출하여 필요한 아이디만 뽑아냈다. taskId로 현재 사용자가 요청한 플레이리스트를 받아볼 수 있다.

private fun startAsyncGeneration() {
    val request = RecommendationRequest(
        place = viewModel.englishPlace,
        decibel = viewModel.decibel,
        goal = viewModel.englishGoal
    )

    RetrofitClient.recommendationApi.sendPlaylistPolling(request).enqueue(object : Callback<BaseResponse<String>> {
        override fun onResponse(call: Call<BaseResponse<String>>, response: Response<BaseResponse<String>>) {
            if(response.isSuccessful) {
                val message = response.body()?.data // "taskId: task_XXX..." 형태
                if(message != null) {
                    // 정규식으로 taskId 추출
                    val regex = "taskId:\\s*([^\\s]+)".toRegex()
                    val matchResult = regex.find(message)
                    val taskId = matchResult?.groupValues?.get(1)

                    if (taskId != null) startPollingLoop(taskId) // 폴링 시작
                }
            }
        }
        // ... 생략
    })
}

 

 

2) 코루틴을 활용한 폴링 루프

2초 간격으로 서버에 플레이리스트를 받기 위해 요청을 보낸다. 무한정으로 기다릴 수는 없기 때문에 타임아웃을 20초로 두었다.

private fun startPollingLoop(taskId: String) {
    pollingJob = viewLifecycleOwner.lifecycleScope.launch {
        val startTime = System.currentTimeMillis()
        var isFinished = false

        while (isActive && !isFinished) {
            // 20초 타임아웃 체크
            if (System.currentTimeMillis() - startTime > 20000L) {
                handleErrorAndExit("생성 시간이 초과되었습니다.")
                return@launch
            }

            val response = kotlinx.coroutines.withContext(Dispatchers.IO) {
                RetrofitClient.recommendationApi.getPlaylistPolling(taskId).execute()
            }

            if (response.isSuccessful) {
                val pollingData = response.body()?.data
                if (pollingData?.status == "COMPLETED") {
                    isFinished = true
                    onPlaylistReady(pollingData.playlistInfo!!) // 결과 처리로 이동
                }
            }
            if (!isFinished) delay(2000) // 2초 대기 후 다시 확인
        }
    }
}

 

 

3) 최종 결과 처리 및 로컬 DB 저장

데이터를 성공적으로 받으면 나중에 홈 화면에서 '최근 추천 기록'을 보여주기 위해 RecommendationManager(싱글톤)와 Room Database에 저장한다.

private fun onPlaylistReady(resultData: RecommendationResponse) {
    isDataLoaded = true

    // 1. 메모리 및 공유 저장소 저장
    RecommendationManager.cachedPlaylist = resultData
    viewModel.currentPlaylist.value = resultData

    // 2. Room Database에 최근 기록 저장 (비동기)
    saveToRoomHistory(requireContext(), viewModel.place, viewModel.goal, resultData.playlistId)

    // 3. UI 업데이트 (완료 상태로 변경)
    viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
        updateUIForCompletion()
    }
}



 


 

Ⅳ. 마무리

 

이번 기능을 구현하면서 비동기 작업을 처리하는 방식에 대해 고민할 수 있었다. 처음에는 단순히 API 요청 보내면 응답이 올 때까지 대기했지만, 플레이리스트를 만드는 데 시간이 걸리기 때문에 안정적인 구조가 아니었다.

 

그래서 Polling 방식을 사용했고 실제 안정적으로 잘 작동했다. 이번 경험을 통해 단순히 API를 호출하는 것이 아니라 작업 흐름을 이해하고 설계하는 경험을 할 수 있었다. 이런 비동기 처리 방식으로 WebSocketdlsk SSE 라는 것도 있던데 나중에 공부해 보면 좋을 것 같다.

 

또한 예외 처리에서도 네트워크 오류나 타임아웃 시 튕기는 것이 아니라 토스트와 함께 이전 화면으로 돌려보내는 식으로 처리했고, 이 과정에서 예외처리 UX에 대해 고민해볼 수 있었다.

 


👾👉 DIP Android Github:
https://github.com/choisio2/DIP_android

 

GitHub - choisio2/DIP_android: Tave 16th team 리듬타고가는중 - 후반기 프로젝트 DIP (위치와 소음 데이터를

Tave 16th team 리듬타고가는중 - 후반기 프로젝트 DIP (위치와 소음 데이터를 분석해 맥락 기반 음악을 추천하는 안드로이드 앱) - choisio2/DIP_android

github.com

 

📽️👉 풀시연영상 보러가기: https://youtu.be/HjFp9eipC_4

 

 

 

'TAVE-16th' 카테고리의 다른 글

[DIP/FE] 라이브러리 - Spotify에서 4분할 커버 가져오기 & 플리 삭제하기  (0) 2026.03.08
[DIP/FE] Playlist 추천 결과 - 리스트형 & 갤러리형 보기(ViewPager2)  (0) 2026.03.08
[DIP/FE] 노래 추천받기(1) - 장소 선택, 데시벨 측정, 목표 설정  (0) 2026.03.07
[DIP/FE] 온보딩 구현 - 문자열 검사 및 Spotify API로 아티스트 목록 가져오기  (0) 2026.03.07
[DIP/FE] 프로젝트 소개 & Kakao Oauth 구현  (0) 2026.02.23
'TAVE-16th' 카테고리의 다른 글
  • [DIP/FE] 라이브러리 - Spotify에서 4분할 커버 가져오기 & 플리 삭제하기
  • [DIP/FE] Playlist 추천 결과 - 리스트형 & 갤러리형 보기(ViewPager2)
  • [DIP/FE] 노래 추천받기(1) - 장소 선택, 데시벨 측정, 목표 설정
  • [DIP/FE] 온보딩 구현 - 문자열 검사 및 Spotify API로 아티스트 목록 가져오기
choisio2
choisio2
sio2-dev 님의 블로그 입니다.
  • choisio2
    SiO2 for Developer
    choisio2
  • 전체
    오늘
    어제
    • 분류 전체보기 (46) N
      • TAVE-16th (14)
      • BDA-11th (16)
      • C++ (5)
      • 개인 프로젝트 (4)
      • 백준 (4) N
      • 컴퓨터 그래픽스 (1)
      • 잡담 (1)
  • 블로그 메뉴

    • 태그
    • 방명록
  • 링크

    • github.com/choisio2
  • 공지사항

  • 인기 글

  • 태그

    SpotifyAPI
    KakaoOauth
    Tave
    백준1463
    데시벨측정
    calculator
    백준
    데이터분석모델링
    kotin
    바이브코딩
    BDA
    viewpager2
    개발자미래
    kotlin
    BDAI
    spotify
    polling
    C++
    프론트엔드
    BDA #데이터분석모델링
    코테
    알고리즘
    알고리즘스터디
    AI시대
    frontend
    개발자
    androidstudio
    코딩테스트
    playconsole
    geminicli
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
choisio2
[DIP/FE] 노래 추천받기(2) - Polling 방식으로 서버 요청 보내기
상단으로

티스토리툴바