Android/Developers 공부하기

AndroidStudio > New Project > Login Activity 로 생성 시, 꽤나 좋은데?!

네모메모 2022. 7. 30. 14:59
반응형

AndroidStudio에서 프로젝트 생성할 때 Login Activity 로 생성했더니 꽤나 좋은 샘플이 생성되어 정리해보았다.

 

- 기본적을 MVVM, ViewModel, dataBinding viewBinding, Repository까지 모두 생성된다!!

- 예전에 이걸로 공부할껄..

    플젝 생성 시 제공하는 다른 컨셉??들 프로젝트도 좀 생성해보고, 정기적으로 안드로이드 스튜디오 제공 플젝 코드 좀 봐봐야겠다.


배운점

1. 사용자 이벤트를  VM에서 받아 유효성 체크하여 에러메시지 resID값 등 각 데이터를 설정해 LoginFormState생성하여 LiveData 갱신 -> UI 갱신하는 것이 인상적

 

2. 결과 클래스를

     >> 실제 원격 데이터 요청 및 응답에 사용하는 (Result, LoggedInUser)

     >> VM의 응답 수신 및 UI갱신 등의 후처리(LoginResult, LoggedInUserView), 

로 나뉘어 만든 것이 인상적이며 (특히 Result가 제네릭으로 값을 보유하는 게▼)

sealed class Result<out T : Any> {

    data class Success<out T : Any>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()

    override fun toString(): String {
        return when (this) {
            is Success<*> -> "Success[data=$data]"
            is Error -> "Error[exception=$exception]"
        }
    }
}

 

3. classLoginViewModelFactory:ViewModelProvider.Factory 를 따로 두어 
LoginRepository를 생성자에서 파라미터로 가지는 'LoginViewModel을 생성'했다.
(파라미터 VM 생성 간편화 언제쯤...ㅠㅠ)
ㄴ> LoginRepository는 생성자에서 LoginDataSource를 파라미터로 받는다. 

loginViewModel = ViewModelProvider(this, LoginViewModelFactory())
    .get(LoginViewModel::class.java)
class LoginViewModelFactory : ViewModelProvider.Factory {

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(LoginViewModel::class.java)) {
            return LoginViewModel(
                loginRepository = LoginRepository(
                    dataSource = LoginDataSource()
                )
            ) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}
 

 

4. ui와 data 로 나뉜 패키지 구조 (구글은 이렇게 나누는 구나..!)

 


- EditText의 리스너 등록을 확장함수를 사용했다.

fun EditText.afterTextChanged(afterTextChanged: (String) -> Unit) {
    this.addTextChangedListener(object : TextWatcher {
        override fun afterTextChanged(editable: Editable?) {
            afterTextChanged.invoke(editable.toString())
        }

        override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}

        override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
    })
}

 

 

- TextView에 setError()가 있었구나!

 

- 이메일 체크법

if (username.contains('@')) {
    Patterns.EMAIL_ADDRESS.matcher(username).matches()
}

 

 

 


동작정리

[입력값의 유효성 검사 관련 클래스 및 동작]

- 로그인폼 상태 클래스를 아래와 같이 따로 만들고, afterTextChanged 이벤트 올 때마다 

data class LoginFormState(
    val usernameError: Int? = null,
    val passwordError: Int? = null,
    val isDataValid: Boolean = false
)

아래 메소드로 유효성 체크 및 에러메시지 resID값 등 _loginForm.value(LoginFormState)에 각 데이터를 설정해 생성한다.

fun loginDataChanged(username: String, password: String) {
    if (!isUserNameValid(username)) {
        _loginForm.value = LoginFormState(usernameError = R.string.invalid_username)
    } else if (!isPasswordValid(password)) {
        _loginForm.value = LoginFormState(passwordError = R.string.invalid_password)
    } else {
        _loginForm.value = LoginFormState(isDataValid = true)
    }
}

 

[데이터 클래스]

LoginResult - 로그인 결과별로 후처리에 필요한 객체만 받도록 LoginResult 데이터클래스로 관리

