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

[FEAT/#5] 1주차 심화과제 (XML) #7

Open
wants to merge 13 commits into
base: develop-xml
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,21 @@
tools:targetApi="31">
<activity
android:name=".presentation.ui.auth.signin.SignInActivity"
android:exported="true">
android:exported="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".presentation.ui.auth.signup.SignUpActivity"
<activity
android:name=".presentation.ui.auth.signup.SignUpActivity"
android:exported="false">

</activity>
<activity android:name=".presentation.ui.MainActivity"
<activity
android:name=".presentation.ui.MainActivity"
android:exported="false">

</activity>
Expand Down
8 changes: 4 additions & 4 deletions app/src/main/java/com/sopt/now/data/User.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import kotlinx.android.parcel.Parcelize

@Parcelize
data class User(
val id: String,
val password: String,
val nickname: String,
val phoneNumber: String
val id: String? = "",
val password: String? = "",
val nickname: String? = "",
val phoneNumber: String? = ""
Comment on lines +8 to +11
Copy link
Member

Choose a reason for hiding this comment

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

c: empty값을 기본적으로 부여하는 이유가 있으실까요?

Copy link
Member

Choose a reason for hiding this comment

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

c: 트레일링 콤마에 대해 아실까요?

) : Parcelable

Comment on lines 6 to 13
Copy link

Choose a reason for hiding this comment

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

기본값이 empty 인데 nullable한 타입인 이유는 null로 넣어주는 상황이 있어서 그런건가요?

29 changes: 28 additions & 1 deletion app/src/main/java/com/sopt/now/presentation/ui/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,23 +1,50 @@
package com.sopt.now.presentation.ui

import android.content.Intent
import android.os.Bundle
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import com.sopt.now.R
import com.sopt.now.data.User
import com.sopt.now.databinding.ActivityMainBinding
import com.sopt.now.presentation.utils.KeyStorage
import com.sopt.now.presentation.utils.getSafeParcelable
import com.sopt.now.presentation.utils.showToast

class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private var user: User? = null

Choose a reason for hiding this comment

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

user 를 클래스의 멤버변수로 둘 정도로 활용을 많이 하는지 의문입니다. 게다가 nullable 하고 기본값은 null? 코틀린의 장점을 살리지 못한다고 생각합니다.

private var backPressedTime: Long = 0

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

getUserInfo()
showUserInfo()

onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (System.currentTimeMillis() - backPressedTime < 2000) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (System.currentTimeMillis() - backPressedTime < 2000) {
if (SystemClock.elapsedRealtime() - backPressedTime < 2000) {

안정성 말고 별 차이는 존재하지 않지만 간격 측정이나 타이머 같은 경우 이렇게 쓰기도 한답니다~

Copy link
Member

Choose a reason for hiding this comment

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

c: 매직넘버의 상수화에 대해 아시나요?

isEnabled = false
onBackPressedDispatcher.onBackPressed()

val intent = Intent(Intent.ACTION_MAIN)
intent.addCategory(Intent.CATEGORY_HOME)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
startActivity(intent)
finish()
Comment on lines +33 to +37
Copy link
Member

Choose a reason for hiding this comment

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

c: 함수로 분리해줘도 괜찮을 것 같아요

} else {
Copy link
Member

Choose a reason for hiding this comment

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

c: 'else 예약어를 쓰지 않는다' 라는 말을 아시나요? when으로 분기해도 가독성이 좋아보일듯요

showToast(
context = this@MainActivity,
message = getString(R.string.mypage_back_handler_caution)
)
backPressedTime = System.currentTimeMillis()
}
}
})
Comment on lines +27 to +46

Choose a reason for hiding this comment

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

요거 모든 액티비티에서 사용된다면 확장함수로 빼는 방법도 좋을 것 같슴다!

Choose a reason for hiding this comment

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

base의 함정에 빠지지 않도록 신중히 결정해보세요!


}

