Kakao map api 활용

Park Jae Hong·2022년 11월 24일
0

Kakao api key 등록

retrofit build gradle(app) 추가

 // retrofit
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, // X 좌표값 혹은 longitude
    val y: String // Y 좌표값 혹은 latitude
)

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)
                }
        }
    }
}

Button Click 시 ViewModel isPlaceList Observing

viewLifecycleOwner.lifecycleScope.launch {
        repeatOnLifecycle(Lifecycle.State.STARTED) {
            searchPlaceViewModel.isPlaceList.collect {
                resultSearchPlace(it)
            }
        }
    }

ViewModel을 Observing 할때 viewLifecycleOwner가 없으면 Error 가 난다. 근데 주석이나 .을 빼고 위에 넣으면 Error가 사라진다. 버그인거 같으나 한번 알아보기 !

profile
The people who are crazy enough to think they can change the world are the ones who do. -Steve Jobs-

0개의 댓글