-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
네트워크 통신 플로우 세팅 & 사용 가능 기프티콘 조회 api test #44
Changes from all commits
c54ab20
7b30017
7c18f5b
6be083d
cab9f2a
512430f
f95e9f6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.yapp.buddycon.data.api | ||
|
||
import com.yapp.buddycon.data.model.response.AvailableGifticonResponse | ||
import retrofit2.Response | ||
import retrofit2.http.GET | ||
import retrofit2.http.Header | ||
import retrofit2.http.Query | ||
|
||
interface GiftiConService { | ||
@GET("api/v1/gifticons/available") | ||
suspend fun requestGiftiConDetail( | ||
@Header("Authorization") token: String = "Bearer " + | ||
"eyJhbGciOiJIUzUxMiJ9.eyJpZCI6MiwiaWF0IjoxNzAyNzg5NDc2LCJleHAiOjE3MDg4Mzc0NzZ9." + | ||
"8YPrIlLexzGiqHwE1T_n2E-hCYbsNqJA5kUPWwgD0H8GmrGGsMgexme4NnNzBgsiHWG2uGtDLZL9fDCdiyZNUw", | ||
@Query("pageNumber") pageNumber: Int, // page | ||
@Query("gifticonStoreCategory") gifticonStoreCategory: String?, // 기프티콘 가게 카테고리 | ||
@Query("rowCount") rowCount: Int = 20 // page 당 요청 데이터 개수 | ||
): Response<AvailableGifticonResponse> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package com.yapp.buddycon.data.di | ||
|
||
import com.yapp.buddycon.data.BuildConfig | ||
import com.yapp.buddycon.data.di.qualifiers.HttpLoggingInterceptorQualifier | ||
import dagger.Module | ||
import dagger.Provides | ||
import dagger.hilt.InstallIn | ||
import dagger.hilt.components.SingletonComponent | ||
import okhttp3.Interceptor | ||
import okhttp3.logging.HttpLoggingInterceptor | ||
import javax.inject.Singleton | ||
|
||
@Module | ||
@InstallIn(SingletonComponent::class) | ||
object InterceptorModule { | ||
@HttpLoggingInterceptorQualifier | ||
@Provides | ||
@Singleton | ||
fun provideHttpLoggingInterceptor(): Interceptor = | ||
HttpLoggingInterceptor().apply { | ||
level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package com.yapp.buddycon.data.di | ||
|
||
import com.yapp.buddycon.data.api.GiftiConService | ||
import com.yapp.buddycon.data.di.qualifiers.BuddyConClient | ||
import com.yapp.buddycon.data.di.qualifiers.BuddyConRetrofit | ||
import com.yapp.buddycon.data.di.qualifiers.HttpLoggingInterceptorQualifier | ||
import dagger.Module | ||
import dagger.Provides | ||
import dagger.hilt.InstallIn | ||
import dagger.hilt.components.SingletonComponent | ||
import okhttp3.Interceptor | ||
import okhttp3.OkHttpClient | ||
import retrofit2.Retrofit | ||
import retrofit2.converter.gson.GsonConverterFactory | ||
import retrofit2.create | ||
import javax.inject.Singleton | ||
|
||
@Module | ||
@InstallIn(SingletonComponent::class) | ||
object NetworkModule { | ||
private const val BASE_URL = "http://43.202.14.1:8080/" | ||
|
||
@BuddyConClient | ||
@Provides | ||
@Singleton | ||
fun provideBuddyConClient( | ||
@HttpLoggingInterceptorQualifier httpLoggingInterceptor: Interceptor, | ||
// @BuddyConInterceptorQualifier buddyConInterceptor: Interceptor | ||
): OkHttpClient = | ||
OkHttpClient.Builder() | ||
.addInterceptor(httpLoggingInterceptor) | ||
// .addInterceptor(buddyConInterceptor) | ||
.build() | ||
|
||
@BuddyConRetrofit | ||
@Provides | ||
@Singleton | ||
fun provideBuddyConRetrofit( | ||
@BuddyConClient okHttpClient: OkHttpClient | ||
): Retrofit = | ||
Retrofit.Builder() | ||
.baseUrl(BASE_URL) | ||
.client(okHttpClient) | ||
.addConverterFactory(GsonConverterFactory.create()) | ||
.build() | ||
|
||
/** api service */ | ||
@Provides | ||
@Singleton | ||
fun provideGiftiConService( | ||
@BuddyConRetrofit retrofit: Retrofit | ||
): GiftiConService = | ||
retrofit.create() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.yapp.buddycon.data.di.qualifiers | ||
|
||
import javax.inject.Qualifier | ||
|
||
@Qualifier | ||
@Retention(AnnotationRetention.BINARY) | ||
annotation class HttpLoggingInterceptorQualifier | ||
|
||
@Qualifier | ||
@Retention(AnnotationRetention.BINARY) | ||
annotation class BuddyConInterceptorQualifier |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.yapp.buddycon.data.di.qualifiers | ||
|
||
import javax.inject.Qualifier | ||
|
||
@Qualifier | ||
@Retention(AnnotationRetention.BINARY) | ||
annotation class LoginClient | ||
|
||
@Qualifier | ||
@Retention(AnnotationRetention.BINARY) | ||
annotation class BuddyConClient |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.yapp.buddycon.data.di.qualifiers | ||
|
||
import javax.inject.Qualifier | ||
|
||
@Qualifier | ||
@Retention(AnnotationRetention.BINARY) | ||
annotation class LoginRetrofit | ||
|
||
@Qualifier | ||
@Retention(AnnotationRetention.BINARY) | ||
annotation class BuddyConRetrofit |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
package com.yapp.buddycon.data.model.request |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package com.yapp.buddycon.data.model.response | ||
|
||
import com.google.gson.annotations.SerializedName | ||
import com.yapp.buddycon.domain.model.gifticon.AvailableGifticon | ||
|
||
data class AvailableGifticonResponse( | ||
@SerializedName("body") | ||
val body: Body, | ||
@SerializedName("message") | ||
val message: String, | ||
@SerializedName("status") | ||
val status: Int | ||
) | ||
|
||
data class Body( | ||
val content: List<Content>, | ||
val empty: Boolean, | ||
val first: Boolean, | ||
val last: Boolean, | ||
val number: Int, | ||
val numberOfElements: Int, | ||
val pageable: Pageable, | ||
val size: Int | ||
) { | ||
data class Content( | ||
val expireDate: String, | ||
val gifticonId: Int, | ||
val gifticonStore: String, | ||
val gifticonStoreCategory: String, | ||
val imageUrl: String, | ||
val memo: String, | ||
val name: String | ||
) { | ||
fun mapToAvailableGifticonInfo() = AvailableGifticon.AvailableGifticonInfo( | ||
imageUrl = this.imageUrl, | ||
name = this.name, | ||
expireDate = this.expireDate | ||
// 가게, 메뉴 카테고리 정보 mapping 추가 예정 | ||
) | ||
} | ||
|
||
data class Pageable( | ||
val pageNumber: Int, | ||
val pageSize: Int, | ||
val paged: Boolean, | ||
val unpaged: Boolean | ||
) | ||
|
||
fun mapToAvailableGifticon() = AvailableGifticon( | ||
availableGifticons = this.content.map { it.mapToAvailableGifticonInfo() }, | ||
isFirstPage = this.first, | ||
isLastPage = this.last, | ||
pageNumber = this.pageable.pageNumber | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
package com.yapp.buddycon.data.repository.local |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.yapp.buddycon.data.repository.remote | ||
|
||
import com.yapp.buddycon.data.api.GiftiConService | ||
import com.yapp.buddycon.domain.repository.AvailableGifticonRepository | ||
import kotlinx.coroutines.flow.catch | ||
import kotlinx.coroutines.flow.flow | ||
import kotlinx.coroutines.flow.map | ||
import javax.inject.Inject | ||
|
||
class AvailableGifticonRepositoryImpl @Inject constructor( | ||
private val gifticonService: GiftiConService | ||
) : AvailableGifticonRepository { | ||
override fun getAvailableGifiticon() = flow { | ||
emit(gifticonService.requestGiftiConDetail(pageNumber = 0, gifticonStoreCategory = null)) | ||
}.catch { error -> | ||
throw Throwable("catch error!", error) | ||
}.map { response -> | ||
if (response.isSuccessful) { | ||
(response.body() ?: throw NullPointerException("null response")).body.mapToAvailableGifticon() | ||
} else { | ||
throw Throwable("error.. msg : ${response.message()}") | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.yapp.buddycon.domain.model.gifticon | ||
|
||
import com.yapp.buddycon.domain.model.type.GifticonCategory | ||
import com.yapp.buddycon.domain.model.type.GifticonStoreCategory | ||
|
||
data class AvailableGifticon( | ||
val availableGifticons: List<AvailableGifticonInfo>, | ||
val isFirstPage: Boolean = false, | ||
val isLastPage: Boolean = false, | ||
val pageNumber: Int = 0, | ||
) { | ||
data class AvailableGifticonInfo( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @hanchang97 클래스 안에 클래스가 있는 구조의 장점이 있나요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 중첩된 data class 사용을 통해 관련된 데이터 모델을 논리적으로 그룹화하려는 의도였습니다. 중첩된 data class는 외부 클래스와 강한 연관성을 가지기 때문에, 해당 클래스가 어떤 목적을 가지는지 명확하게 표현할 수 있을거라 생각했습니다..! |
||
val imageUrl: String = "", | ||
val category: GifticonCategory = GifticonCategory.ETC, | ||
val storeCategory: GifticonStoreCategory = GifticonStoreCategory.OTHERS, | ||
val name: String = "", | ||
val expireDate: String = "", | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.yapp.buddycon.domain.model.type | ||
|
||
enum class GifticonStoreCategory(val value: String) { | ||
CAFE("CAFE"), | ||
CONVENIENCE_STORE("CONVENIENCE_STORE"), | ||
OTHERS("OTHERS") | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.yapp.buddycon.domain.repository | ||
|
||
import com.yapp.buddycon.domain.model.gifticon.AvailableGifticon | ||
import kotlinx.coroutines.flow.Flow | ||
|
||
interface AvailableGifticonRepository { | ||
fun getAvailableGifiticon(): Flow<AvailableGifticon> // todo - parameter 추가 예정 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package com.yapp.buddycon.gifticon.available | ||
|
||
import androidx.compose.foundation.background | ||
import androidx.compose.foundation.layout.Box | ||
import androidx.compose.foundation.layout.fillMaxSize | ||
import androidx.compose.material3.Button | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.graphics.Color | ||
import androidx.hilt.navigation.compose.hiltViewModel | ||
|
||
@Composable | ||
fun AvailabeGifticonScreen( | ||
availableGifticonViewModel: AvailableGifticonViewModel = hiltViewModel() | ||
) { | ||
Box( | ||
modifier = Modifier.fillMaxSize().background(Color.LightGray) | ||
) { | ||
Button( | ||
modifier = Modifier.align(Alignment.Center), | ||
onClick = { | ||
availableGifticonViewModel.getAvailableGifiticon() | ||
} | ||
) { | ||
Text("test") | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package com.yapp.buddycon.gifticon.available | ||
|
||
import android.util.Log | ||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.viewModelScope | ||
import com.yapp.buddycon.domain.repository.AvailableGifticonRepository | ||
import dagger.hilt.android.lifecycle.HiltViewModel | ||
import kotlinx.coroutines.flow.catch | ||
import kotlinx.coroutines.flow.collectLatest | ||
import kotlinx.coroutines.flow.onStart | ||
import kotlinx.coroutines.launch | ||
import javax.inject.Inject | ||
|
||
@HiltViewModel | ||
class AvailableGifticonViewModel @Inject constructor( | ||
private val availableGifticonRepository: AvailableGifticonRepository | ||
) : ViewModel() { | ||
|
||
fun getAvailableGifiticon() { | ||
viewModelScope.launch { | ||
availableGifticonRepository.getAvailableGifiticon() | ||
.onStart { | ||
// set loading state | ||
Log.e("BuddyConTest", "loading...") | ||
}.catch { | ||
// error handling | ||
Log.e("BuddyConTest", "catch error!") | ||
}.collectLatest { availableGifticon -> | ||
Log.e("BuddyConTest", "collect data : $availableGifticon") | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hanchang97
Pageable , SortX 클래스는 필요하지 않을 것 같은데 왜 모델링 하셨을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
응답 정상적으로 오는지 확인하는 과정에서 일단 만들어뒀는데 사양적으로 필요하지 않을 것 같아서 제거하도록 하겠습니다!