private fun getUserInfo() {
Expand All @@ -37,4 +64,4 @@ class MainActivity : AppCompatActivity() {
tvMyPagePhoneNumberContent.text = user?.phoneNumber
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.sopt.now.presentation.ui.auth.signin
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import com.sopt.now.R
import com.sopt.now.data.User
import com.sopt.now.databinding.ActivitySigninBinding
Expand All @@ -14,34 +15,43 @@ import com.sopt.now.presentation.utils.showToast

class SignInActivity : AppCompatActivity() {
private lateinit var binding: ActivitySigninBinding
private var user: User? = null
private lateinit var viewModel: SignInViewModel
Copy link
Member

Choose a reason for hiding this comment

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

c: 조금 게으르게 선언은 어떠심

Choose a reason for hiding this comment

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

by viewModels() 알아보세요!


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySigninBinding.inflate(layoutInflater)
setContentView(binding.root)
viewModel = ViewModelProvider(this).get(SignInViewModel::class.java)

Choose a reason for hiding this comment

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

ViewModelProvider가 어떤 친구인지 공부하면 왜 Hilt의 고마움을 느낄 수 있어요


getUserInfo()
onSignInClicked()
onSignUpClicked()
}

private fun getUserInfo() {
user = intent.getSafeParcelable(KeyStorage.USER_INFO, User::class.java)
val user = intent.getSafeParcelable(KeyStorage.USER_INFO, User::class.java)
viewModel.setUser(user)
}
Comment on lines 31 to +34
Copy link

Choose a reason for hiding this comment

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

viewModel에서 SavedStateHandle라는걸 쓰면 Intent에 직접 접근해서 값을 Setting할 수 있습니다.
번거롭게 View -> ViewModel로 Set 하는 로직이 줄어들 수 있어요


private fun setupObservers() {
viewModel.signInState.observe(this) { isSuccess ->
if (isSuccess) {
showToast(this, getString(R.string.signin_signin_success))
navigateToMain(viewModel.user.value)
} else {
showToast(this, getString(R.string.signin_signin_failure))
}
}
}

private fun onSignInClicked() {
binding.btnSignInSignIn.setOnClickListener {
with(binding) {
val inputId = etSignInId.text.toString()
val inputPassword = etSignInPw.text.toString()

if (inputId == user?.id && inputPassword == user?.password) {
showToast(this@SignInActivity, getString(R.string.signin_signin_success))
navigateToMain()
} else {
showToast(this@SignInActivity, getString(R.string.signin_signin_failure))
}
viewModel.validateSignIn(inputId, inputPassword)
}
setupObservers()
Copy link
Member

Choose a reason for hiding this comment

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

ViewModel의 signInState가 변경되면 이 함수의 Observer가 변화를 자동으로 감지해서 UI 업데이트 처리를 하기 때문에 버튼 클릭 시마다 호출하지 않아도 될 것 같습니다. onCreate로 이동시켜도 괜찮을 것 같네용

}
}

Expand All @@ -51,7 +61,7 @@ class SignInActivity : AppCompatActivity() {
}
}

private fun navigateToMain() {
private fun navigateToMain(user: User?) {
val intent = Intent(this@SignInActivity, MainActivity::class.java)
intent.putExtra(KeyStorage.USER_INFO, user)
startActivity(intent)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.sopt.now.presentation.ui.auth.signin

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.sopt.now.data.User

class SignInViewModel : ViewModel() {
private val _user = MutableLiveData<User?>()
val user: LiveData<User?> = _user

private val _signInState = MutableLiveData(false)
val signInState: LiveData<Boolean> = _signInState
Comment on lines +8 to +13
Copy link

Choose a reason for hiding this comment

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

Kotlin 2.0을 쓴다면? explicit backing fields 를 활용할 수 있어욥

Suggested change
class SignInViewModel : ViewModel() {
private val _user = MutableLiveData<User?>()
val user: LiveData<User?> = _user
private val _signInState = MutableLiveData(false)
val signInState: LiveData<Boolean> = _signInState
val user: LiveData<User?>
field = MutableLiveData<User?>()
val signInState: LiveData<Boolean>
field = MutableLiveData(false)

관련 문서


fun setUser(user: User?) {
_user.value = user
}

fun validateSignIn(inputId: String, inputPassword: String) {
_signInState.value = inputId == _user.value?.id && inputPassword == _user.value?.password
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
_signInState.value = inputId == _user.value?.id && inputPassword == _user.value?.password
_signInState.value = inputId == user.value?.id && inputPassword == user.value?.password

c: _가 붙는 의미가 무엇일까요?

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.sopt.now.presentation.ui.auth.signup
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import com.sopt.now.R
import com.sopt.now.data.User
import com.sopt.now.databinding.ActivitySignupBinding
Expand All @@ -12,14 +13,28 @@ import com.sopt.now.presentation.utils.showToast

class SignUpActivity : AppCompatActivity() {
private lateinit var binding: ActivitySignupBinding
private lateinit var viewModel: SignUpViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySignupBinding.inflate(layoutInflater)
setContentView(binding.root)
viewModel = ViewModelProvider(this).get(SignUpViewModel::class.java)

onSignUpClicked()
}

private fun setupObservers() {
Copy link
Member

Choose a reason for hiding this comment

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

c: 여러개의 옵저버를 세팅해주고 있는지 함수명에 대해 고민해봅시다!

viewModel.signUpState.observe(this) { isSuccess ->
if (isSuccess) {
showToast(this@SignUpActivity, getString(R.string.signup_signup_success))
navigateToSignIn(viewModel.user.value)
} else {
showToast(this@SignUpActivity, getString(R.string.signup_signup_failure))
}
}
}

private fun onSignUpClicked() {
binding.btnSignUpSignUp.setOnClickListener {
with(binding) {
Expand All @@ -29,19 +44,14 @@ class SignUpActivity : AppCompatActivity() {
etSignUpNickname.text.toString(),
etSignUpPhoneNumber.text.toString()
)

if (SignUpValidation.isSignUpValid(user)
) {
showToast(this@SignUpActivity, getString(R.string.signup_signup_success))
navigateToSignIn(user)
} else {
showToast(this@SignUpActivity, getString(R.string.signup_signup_failure))
}
viewModel.setUser(user)
}
viewModel.validateSignUp()
setupObservers()
}
Comment on lines +49 to 51
Copy link

Choose a reason for hiding this comment

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

클릭 될때마다 옵저버가 구독되면 어떤 일이 발생할까요?!

}

private fun navigateToSignIn(user: User) {
private fun navigateToSignIn(user: User?) {
val intent = Intent(this@SignUpActivity, SignInActivity::class.java)
intent.putExtra(KeyStorage.USER_INFO, user)
startActivity(intent)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.sopt.now.presentation.ui.auth.signup

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.sopt.now.data.User
import java.util.regex.Pattern

class SignUpViewModel() : ViewModel() {
private val _user = MutableLiveData<User?>()
val user: LiveData<User?> = _user

private val _signUpState = MutableLiveData(false)
val signUpState: LiveData<Boolean> = _signUpState

fun setUser(user: User) {
_user.value = user
}

private fun isIdValid(): Boolean {
return ID_VALIDATION_PATTERN.matcher(_user.value?.id ?: "").matches()

Choose a reason for hiding this comment

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

orEmpty() 에 대해 찾아보시면 좋을 것 같네요 ㅎ.ㅎ

}

private fun isPasswordValid(): Boolean {
return PW_VALIDATION_PATTERN.matcher(_user.value?.password ?: "").matches()
}
Comment on lines +24 to +26

Choose a reason for hiding this comment

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

Suggested change
private fun isPasswordValid(): Boolean {
return PW_VALIDATION_PATTERN.matcher(_user.value?.password ?: "").matches()
}
private fun isPasswordValid(): Boolean = PW_VALIDATION_PATTERN.matcher(_user.value?.password ?: "").matches()

이렇게 쓸 수도 있습니당


private fun isNicknameValid(): Boolean {
return NICKNAME_VALIDATION_PATTERN.matcher(_user.value?.nickname ?: "").matches()
}

private fun isPhoneNumberValid(): Boolean {
return PHONE_NUMBER_VALIDATION_PATTERN.matcher(_user.value?.phoneNumber ?: "").matches()
}

fun validateSignUp() {
_signUpState.value = isIdValid() &&
isPasswordValid() &&
isNicknameValid() &&
isPhoneNumberValid()
}
Comment on lines +28 to +41

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 ID_MIN_LENGTH = 6
private const val ID_MAX_LENGTH = 10
private const val PW_MIN_LENGTH = 8
private const val PW_MAX_LENGTH = 12

private const val ID_VALIDATION_REGEX = "^[a-zA-Z0-9]{$ID_MIN_LENGTH,$ID_MAX_LENGTH}$"
private val ID_VALIDATION_PATTERN: Pattern = Pattern.compile(ID_VALIDATION_REGEX)

private const val PW_VALIDATION_REGEX = "^[a-zA-Z0-9]{$PW_MIN_LENGTH,$PW_MAX_LENGTH}$"
private val PW_VALIDATION_PATTERN: Pattern = Pattern.compile(PW_VALIDATION_REGEX)

Choose a reason for hiding this comment

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

코틀린에서는 Pattern 말고 Regex를 이용해 정규식을 사용할 수 있는데 이에 대해 공부해보셔도 좋을 것 같아요!


private const val NICKNAME_VALIDATION_REGEX = "^\\S+$"
private val NICKNAME_VALIDATION_PATTERN: Pattern =
Pattern.compile(NICKNAME_VALIDATION_REGEX)

private const val PHONE_NUMBER_VALIDATION_REGEX = "^010-\\d{4}-\\d{4}$"
private val PHONE_NUMBER_VALIDATION_PATTERN: Pattern =
Pattern.compile(PHONE_NUMBER_VALIDATION_REGEX)
}
}
Loading