Skip to content
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

[4주차 과제] 서버 통신 기초 #9

Merged
merged 39 commits into from
May 30, 2023
Merged

[4주차 과제] 서버 통신 기초 #9

merged 39 commits into from
May 30, 2023

Conversation

leeeha
Copy link
Member

@leeeha leeeha commented May 12, 2023

  • [4주차 과제] 서버 통신 기초 #7
  • 저번 과제 중에 repo json 데이터 파싱하는 거 HomeFragment에 구현했습니다.
  • 회원가입, 로그인, 로그아웃, 자동 로그인 구현 방법은 이슈에 적어뒀는데, 더 좋은 방법 있으면 알려주세요!
  • 심화 과제는 다음주에 이어서 할 예정입니다!

필수 과제

  • (진짜) 회원가입/로그인 기능 구현하기
  • Reqres 샘플 데이터로 Gallery 프래그먼트 꾸미기

심화 과제

  • SearchFragment에서 카카오 검색 API 사용해보기
  • 코루틴을 활용한 서버 통신 (enqueue 함수 사용 방식과 비교)

도전 과제

  • Debounce Search에 대해 알아보고 심화과제에 적용해보기

실행 결과

Screen_Recording_20230513_012507_GO.SOPT.Android.mp4

리팩토링 Todo

  • 회원가입 시 중복 id 체크 방법 변경
  • 그레들 파일에서 Groovy 대신 KTS 사용하도록
  • Content-Type 알아보기
  • 서버 Response Dto 제네릭으로 만들어보기
  • ViewModel, LiveData, 양방향 데이터 바인딩
  • UiState, Result로 서버 통신 결과 처리
  • 코루틴 사용
  • Coil 라이브러리
  • 문자열 상수화
  • object vs companion object
    • 클래스가 처음 로딩될 때 초기화 되는 companion object (자바의 static, 프로그램과 생명주기를 같이 함.)
    • 반면에, object는 클래스가 처음 사용되는 시점까지 초기화가 지연된다.
    • object, companion object 모두 싱글톤 패턴 보장

leeeha added 15 commits May 6, 2023 16:23
- MultiViewHolderFactory의 getViewHolder에서 MultiViewHolder 객체를 리턴할 때, `as MultiViewHolder<MultiViewItem>`로 강제 캐스팅을 해야 했음. (그 이유는 무변성 때문에)
- Invariance: 형식 매개변수끼리는 하위 타입 관계를 만족시키더라도, 제네릭을 사용하는 클래스나 인터페이스는 하위 타입 관계가 유지되지 않는 것
- 그래서 MultiViewHolder의 형식 매개변수를 제거하면 하위 타입(HearderViewHolder, RepoViewHolder)에서 상위 타입(MultiViewHolder)으로 자동 캐스팅 되어 경고가 발생하지 않음.
- MultiViewHolder의 형식 매개변수를 제거한 대신에, 어댑터의 onBindViewHolder에서 holder의 타입을 구분하여 bind 함수를 호출하도록 변경함.
- CoordinatorLayout, AppBarLayout, Toolbar, RecyclerView
- 리사이클러뷰에 `appbar_scrolling_view_behavior` 속성을 설정하면, 스크롤 이벤트가 발생할 때마다 AppBarLayout에게 알림이 간다. (AppBarLayout 안에 있는 뷰들은 `layout_scrollFlags`에 설정된 플래그에 따라 동작한다.)
@leeeha leeeha requested review from b1urrrr, sxunea, lsakee and a team May 12, 2023 14:36
@leeeha leeeha self-assigned this May 12, 2023
@leeeha leeeha added Essential 필수 과제 Advanced 심화 과제 labels May 12, 2023
- 회원가입 화면에서 받은 인텐트가 아니라, prefs에 저장된 id, pw와 입력값을 비교하도록 수정
- 서버에 로그인 정보 등록
- prefs에 현재 로그인 상태를 true로 저장 (자동 로그인에 활용)
- 마이페이지에는 prefs에 저장된 데이터 표시
- prefs의 모든 데이터를 삭제하지 않고, 로그인 상태를 나타내는 Boolean 값만 조정
- 회원가입 다시 하지 않아도 prefs에 저장된 데이터와 입력값 비교하여 바로 재로그인 가능함.
Copy link
Member

@b1urrrr b1urrrr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이번 주도 과제하시느라 고생하셨습니다 👍👍
코드가 깔끔해서 읽기 너무 좋네요 🤭