data class LoginResult(
    val success: LoggedInUserView? = null,
    val error: Int? = null
)

vs 

Result - 성공/실패에 따른 하위 클래스를 가지며 성공 시 응답 데이터를  보유하기 위한 <T>를 보유

sealed class Result<out T : Any> {

    data class Success<out T : Any>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()

    override fun toString(): String {
        return when (this) {
            is Success<*> -> "Success[data=$data]"
            is Error -> "Error[exception=$exception]"
        }
    }
}

LoggedInUserView - 로그인 성공 시 후처리인 UI 갱신에 필요한 정보 들고 있는 클래스!! / 응답받은 VM에서 생성 / LoginResult에 포함

/**
 * User details post authentication that is exposed to the UI
 */
data class LoggedInUserView(
    val displayName: String
    //... other data fields that may be accessible to the UI
)

vs

LoggedInUser - Result.Success에서  <T> / 응답 성공 시 성공한 사용자정보 대한 LoggedInUser 클래스가 존재한다.

data class LoggedInUser(
    val userId: String,
    val displayName: String
)

LoginRepository클래스  : 원격 데이터 소스에서 인증 및 사용자 정보를 요청하는 클래스와 로그인 상태 및 사용자 자격 증명 정보의 메모리 내 캐시를 유지 관리합니다.

ㄴ> LoginDataSource를 생성자로 주입받음

ㄴ> 리턴 타입이 명확하다! Result<LoggedInUser>

ㄴ> 성공 시 LoggedInUser클래스 전달해서 LoginRepository의 성공 데이터 저장(캐싱용)

 

LoginDataSource클래스 : '원격 데이터 소스에서 인증 및 사용자 정보를 요청'를 실제로 처리하고 사용자 정보를 검색하는 클래스입니다.

ㄴ> 사용자 인증 조회 결과별로 Result 클래스 전달(여기에 인증정보 LoggedInUser를 포함)

ㄴ> 리턴 타입이 명확하다! Result<LoggedInUser>


[로그인 처리 과정]

1.요청

(유효성 검사 통과하는 값을 입력하여) 로그인 선택

-> VM의 로그인 처리 메소드 호출하면 > LoginRepository의 원격 로그인 처리 메소드  호출하면 >  LoginDataSource 로그인 자격 증명으로 인증 처리한다.

 

2.응답

-> 이후 LoginDataSource에서는 인증 결과에 따라 Result의 하위 클래스 객체를 생성하고 여기에 인증정보 LoggedInUser를 포함하여 리턴해준다.

class LoginDataSource {

    fun login(username: String, password: String): Result<LoggedInUser> {
        try {
            // TODO: handle loggedInUser authentication
            val fakeUser = LoggedInUser(java.util.UUID.randomUUID().toString(), "Jane Doe")
            return Result.Success(fakeUser)
        } catch (e: Throwable) {
            return Result.Error(IOException("Error logging in", e))
        }
    }

-> LoginRepository는 결과가 성공객체일 때, 내부 LoggedInUser타입 프로퍼티에 캐싱용으로 이 객체를 저장 후 리턴

-> 성공, 실패에 따른 Result 클래스 객체를 리턴받은 VM결과별 LoginResult 객체를 생성 후  내부 프로퍼티 MutableLiveData<LoginResult>()를 갱신시킨다.

-> 이 Livedata를 observe한 액티비티에서는 UI를 갱신한다.

 

 


 

 

[의문]

이 샘플에서 로그인 성공 후 아래 코드는 왜 호출하는지 ??

setResult(Activity.RESULT_OK)

[startActivityForResult(), setResult() 설명]

더보기

startActivityForResult()를 통해 액티비티를 생성하면 액티비티가 종료될 때 지정한 requestCode와 함께 onActivityResult() 메소드가 호출됩니다. 

 

이때 종료한 액티비티를 호출한 액티비티로 결과를 반환하기 위해서 setResult()를 호출하여 resultCode와 result Intent를 지정할 수 있습니다.

 

출처 : https://scshim.tistory.com/50

 

반응형