-
Notifications
You must be signed in to change notification settings - Fork 4
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
[AN] feat: 소셜 로그인 기능 구현 #235
Changes from 20 commits
ad222ea
3f08cfc
1697875
bb066d3
d1f7ed0
bce127f
31f7faa
a9e266c
3bd71ee
fa97311
bd35baf
7e59267
cff5507
460efe8
4816d66
5c24439
a40f690
e7f8eaa
faf9967
9bbc81c
a146f29
6bc2bc0
23a9bfc
407b3b5
2ab0d18
b19b1f7
5cc2224
18ba7c5
bf96baa
2d11ffe
73a59ae
82a5c97
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,6 @@ | ||
package com.now.naaga.data.local | ||
|
||
interface AuthPreference { | ||
fun getAccessToken(): String? | ||
fun setAccessToken(newToken: String) | ||
} | ||
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. 토큰 관리를 추상화한 것 같습니다. 그런데 Preference라는 네이밍은 안드로이드 프레임워크의 SharedPreference를 특정하는 느낌이 강하게 느껴집니다. 추상화라면 특정 기술이 떠오르면 안 될 것 같아요! 일반적으로 데이터 소스라 불리는 기능을 하고 있는 것 같은데 네이밍 후보 중 하나로 고려해봐도 좋을 것 같아요 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. 해당 네이밍에 대해서 저도 데이터 소스로 할지 그냥 Preference를 할 지 고민을 많이 했는데요.... |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.now.naaga.data.local | ||
|
||
import android.content.Context | ||
import android.content.SharedPreferences | ||
|
||
class KakaoAuthPreference(context: Context): AuthPreference { | ||
private val pref: SharedPreferences = | ||
context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE) | ||
|
||
override fun getAccessToken(): String? { | ||
return pref.getString(ACCESS_TOKEN_KEY, "") | ||
} | ||
|
||
override fun setAccessToken(newToken: String) { | ||
pref.edit().putString(ACCESS_TOKEN_KEY, newToken).apply() | ||
} | ||
|
||
companion object { | ||
private const val PREFERENCE_NAME = "ACCESS_TOKEN_" | ||
private const val ACCESS_TOKEN_KEY = "access_token_key" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.now.naaga.data.mapper | ||
|
||
import com.now.domain.model.PlatformAuth | ||
import com.now.domain.model.AuthPlatformType | ||
import com.now.domain.model.NaagaAuth | ||
import com.now.naaga.data.remote.dto.NaagaAuthDto | ||
import com.now.naaga.data.remote.dto.PlatformAuthDto | ||
|
||
fun PlatformAuthDto.toDomain(): PlatformAuth { | ||
return PlatformAuth(token, AuthPlatformType.findByName(type)) | ||
} | ||
|
||
fun PlatformAuth.toDto(): PlatformAuthDto { | ||
return PlatformAuthDto(token, type.name) | ||
} | ||
|
||
fun NaagaAuthDto.toDomain(): NaagaAuth { | ||
return NaagaAuth(accessToken, refreshToken) | ||
} | ||
|
||
fun NaagaAuth.toDto(): NaagaAuthDto { | ||
return NaagaAuthDto(accessToken, refreshToken) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.now.naaga.data.remote.dto | ||
|
||
import kotlinx.serialization.Serializable | ||
|
||
@Serializable | ||
data class NaagaAuthDto( | ||
val accessToken: String, | ||
val refreshToken: String?, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.now.naaga.data.remote.dto | ||
|
||
import kotlinx.serialization.Serializable | ||
|
||
@Serializable | ||
data class PlatformAuthDto( | ||
val token: String, | ||
val type: String, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.now.naaga.data.remote.retrofit.service | ||
|
||
import com.now.naaga.data.remote.dto.NaagaAuthDto | ||
import com.now.naaga.data.remote.dto.PlatformAuthDto | ||
import retrofit2.Call | ||
import retrofit2.http.Body | ||
import retrofit2.http.POST | ||
|
||
interface AuthService { | ||
@POST("/auth") | ||
fun requestToken( | ||
@Body platformAuthDto: PlatformAuthDto, | ||
): Call<NaagaAuthDto> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.now.naaga.data.repository | ||
|
||
import com.now.domain.model.NaagaAuth | ||
import com.now.domain.model.PlatformAuth | ||
import com.now.domain.repository.AuthRepository | ||
import com.now.naaga.data.mapper.toDomain | ||
import com.now.naaga.data.mapper.toDto | ||
import com.now.naaga.data.remote.retrofit.ServicePool | ||
import com.now.naaga.data.remote.retrofit.fetchNaagaResponse | ||
|
||
class DefaultAuthRepository : AuthRepository { | ||
override fun getToken( | ||
platformAuth: PlatformAuth, | ||
callback: (Result<NaagaAuth>) -> Unit, | ||
) { | ||
val call = ServicePool.authService.requestToken(platformAuth.toDto()) | ||
call.fetchNaagaResponse( | ||
onSuccess = { callback(Result.success(it.toDomain())) }, | ||
onFailure = { callback(Result.failure(it)) }, | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package com.now.naaga.presentation.login | ||
|
||
import android.content.Context | ||
import android.content.Intent | ||
import android.os.Bundle | ||
import androidx.appcompat.app.AppCompatActivity | ||
import androidx.core.view.WindowInsetsControllerCompat | ||
import androidx.lifecycle.ViewModelProvider | ||
import com.now.naaga.R | ||
import com.now.naaga.data.local.KakaoAuthPreference | ||
import com.now.naaga.data.repository.DefaultAuthRepository | ||
import com.now.naaga.databinding.ActivityLoginBinding | ||
import com.now.naaga.presentation.beginadventure.BeginAdventureActivity | ||
import com.now.naaga.util.loginWithKakao | ||
|
||
class LoginActivity : AppCompatActivity() { | ||
private lateinit var binding: ActivityLoginBinding | ||
private lateinit var viewModel: LoginViewModel | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
binding = ActivityLoginBinding.inflate(layoutInflater) | ||
setContentView(binding.root) | ||
binding.lifecycleOwner = this | ||
initViewModel() | ||
setClickListeners() | ||
setStatusBar() | ||
viewModel.fetchToken() | ||
} | ||
|
||
private fun initViewModel() { | ||
val authRepository = DefaultAuthRepository() | ||
val authPreference = KakaoAuthPreference(this) | ||
val factory = LoginViewModelFactory(authRepository, authPreference) | ||
viewModel = ViewModelProvider(this, factory)[LoginViewModel::class.java] | ||
} | ||
|
||
private fun setClickListeners() { | ||
binding.ivLoginKakao.setOnClickListener { | ||
loginWithKakao(this) { navigateHome() } | ||
} | ||
} | ||
|
||
private fun setStatusBar() { | ||
window.apply { | ||
statusBarColor = getColor(R.color.white) | ||
WindowInsetsControllerCompat(this, this.decorView).isAppearanceLightStatusBars = true | ||
} | ||
} | ||
|
||
private fun navigateHome() { | ||
startActivity(BeginAdventureActivity.getIntent(this)) | ||
finish() | ||
} | ||
|
||
companion object { | ||
fun getIntent(context: Context): Intent { | ||
return Intent(context, LoginActivity::class.java) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package com.now.naaga.presentation.login | ||
|
||
import androidx.lifecycle.ViewModel | ||
import com.kakao.sdk.auth.TokenManagerProvider | ||
import com.now.domain.model.AuthPlatformType.KAKAO | ||
import com.now.domain.model.PlatformAuth | ||
import com.now.domain.repository.AuthRepository | ||
import com.now.naaga.data.local.AuthPreference | ||
|
||
class LoginViewModel( | ||
private val authRepository: AuthRepository, | ||
private val authPreference: AuthPreference, | ||
) : ViewModel() { | ||
fun fetchToken() { | ||
val token = TokenManagerProvider.instance.manager.getToken()?.accessToken | ||
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. 카카오 sdk에 의존성이 생기는 부분인데,액티비티에서 토큰을 받아서 뷰모델의 인자로 넘겨주는 건 어떤가요? 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. 뷰모델 테스트에 어려움이 생길 것 같다는 의견에 공감합니다! |
||
token?.let { PlatformAuth(it, KAKAO) }?.let { platformAuth -> | ||
authRepository.getToken( | ||
platformAuth, | ||
callback = { result -> | ||
result | ||
.onSuccess { authPreference.setAccessToken(it.accessToken) } | ||
.onFailure { } | ||
}, | ||
) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package com.now.naaga.presentation.login | ||
|
||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.ViewModelProvider | ||
import com.now.domain.repository.AuthRepository | ||
import com.now.naaga.data.local.AuthPreference | ||
|
||
class LoginViewModelFactory( | ||
private val authRepository: AuthRepository, | ||
private val authPreference: AuthPreference, | ||
) : | ||
ViewModelProvider.Factory { | ||
override fun <T : ViewModel> create(modelClass: Class<T>): T { | ||
if (modelClass.isAssignableFrom(LoginViewModel::class.java)) { | ||
return LoginViewModel(authRepository, authPreference) as T | ||
} else { | ||
throw IllegalArgumentException() | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.now.naaga.presentation.login | ||
|
||
import android.app.Application | ||
import com.kakao.sdk.common.KakaoSdk | ||
import com.now.naaga.BuildConfig | ||
|
||
class NaagaApplication : Application() { | ||
override fun onCreate() { | ||
super.onCreate() | ||
KakaoSdk.init(this, BuildConfig.KAKAO_NATIVE_APP_KEY) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,10 @@ | ||
package com.now.naaga.presentation.mypage | ||
|
||
import android.util.Log | ||
import androidx.lifecycle.LiveData | ||
import androidx.lifecycle.MutableLiveData | ||
import androidx.lifecycle.ViewModel | ||
import com.kakao.sdk.user.UserApiClient | ||
import com.now.domain.model.OrderType | ||
import com.now.domain.model.Place | ||
import com.now.domain.model.Rank | ||
|
@@ -30,6 +32,9 @@ class MyPageViewModel( | |
private val _errorMessage = MutableLiveData<String>() | ||
val errorMessage: LiveData<String> = _errorMessage | ||
|
||
private val _nickname = MutableLiveData<String>() | ||
val nickname: LiveData<String> = _nickname | ||
|
||
fun fetchRank() { | ||
rankRepository.getMyRank { result: Result<Rank> -> | ||
result | ||
|
@@ -54,10 +59,27 @@ class MyPageViewModel( | |
} | ||
} | ||
|
||
fun fetchNickname() { | ||
UserApiClient.instance.me { user, error -> | ||
if (error != null) { | ||
Log.d(KAKAO_USER_INFO_LOG_TAG, KAKAO_USER_INFO_FAIL_MESSAGE + error) | ||
} else if (user != null) { | ||
Log.d(KAKAO_USER_INFO_LOG_TAG, KAKAO_USER_INFO_SUCCESS_MESSAGE) | ||
Comment on lines
+64
to
+67
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. 필요하지 않은 로그라면 삭제해볼까요? 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. 만일 카카오톡에서 사용자 정보를 불러오지 못했을 때 발생할 수 있는 에러에 대해 개발자가 상황을 확인하고 처리할 수 있도록 해당 로그를 넣었습니다. |
||
_nickname.value = user.kakaoAccount?.profile?.nickname.toString() | ||
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. 위와 마찬가지로 뷰모델 안에서 카카오에 대한 의존성이 발생하는 것 같습니다. |
||
} | ||
} | ||
} | ||
|
||
private fun setErrorMessage(throwable: Throwable) { | ||
when (throwable) { | ||
is NaagaThrowable.ServerConnectFailure -> | ||
_errorMessage.value = throwable.message | ||
} | ||
} | ||
|
||
companion object { | ||
private const val KAKAO_USER_INFO_LOG_TAG = "kakao user" | ||
private const val KAKAO_USER_INFO_FAIL_MESSAGE = "사용자 정보 요청 실패" | ||
private const val KAKAO_USER_INFO_SUCCESS_MESSAGE = "사용자 정보 요청 성공" | ||
} | ||
} |
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.
오 이 친구들은 뭔가요?
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.
나중에 저희가 앱 출시하면서 난독화를 진행할 때 카카오 SDK까지 난독화 되지 않도록 막아주는 코드입니다!
카카오 SDK 관련 코드까지 난독화가 되면 해당 기능이 잘 실행되지 않아 API를 사용할 수 없다고 하네요....
그래서 해당 코드 추가했습니다~!