Conversation
| override fun onFailure(call: Call<UserListResponse>, t: Throwable) { | ||
| Toast.makeText(context, "목록 로드 실패", Toast.LENGTH_SHORT).show() | ||
| } |
jeongkyueun
left a comment
There was a problem hiding this comment.
데이터 연동 전과 후 스크린샷 첨부하신 거 너무 좋습니다! 미션 모두 잘 수행하신 거 같아요~!!
| android:id="@+id/recyclerWish" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="match_parent"/> | ||
|
|
kimdoyeon1234
left a comment
There was a problem hiding this comment.
수고하셨습니다! 꼼꼼한 구현이 곳곳에서 보였어요! 이미지 리소스를 String 이름으로 저장해서 리소스 ID 꼬임 문제를 자연스럽게 해결하신 점이 인상적이였습니다! 다만 몇가지 피드백을 적어보자면,
- Retrofit을 ApiClient 싱글톤으로 분리하고, enqueue 대신 코루틴 방식으로 바꿔보세요!
- API 키를 BuildConfig로 분리해서 관리해주세요
- WishlistFragment의 getProductsOnce()를 getProductsFlow().collect로 바꾸면 실시간 반영이 돼요
수고하셨습니다!
| val okHttpClient = OkHttpClient.Builder() | ||
| .addInterceptor { chain -> | ||
| val original = chain.request() | ||
| val requestBuilder = original.newBuilder() | ||
| .header("x-api-key", "reqres_286fae62df9643f089a91de5a72a2ef4") | ||
| .method(original.method, original.body) | ||
| val request = requestBuilder.build() | ||
| chain.proceed(request) | ||
| } | ||
| .addInterceptor(HttpLoggingInterceptor().apply { | ||
| level = HttpLoggingInterceptor.Level.BODY | ||
| }) | ||
| .build() | ||
|
|
||
| val retrofit = Retrofit.Builder() | ||
| .baseUrl("https://reqres.in/") | ||
| .client(okHttpClient) | ||
| .addConverterFactory(GsonConverterFactory.create()) | ||
| .build() | ||
| val service = retrofit.create(ReqResService::class.java) |
There was a problem hiding this comment.
Retrofit 객체를 Fragment 안에서 직접 생성하면 Fragment가 재생성될 때마다 새로 만들어집니다. object로 싱글톤 ApiClient를 별도 파일로 분리하는 게 좋아요!
// ApiClient.kt (별도 파일로 분리)
object ApiClient {
private val retrofit = Retrofit.Builder()
.baseUrl("https://reqres.in/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
val service: ReqResService = retrofit.create(ReqResService::class.java)
}
// ProfileFragment.kt
val service = ApiClient.service // ← 이렇게 사용
이런식으로
| .header("x-api-key", "reqres_286fae62df9643f089a91de5a72a2ef4") | ||
| .method(original.method, original.body) |
There was a problem hiding this comment.
local.properties + BuildConfig로 분리해서 관리해주세요!
| service.getProfile().enqueue(object : Callback<SingleUserResponse> { | ||
| override fun onResponse(call: Call<SingleUserResponse>, response: Response<SingleUserResponse>) { | ||
| if (response.isSuccessful) { | ||
| val user = response.body()?.data | ||
| user?.let { | ||
| tvNickname.text = "${it.firstName} ${it.lastName}" | ||
| Glide.with(this@ProfileFragment).load(it.avatar).into(imgProfile) | ||
| } | ||
| } | ||
| } | ||
| override fun onFailure(call: Call<SingleUserResponse>, t: Throwable) {} | ||
| }) | ||
|
|
||
| service.getFollowingList().enqueue(object : Callback<UserListResponse> { | ||
| override fun onResponse(call: Call<UserListResponse>, response: Response<UserListResponse>) { | ||
| if (response.isSuccessful) { | ||
| val allUsers = response.body()?.data ?: listOf() | ||
|
|
||
| val filteredUsers = allUsers.filter { it.id != 1 } | ||
|
|
||
| val adapter = FollowingAdapter(filteredUsers) | ||
| rvFollowing.adapter = adapter | ||
| rvFollowing.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) | ||
| } | ||
| } |
There was a problem hiding this comment.
ReqResService의 함수에 suspend를 붙이고 lifecycleScope.launch로 코루틴 방식으로 바꾸면 코드가 훨씬 간결해집니다!
| private fun updateTabStyle(tab: TabLayout.Tab?, isBold: Boolean) { | ||
| val tabView = (binding.tabLayout.getChildAt(0) as ViewGroup).getChildAt(tab?.position ?: 0) as ViewGroup | ||
| val textView = tabView.getChildAt(1) as? TextView | ||
|
|
||
| textView?.let { | ||
| it.typeface = if (isBold) Typeface.DEFAULT_BOLD else Typeface.DEFAULT | ||
| } | ||
| } |
There was a problem hiding this comment.
TabLayout 내부 뷰 구조에 직접 접근해서 텍스트를 굵게 바꾸고 있어요. TabLayout 내부 구조는 라이브러리 버전에 따라 바뀔 수 있어서 불안정한 방식이에요. SpannableString으로 굵은 텍스트를 만들어서 탭에 설정하는 방식이 더 안전합니다!
📌 PR 제목
🔗 관련 이슈
Closes #40
✨ 변경 사항
🔍 테스트
📸 스크린샷 (선택)
🚨 추가 이슈