Skip to content

Commit

Permalink
Merge pull request #23 from Noostak/feature/#17-social-login
Browse files Browse the repository at this point in the history
[Feature/#17] : 카카오 로그인, 구글 로그인 구현
  • Loading branch information
youjin09222 authored Jan 16, 2025
2 parents 8d5c5b6 + a079317 commit 0450000
Show file tree
Hide file tree
Showing 21 changed files with 700 additions and 29 deletions.
8 changes: 7 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import java.util.Properties

plugins {
Expand Down Expand Up @@ -30,6 +29,9 @@ android {
useSupportLibrary = true
}
buildConfigField("String", "BASE_URL", properties["base.url"].toString())
buildConfigField("String", "KAKAO_API_KEY", properties["KAKAO_API_KEY"].toString())
buildConfigField("String", "GOOGLE_CLIENT_ID", properties["GOOGLE_CLIENT_ID"].toString())
manifestPlaceholders["KAKAO_NATIVE_APP_KEY"] = properties["KAKAO_NATIVE_APP_KEY"].toString()
}

buildTypes {
Expand Down Expand Up @@ -130,4 +132,8 @@ dependencies {
// Kakao
implementation(libs.kakao.all)
implementation(libs.kakao.user)

// Google
implementation(libs.play.services.auth)
implementation(libs.google.id)
}
22 changes: 19 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

<application
android:name="com.sopt.noostak.MyApp"
Expand All @@ -29,6 +30,21 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity
android:name="com.kakao.sdk.auth.AuthCodeHandlerActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data
android:host="oauth"
android:scheme="${KAKAO_NATIVE_APP_KEY}" />
</intent-filter>
</activity>
</application>

</manifest>
6 changes: 6 additions & 0 deletions app/src/main/java/com/sopt/noostak/MyApp.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.sopt.noostak

import android.app.Application
import com.kakao.sdk.common.KakaoSdk
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber

Expand All @@ -9,9 +10,14 @@ class MyApp : Application() {
override fun onCreate() {
super.onCreate()
setTimber()
initKakao()
}

private fun setTimber() {
Timber.plant(Timber.DebugTree())
}

private fun initKakao() {
KakaoSdk.init(this, BuildConfig.KAKAO_API_KEY)
}
}
20 changes: 20 additions & 0 deletions app/src/main/java/com/sopt/noostak/di/GoogleIdModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.sopt.noostak.di

import com.sopt.noostak.BuildConfig
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Named
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object GoogleIdModule {
@Provides
@Singleton
@Named("GoogleClientId")
fun provideGoogleClientId(): String {
return BuildConfig.GOOGLE_CLIENT_ID
}
}
25 changes: 25 additions & 0 deletions app/src/main/java/com/sopt/noostak/di/PreferencesDataStore.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.sopt.noostak.di

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object PreferencesDataStore {
private val Context.dataStore by preferencesDataStore(name = "noostak_data_store")

@Singleton
@Provides
fun provideDataStore(
@ApplicationContext context: Context
): DataStore<Preferences> =
context.dataStore
}
27 changes: 27 additions & 0 deletions app/src/main/java/com/sopt/noostak/di/UserInfoModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.sopt.noostak.di

import com.sopt.data.datasource.UserDataSource
import com.sopt.data.datasourceimpl.UserDataSourceImpl
import com.sopt.data.repositoryimpl.UserInfoRepositoryImpl
import com.sopt.domain.repository.UserInfoRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
abstract class UserInfoModule {
@Binds
@Singleton
abstract fun bindsAuthDataSource(
authDataSourceImpl: UserDataSourceImpl
): UserDataSource

@Binds
@Singleton
abstract fun bindAuthRepository(
authDataSourceImpl: UserInfoRepositoryImpl
): UserInfoRepository
}
3 changes: 3 additions & 0 deletions core/src/main/java/com/sopt/core/type/SocialType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.sopt.core.type

enum class SocialType { KAKAO, GOOGLE }
4 changes: 4 additions & 0 deletions data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,8 @@ dependencies {
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.espresso.core)

// DataStore
implementation(libs.androidx.datastore.core)
implementation(libs.androidx.datastore.preferences)
}
28 changes: 28 additions & 0 deletions data/src/main/java/com/sopt/data/datasource/UserDataSource.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.sopt.data.datasource

import kotlinx.coroutines.flow.Flow

interface UserDataSource {
val accessToken: Flow<String>
val refreshToken: Flow<String>
val userId: Flow<Int>
val isAutoLogin: Flow<Boolean>
val nickName: Flow<String>
val profileImage: Flow<String>

suspend fun updateAccessToken(accessToken: String)

suspend fun updateRefreshToken(refreshToken: String)

suspend fun updateUserId(userId: Int)

suspend fun updateIsAutoLogin(isAutoLogin: Boolean)

suspend fun updateNickName(nickName: String)

suspend fun updateProfileImage(profileImage: String)

suspend fun clear()

suspend fun clearForRefreshToken()
}
123 changes: 123 additions & 0 deletions data/src/main/java/com/sopt/data/datasourceimpl/UserDataSourceImpl.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package com.sopt.data.datasourceimpl

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import com.sopt.data.datasource.UserDataSource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import java.io.IOException
import javax.inject.Inject

class UserDataSourceImpl @Inject constructor(
private val dataStore: DataStore<Preferences>
) : UserDataSource {
private object PreferencesKeys {
val accessToken = stringPreferencesKey("accessToken")
val refreshToken = stringPreferencesKey("refreshToken")
val userId = intPreferencesKey("userId")
val isAutoLogin = booleanPreferencesKey("isAutoLogin")
val nickName = stringPreferencesKey("nickName")
val profileImage = stringPreferencesKey("profileImage")
}

override val accessToken: Flow<String> = dataStore.data
.catch { handleError(it) }
.map { preferences ->
preferences[PreferencesKeys.accessToken].orEmpty()
}

override val refreshToken: Flow<String> = dataStore.data
.catch { handleError(it) }
.map { preferences ->
preferences[PreferencesKeys.refreshToken].orEmpty()
}

override val userId: Flow<Int> = dataStore.data
.catch { handleError(it) }
.map { preferences ->
preferences[PreferencesKeys.userId] ?: -1
}

override val isAutoLogin: Flow<Boolean> = dataStore.data
.catch { handleError(it) }
.map { preferences ->
preferences[PreferencesKeys.isAutoLogin] ?: false
}

override val nickName: Flow<String> = dataStore.data
.catch { handleError(it) }
.map { preferences ->
preferences[PreferencesKeys.nickName].orEmpty()
}

override var profileImage: Flow<String> = dataStore.data
.catch { handleError(it) }
.map { preferences ->
preferences[PreferencesKeys.profileImage].orEmpty()
}

override suspend fun updateAccessToken(accessToken: String) {
dataStore.edit { preferences ->
preferences[PreferencesKeys.accessToken] = accessToken
}
}

override suspend fun updateRefreshToken(refreshToken: String) {
dataStore.edit { preferences ->
preferences[PreferencesKeys.refreshToken] = refreshToken
}
}

override suspend fun updateUserId(userId: Int) {
dataStore.edit { preferences ->
preferences[PreferencesKeys.userId] = userId
}
}

override suspend fun updateIsAutoLogin(isAutoLogin: Boolean) {
dataStore.edit { preferences ->
preferences[PreferencesKeys.isAutoLogin] = isAutoLogin
}
}

override suspend fun updateNickName(nickName: String) {
dataStore.edit { preferences ->
preferences[PreferencesKeys.nickName] = nickName
}
}

override suspend fun updateProfileImage(profileImage: String) {
dataStore.edit { preferences ->
preferences[PreferencesKeys.profileImage] = profileImage
}
}

override suspend fun clear() {
dataStore.edit { preferences ->
preferences.clear()
}
}

override suspend fun clearForRefreshToken() {
dataStore.edit { preferences ->
preferences.remove(PreferencesKeys.accessToken)
preferences.remove(PreferencesKeys.refreshToken)
preferences.remove(PreferencesKeys.isAutoLogin)
}
}
}

private suspend fun FlowCollector<Preferences>.handleError(it: Throwable) {
if (it is IOException) {
emit(emptyPreferences())
} else {
throw it
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.sopt.data.repositoryimpl

import com.sopt.data.datasource.UserDataSource
import com.sopt.domain.repository.UserInfoRepository
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject

class UserInfoRepositoryImpl @Inject constructor(
private val authDataSource: UserDataSource
) : UserInfoRepository {
override fun getAccessToken(): Flow<String> = authDataSource.accessToken

override fun getRefreshToken(): Flow<String> = authDataSource.refreshToken

override fun getUserId(): Flow<Int> = authDataSource.userId

override fun getIsAutoLogin(): Flow<Boolean> = authDataSource.isAutoLogin

override fun getNickName(): Flow<String> = authDataSource.nickName

override fun getProfileImage(): Flow<String> = authDataSource.profileImage

override suspend fun saveAccessToken(accessToken: String) {
authDataSource.updateAccessToken(accessToken)
}

override suspend fun saveRefreshToken(refreshToken: String) {
authDataSource.updateRefreshToken(refreshToken)
}

override suspend fun saveUserId(userId: Int) {
authDataSource.updateUserId(userId)
}

override suspend fun saveIsAutoLogin(isAutoLogin: Boolean) {
authDataSource.updateIsAutoLogin(isAutoLogin)
}

override suspend fun saveNickName(nickName: String) {
authDataSource.updateNickName(nickName)
}

override suspend fun saveProfileImage(profileImage: String) {
authDataSource.updateProfileImage(profileImage)
}

override suspend fun clearAll() {
authDataSource.clear()
}

override suspend fun clearForRefreshToken() {
authDataSource.clearForRefreshToken()
}
}
10 changes: 10 additions & 0 deletions domain/src/main/java/com/sopt/domain/entity/UserEntity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.sopt.domain.entity

data class UserEntity(
val accessToken: String?,
val refreshToken: String?,
val userId: Int?,
val isAutoLogin: Boolean,
val nickName: String,
val profileImage: String?
)
Loading

0 comments on commit 0450000

Please sign in to comment.