Comment on lines 7 to 13
class GoSoptApplication : Application() {
override fun onCreate() {
super.onCreate()
prefs = PreferenceUtil(applicationContext)

initTimber()
initPrefsManager()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

함수화 너무 깔끔하네요 🤩

app/build.gradle Outdated
Comment on lines 75 to 85
// retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1'
implementation 'com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0'

// define a BOM and its version
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.10.0"))

// define any required OkHttp artifacts without version
implementation("com.squareup.okhttp3:okhttp")
implementation("com.squareup.okhttp3:logging-interceptor")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

implementation 할 때 Kotlin 스크립트(KTS)와 Groovy 문법이 섞여있는데 통일 시켜주시면 좋을 것 같습니다 :)
공식문서에 따르면 Groovy보다 KTS로 gradle을 작성하는 것이 선호된다고 합니다.

val name: String,
@SerialName("full_name")
val fullName: String,
val `private`: Boolean,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private은 소프트 키워드가 아닌 키워드(예약어)에 해당하기 때문에 @SerialName을 통해 다른 이름으로 바꿔주는 것이 헷갈리지 않아 가독성과 유지보수성이 좋아질 것 같습니다 :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

허걱 플러그인으로 자동 생성했는데 이렇게 변환했는지 몰랐네요! 자동 생성한 코드도 유심히 다시 살펴보겠습니다!

class GalleryFragment : BindingFragment<FragmentGalleryBinding>(R.layout.fragment_gallery) {
private val listAdapter by lazy { MyListAdapter() }
private val followerAdapter by lazy { FollowerAdapter() }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adapter 변수도 GalleryFragment의 뷰에 종속된 리소스이기 때문에 메모리 누수를 예방하기 위해 onDestroyView()에서 해제시켜주는 것이 좋을 것 같습니다!

Comment on lines 58 to 78
private fun checkDuplicateId() {
val userId = binding.etId.text.toString()
AuthFactory.ServicePool.authService.getUserInfo(userId)
.enqueue(object : retrofit2.Callback<ResLoginDto> {
override fun onResponse(call: Call<ResLoginDto>, response: Response<ResLoginDto>) {
// 이미 등록된 유저인 경우
if (response.isSuccessful) {
response.body()?.let {
Timber.d(it.status.toString())
Timber.d(it.message)
Timber.d(it.data.id)
Timber.d(it.data.name)
Timber.d(it.data.skill)
}
showSnackbar(binding.root, getString(R.string.id_duplicate_error_msg))
return
}

// 새로운 유저 등록
registerNewUser()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sign-up api의 요청으로 중복된 정보를 입력할 경우 status code 409를 반환하고 있기 때문에 이에 대한 예외 처리를 하고 유저 정보 조회는 통신을 하지 않는 것이 서버 통신 횟수를 줄일 수 있어 효율적일 것 같습니다!

Comment on lines 46 to 54
fun putUserData(user: User) {
val json = Gson().toJson(user)
putString(Intent.EXTRA_USER, json)
}

fun deleteAllData() {
sharedPreferences.edit().clear().apply()
fun getUserData(): User? {
val json = getString(Intent.EXTRA_USER, "")
return Gson().fromJson(json, User::class.java)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

json 문자열로 객체를 저장하고 반환하도록 구현까지 하셨네요 크 👍👍

Comment on lines 9 to 24
object BindingAdapter {
@JvmStatic
@BindingAdapter("app:imgUrl", "app:placeholder", "app:error")
fun ImageView.loadImage(
imgUrl: String,
loadingImg: Drawable,
errorImg: Drawable
) {
Glide.with(this.context)
.load(imgUrl)
.placeholder(loadingImg)
.error(errorImg)
.apply(RequestOptions().fitCenter())
.into(this)
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BindingAdapter 활용 너무 좋습니다 👍
코드랩에서 활용하고 있는 Coil 라이브러리도 한번 찾아보시면 좋을 것 같습니다!

- 기존 방식: 서버에서 GET 요청으로 유저 정보 조회 성공하면 중복 id로 판단
- 바꾼 방식: 서버에 POST로 회원가입 요청을 보냈는데 409 에러가 뜨면 중복 id로 판단하여 에러 스낵바 띄우기
- 회원가입에서 로그인으로 다시 돌아올 때 회원가입 성공 스낵바 띄우도록 변경
Copy link

@sxunea sxunea left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이번주도고생하셨습니다 💟 합세 화이팅

}

companion object {
private const val FILE_MOCK_REPO_LIST = "mock_repo_list.json"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

json 파일명까지 상수화 꼼꼼하십니다 👍

}

private fun compareInputWithPrefs(): Boolean {
val id = binding.etId.text.toString()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User라는 데이터 클래스가 있으니 binding.~~.text.toString() 을 반복하는 것 보다는 savedUser처럼 User형을 하나 더 만들어서 재사용하는건 어떨까요 ?

}

private fun initSignUpButtonClickListener() {
val signUpResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK) {
handleSignUpResult(result)
showSnackbar(binding.root, getString(R.string.sign_up_success_msg))
}
}

binding.btnSignUp.setOnClickListener {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

버튼을 클릭하고 나면 초기화되는것까지 너무 좋은데요 ? !

return true
}

if (response.code() == CODE_DUPLICATE_ID) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아이디 중복확인까지 👍

<string name="sign_up_success_msg">회원가입이 완료되었습니다.</string>
<string name="login_success_msg">로그인에 성공했습니다.</string>
<string name="login_fail_msg">로그인에 실패했습니다.</string>
<string name="sign_up_invalid_input_err">모든 항목에 유효한 값을 입력해주세요.</string>
<string name="invalid_input_error">모든 항목에 유효한 값을 입력해주세요.</string>

<!-- login -->
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

상황별 꼼꼼한 처리 멋집니다

@leeeha leeeha requested a review from b1urrrr May 23, 2023 06:59
@leeeha leeeha linked an issue May 29, 2023 that may be closed by this pull request
6 tasks
@leeeha leeeha merged commit 201b899 into develop May 30, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Advanced 심화 과제 Essential 필수 과제
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[4주차 과제] 서버 통신 기초
3 participants