GGo-ging / riding_balloon

1 stars 0 forks source link

내배캠 안드로이드 앱개발 심화 팀프로젝트 피드백 #40

Open tutorhwang opened 3 months ago

tutorhwang commented 3 months ago

총평

안녕하세요! 결과물을 보고나니 저도 가시오갈피의 멤버가 되고 싶군요! 멋진 데뷔 축하드립니다. 과제 요구 사항에만 그치지 않고 몰입해서 코딩했다는게 결과물에 보입니다. 이제 앞으로는 example package 명은 유의미한 이름으로 변경해주세요. data/di/network/presentation/useCase(package 명은 소문자) 기능에 맞게 package도 잘 나누어서 코드가 많은데도 가독성이 좋네요. 조금만 더 정리하면 완벽해지겠어요!

model: data class들 잘 구성했어요. 기능별로 package 분리를 한 김에 사용처 별로 좀 더 분리되어도 보기 좋을 것 같아요.

repository: project가 커지면서 이제 presentation / domain(optional)/data 영역으로 구분하며 domain 영역을 도입하게 되실거예요. 그럼 repository interface는 domain 영역으로 이동하시면 됩니다. repository 도 기능별로 조금더 package 분리가 되면 좋겠어요. 지금은 분리를 잘한 기능과 안한 기능이 혼재되어 있네요. 이제부터는 안정성과 성능을 높여보아요!

di : DI 시작을 축하합니다!

presentation : 여러 명이 개발한 티가나요! 좀더 깔끔하게 가독성을 높여보아요! 전반적으로 잘 해냈고. 처음 1주차때가 생각이 나며 눈물이 핑 도네요! 이 경험을 바탕으로 멋진 실전 프로젝트 완성해 보아요!

코드 피드백

https://github.com/GGo-ging/riding_balloon/blob/a355b377ce165d5e98acee9bae2b7d71f87051f6/app/src/main/java/com/example/riding_balloon/data/repository/favoritetravelspot/FavoriteTravelSpotRepositoryImpl.kt#L13

https://github.com/GGo-ging/riding_balloon/blob/a355b377ce165d5e98acee9bae2b7d71f87051f6/app/src/main/java/com/example/riding_balloon/data/repository/favoritetravelspot/FavoriteTravelSpotRepositoryImpl.kt#L41 https://github.com/GGo-ging/riding_balloon/blob/a355b377ce165d5e98acee9bae2b7d71f87051f6/app/src/main/java/com/example/riding_balloon/data/repository/FavoriteRepositoryImpl.kt#L44-L48

https://github.com/GGo-ging/riding_balloon/blob/a355b377ce165d5e98acee9bae2b7d71f87051f6/app/src/main/java/com/example/riding_balloon/data/repository/favoritetravelspot/FavoriteTravelSpotRepositoryImpl.kt#L46

FavoriteRepositoryImpl와 FavoriteTravelSpotRepositoryImpl 겹치는 기능이 많아보여요! 재사용 할 수 있는 부분도 있겠네요!

https://github.com/GGo-ging/riding_balloon/blob/a355b377ce165d5e98acee9bae2b7d71f87051f6/app/src/main/java/com/example/riding_balloon/data/repository/FavoriteTravelSpotDeserializer.kt#L17-L22

https://github.com/GGo-ging/riding_balloon/blob/a355b377ce165d5e98acee9bae2b7d71f87051f6/app/src/main/java/com/example/riding_balloon/data/source/local/room/VideoDao.kt#L10-L13

https://github.com/GGo-ging/riding_balloon/blob/a355b377ce165d5e98acee9bae2b7d71f87051f6/app/src/main/java/com/example/riding_balloon/data/source/remote/YoutubeApi.kt#L28-L48

https://github.com/GGo-ging/riding_balloon/blob/a355b377ce165d5e98acee9bae2b7d71f87051f6/app/src/main/java/com/example/riding_balloon/network/AuthorizationInterceptor.kt#L5-L12

https://github.com/GGo-ging/riding_balloon/blob/a355b377ce165d5e98acee9bae2b7d71f87051f6/app/src/main/java/com/example/riding_balloon/network/RetrofitClient.kt#L12

    private val okHttpClient by lazy {
        createOkHttpClient(withTimeout = false)
    }
    private val thirtyTimeOutOkHttpClient by lazy {
        createOkHttpClient(withTimeout = true)
    }
    private fun createOkHttpClient(withTimeout: Boolean): OkHttpClient {
        val interceptor = HttpLoggingInterceptor().apply {
            level = HttpLoggingInterceptor.Level.BODY
        }

        return OkHttpClient.Builder().apply {
            addInterceptor(AuthorizationInterceptor())
            addNetworkInterceptor(interceptor)
            if (withTimeout) {
                connectTimeout(30, TimeUnit.SECONDS)
                readTimeout(30, TimeUnit.SECONDS)
                writeTimeout(30, TimeUnit.SECONDS)
            }
        }.build()
    }

