Kakao api key 등록
retrofit build gradle(app) 추가
def retrofit_version = "2.9.0"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation "com.squareup.okhttp3:logging-interceptor:4.10.0"
Api Call 을 받기 위한 data class
data class PlaceRemote (
val place_name: String,
val address_name: String,
val road_address_name: String,
val x: String,
val y: String
)
data class PlaceListRemote (
var documents: List<PlaceRemote>
)
API Call 을 하기 위한 Service 추가
interface SearchService {
@GET("v2/local/search/keyword.json")
suspend fun getSearchKeyword(
@Query("x") lng: String,
@Query("y") lat: String,
@Query("radius") radius: String,
@Query("query") place: String
): Response<PlaceListRemote>
}
Hilt 를 활용해 API Client 추가
import android.util.Log
import com.kiwi.kiwitalk.Const.BASE_URL
import com.kiwi.kiwitalk.Const.KAKAO_AUTH
import com.kiwi.kiwitalk.Const.KAKAO_KEY
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import okhttp3.Interceptor
import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.io.IOException
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object KakaoApiModule {
@Provides
@Singleton
fun provideHttpLoggingInterceptor() = HttpLoggingInterceptor { message ->
Log.d("API", message)
}.apply {
setLevel(HttpLoggingInterceptor.Level.BODY)
}
@Provides
@Singleton
fun provideKakaoApiInterceptor(): ApiInterceptor = ApiInterceptor()
@Provides
@Singleton
fun provideOkHttpClient(
httpLoggingInterceptor: HttpLoggingInterceptor,
apiInterceptor: ApiInterceptor,
): OkHttpClient = OkHttpClient.Builder()
.addInterceptor(apiInterceptor)
.addInterceptor(httpLoggingInterceptor)
.build()
@Provides
@Singleton
fun getClient(
client: OkHttpClient,
): Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
class ApiInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder()
.addHeader(KAKAO_AUTH, KAKAO_KEY)
.build()
return chain.proceed(request)
}
}
}
DataSource interface & 기능 구현
interface SearchPlaceRemoteDataSource {
suspend fun getSearchKeyword(lng:String,lat:String,place: String): Flow<PlaceListRemote>
}
---------------------------------------------
import android.accounts.NetworkErrorException
import com.kiwi.data.model.remote.PlaceListRemote
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import retrofit2.Retrofit
import javax.inject.Inject
class SearchPlaceRemoteDataSourceImpl @Inject constructor(
client: Retrofit?
) : SearchPlaceRemoteDataSource {
private val searchService = client?.create(SearchService::class.java)
override suspend fun getSearchKeyword(
lng: String,
lat: String,
place: String
): Flow<PlaceListRemote> = flow {
val response = searchService?.getSearchKeyword(lng,lat,"15000",place)
val body = response?.body()
if(response?.isSuccessful == true && body != null){
emit(body)
} else {
throw NetworkErrorException("Network Connect Error")
}
}
}
UI 에서 사용하기 위한 data class Mapping
fun PlaceListRemote.toPlaceList() : PlaceList {
val result = mutableListOf<Place>()
documents.mapIndexed { index, placeRemote ->
result.add(index,placeRemote.toPlace())
}
return PlaceList(list = result)
}
fun PlaceRemote.toPlace() = Place(
placeName = place_name,
addressName = address_name,
roadAddressName = road_address_name,
lng = x,
lat =y
)
Repository interface & 기능 구현
interface SearchPlaceRepository {
suspend fun getSearchKeyword(lng:String,lat:String,place: String): Flow<PlaceList>
}
------------------------------------------------
class SearchPlaceRepositoryImpl @Inject constructor(
private val dataSource: SearchPlaceRemoteDataSource
): SearchPlaceRepository {
override suspend fun getSearchKeyword(
lng: String,
lat: String,
place: String
): Flow<PlaceList> = flow {
dataSource.getSearchKeyword(lng, lat, place).collect {
emit(it.toPlaceList())
}
}
}
ViewModle 에서 Repository 호출
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.kiwi.domain.model.PlaceList
import com.kiwi.domain.repository.SearchPlaceRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import io.getstream.chat.android.client.utils.toResult
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class SearchPlaceViewModel @Inject constructor(
private val searchPlaceRepository: SearchPlaceRepository
) : ViewModel() {
private val _isPlaceList = MutableSharedFlow<PlaceList>()
val isPlaceList: SharedFlow<PlaceList> = _isPlaceList
fun getSearchPlace(lng: String, lat: String, place: String) {
viewModelScope.launch {
searchPlaceRepository.getSearchKeyword(lng, lat, place)
.catch {
Log.d("getSearchPlace","getSearchPlace: ${this.toResult()} $it")
}.collect {
_isPlaceList.emit(it)
}
}
}
}
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
searchPlaceViewModel.isPlaceList.collect {
resultSearchPlace(it)
}
}
}
ViewModel을 Observing 할때 viewLifecycleOwner가 없으면 Error 가 난다. 근데 주석이나 .을 빼고 위에 넣으면 Error가 사라진다. 버그인거 같으나 한번 알아보기 !