여러 방식의 정리가 가능할 거예요! 막바지에 급하게 넣은 기능은 코드에서 티가 난답니다 : D

    private val retrofit by lazy {
        createRetrofit()
    }

    // Youtube 
    val youtubeApi: YoutubeApi by lazy {
        retrofit.newBuilder()
            .baseUrl(YOUTUBE_BASE_URL)
            .build()
            .create(YoutubeApi::class.java)
    }

    // OpenAI 
    val openAiApi: OpenAIApi by lazy {
        retrofit.newBuilder()
            .baseUrl(OPEN_AI_BASE_URL)
            .client(thirtyTimeOutOkHttpClient) // 필요 시 클라이언트 재설정
            .build()
            .create(OpenAIApi::class.java)
    }

    private fun createRetrofit(): Retrofit {
        return Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create())
            .client(okHttpClient)
            .build()
    }

https://github.com/GGo-ging/riding_balloon/blob/a355b377ce165d5e98acee9bae2b7d71f87051f6/app/src/main/java/com/example/riding_balloon/presentation/extensions/TextViewExtensions.kt#L18

https://github.com/GGo-ging/riding_balloon/blob/a355b377ce165d5e98acee9bae2b7d71f87051f6/app/src/main/java/com/example/riding_balloon/presentation/home/adapters/Best10ListAdapter.kt#L15-L17 매 adapter마다 유사패턴의 onclick interface를 선언하는 대신 generic을 활용할 수 있어요!

interface ItemClick<T> {
    fun onClickItem(position: Int, item: T)
}
val channelClickListener = object : ItemClick<ChannelListModel> {
    override fun onClickItem(position: Int, item: ChannelListModel) {
        // ChannelListModel을 처리하는 로직
    }
}

val travelSpotClickListener = object : ItemClick<TravelSpotInfo> {
    override fun onClickItem(position: Int, item: TravelSpotInfo) {
        // TravelSpotInfo를 처리하는 로직
    }
}

https://github.com/GGo-ging/riding_balloon/blob/a355b377ce165d5e98acee9bae2b7d71f87051f6/app/src/main/java/com/example/riding_balloon/presentation/extensions/TextViewExtensions.kt#L13-L20

custom 'Date' 포멧터를 사용하면 날짜를 String이 아닌 Date로 바로 받아 올 수 있어요. 매번 변환하는 루틴의 호출이 매우 잦다면 미리 Date로 변환해서 받는게 효율적이예요.

val gson = GsonBuilder()
    .setDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") // 서버에서 사용하는 날짜 포맷에 맞게 설정
    .create()

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.~~") 
    .addConverterFactory(GsonConverterFactory.create(gson)) 
    .build()

data class VideoSnippet(
    @SerializedName("publishedAt")
    val publishedAt: Date?
)

https://github.com/GGo-ging/riding_balloon/blob/a355b377ce165d5e98acee9bae2b7d71f87051f6/app/src/main/java/com/example/riding_balloon/presentation/home/HomeFragment.kt#L84-L87

https://github.com/GGo-ging/riding_balloon/blob/a355b377ce165d5e98acee9bae2b7d71f87051f6/app/src/main/java/com/example/riding_balloon/presentation/home/HomeViewModel.kt#L45-L78

https://github.com/GGo-ging/riding_balloon/blob/a355b377ce165d5e98acee9bae2b7d71f87051f6/app/src/main/java/com/example/riding_balloon/presentation/videodetail/VideoDetailFragment.kt#L49-L52 성공했나요? : D

https://github.com/GGo-ging/riding_balloon/blob/a355b377ce165d5e98acee9bae2b7d71f87051f6/app/src/main/java/com/example/riding_balloon/useCases/ChannelUseCase.kt#L10

이제는 통신의 안정성도 챙길 때죠? api 서버에 붙다보면 여러가지 예상치 못한 error가 발생하게 될 수 있어요. 그에 따라 api 문서에서는 에러 코드에 대한 설명도 추가되어 있답니다. (400, 404, 200 등등) 각 에러 상황에 맞게 에러 메세지나 추후 동작이 연결되도록 좀 더 구체적으로 에러 핸들링을 하면 안정성을 높일 수 있답니다. https://developers.google.com/youtube/v3/docs/errors

try {
    val response = apiService.getData()
    if (response.isSuccessful) {
        // 성공 처리
    } else {
        when (response.code()) {
            400 -> handleError("Bad Request: 잘못된 요청입니다.")
            401 -> handleError("Unauthorized: 인증 오류입니다.")
            403 -> handleError("Forbidden: 접근 권한이 없습니다.")
            404 -> handleError("Not Found: 요청한 리소스를 찾을 수 없습니다.")
            500 -> handleError("Internal Server Error: 서버 오류가 발생했습니다.")
            503 -> handleError("Service Unavailable: 서버가 일시적으로 사용 불가능합니다.")
            else -> handleError("Unknown Error: ${response.message()}")
        }
    }
} catch (e: Exception) {
    handleError("An unexpected error occurred: ${e.message}")
}
Jisung15 commented 3 months ago

가시오갈피 가입 축하드립니다! ㅋㅋ 그리고 상세 피드백 감사합니다