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/#4] 4주차 필수과제 #9

Open
wants to merge 19 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ce4217f
[add/#8] 서버통신 의존성 추가
SYAAINN Nov 5, 2024
1875f18
[del/#8] 미사용하게 된 data class를 삭제합니다.
SYAAINN Nov 15, 2024
8075f87
[chore/#8] 패키지 이름을 utils -> util로 변경합니다.
SYAAINN Nov 15, 2024
265bb45
[feat/#8] 서버통신을 위한 dto를 생성합니다.
SYAAINN Nov 15, 2024
7d6c91f
[chore/#8] localProperties 변수명을 수정합니다.
SYAAINN Nov 15, 2024
a99668b
[del/#8] 미사용 object를 삭제합니다.
SYAAINN Nov 15, 2024
75e5dc5
[feat/#8] 서버통신 api를 구현합니다.
SYAAINN Nov 15, 2024
376f458
[feat/#8] 서버통신 response에 대한 템플릿을 생성합니다.
SYAAINN Nov 15, 2024
135e9ff
[feat/#8] 서버통신을 위한 retrofit 객체를 생성합니다.
SYAAINN Nov 15, 2024
d16061b
[feat/#8] 로그인 시 받은 token 값을 불러오는 dataSource를 구현합니다.
SYAAINN Nov 15, 2024
98f2445
[feat/#8] token을 위한 repository를 생성합니다.
SYAAINN Nov 15, 2024
e1323ca
[feat/#8] 의존성 주입을 위한 module을 생성합니다.
SYAAINN Nov 15, 2024
e845e52
[feat/#8] 의존성 주입을 위한 module을 생성합니다.
SYAAINN Nov 15, 2024
3ba2ba5
[add/#8] 마이페이지 서버통신 상태를 저장할 State를 생성합니다.
SYAAINN Nov 15, 2024
c08635f
[chore/#8] 서버통신에 맞추어 SignInState, SignUpState를 수정합니다.
SYAAINN Nov 15, 2024
bf155e7
[feat/#8] 서버통신 로직을 viewModel에 적용합니다.
SYAAINN Nov 15, 2024
7e6b0c6
[refactor/#8] viewModel 선언 위치를 navHost 에서 Route 단으로 변경합니다.
SYAAINN Nov 15, 2024
c4f2044
[refactor/#8] navigate 함수를 상위 컴포저블로 이동합니다.
SYAAINN Nov 15, 2024
35bed35
[refactor/#8] 바텀 네비게이션 구조를 수정합니다.
SYAAINN Nov 15, 2024
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
26 changes: 20 additions & 6 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import java.util.Properties

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
Expand All @@ -7,6 +9,10 @@ plugins {
id("kotlin-kapt") // kapt 플러그인 추가
}

val localProperties = Properties().apply {
load(project.rootProject.file("local.properties").inputStream())
}

android {
namespace = "org.sopt.and"
compileSdk = 34
Expand All @@ -18,6 +24,7 @@ android {
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
buildConfigField("String","BASE_URL", localProperties["base.url"].toString())
}

buildTypes {
Expand All @@ -38,6 +45,7 @@ android {
}
buildFeatures {
compose = true
buildConfig = true
}
}

Expand All @@ -56,17 +64,23 @@ dependencies {
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.compose.navigation)
implementation(libs.kotlinx.serialization.json)
// Hilt 관련 의존성 추가
implementation(libs.hilt.android)
implementation(libs.hilt.navigation.compose)
implementation(libs.androidx.appcompat)
kapt(libs.hilt.compiler)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
// Hilt 관련 의존성 추가
implementation(libs.hilt.android)
implementation(libs.hilt.navigation.compose)
implementation(libs.androidx.appcompat)
kapt(libs.hilt.compiler)
// Network
implementation(platform(libs.okhttp.bom))
implementation(libs.okhttp)
implementation(libs.okhttp.logging.interceptor)
implementation(libs.retrofit)
implementation(libs.retrofit.kotlin.serialization.converter)
implementation(libs.kotlinx.serialization.json)
}
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".WavveApp"
android:allowBackup="true"
Expand All @@ -12,6 +14,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ANDANDROID"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".presentation.ui.navigation.WavveActivity"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.sopt.and.data.local

interface TokenLocalDataSource {
var token: String
fun clearInfo()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.sopt.and.data.local

import android.content.SharedPreferences
import javax.inject.Inject

class TokenLocalDataSourceImpl @Inject constructor(
private val sharedPreferences: SharedPreferences
): TokenLocalDataSource {
override var token: String
get() = sharedPreferences.getString(TOKEN, "") ?: ""
set(value) = sharedPreferences.edit().putString(TOKEN, value).apply()

override fun clearInfo() {
sharedPreferences.edit().clear().apply()
Copy link

Choose a reason for hiding this comment

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

clear는 현재 모든 SharedPreferences 데이터를 지울 수 있으므로, 나중에 Token만 지우고 싶을 때는 clear() 대신 remove(Token)을 사용할 수 있다고 합니다!

}

companion object {
private const val TOKEN = "TOKEN"
}
}
14 changes: 14 additions & 0 deletions app/src/main/java/org/sopt/and/data/remote/ApiResponse.kt
Copy link
Contributor

Choose a reason for hiding this comment

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

사용하지 않는 친구인 것 같은데!

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.sopt.and.data.remote

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class ApiResponse<T>(
@SerialName("status")
val status: Int,
@SerialName("message")
val message: String,
@SerialName("result")
val result: T
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

ApiResponse 는 @SerialName 과 변수 선언을 분리했는데 어떤 곳은 한줄로 어떤 곳은 여러 줄로 하신 이유가 궁금해요

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.sopt.and.data.remote.dto.request

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class RequestLoginDto(
@SerialName("username") val username: String,
@SerialName("password") val password: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.sopt.and.data.remote.dto.request

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class RequestUserRegistrationDto (
@SerialName("username") val username: String,
@SerialName("password") val password: String,
@SerialName("hobby") val hobby: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.sopt.and.data.remote.dto.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class ResponseGetMyHobbyDto(
@SerialName("result") val result: GetMyHobbyResult
)
Comment on lines +6 to +9
Copy link
Contributor

Choose a reason for hiding this comment

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

이렇게 공통되는 부분에 대한 BaseResponse를 만들어두셔도 좋습니다. (ApiResponse의 형태를 이렇게 바꾸면 될 것 같네요!)


@Serializable
data class GetMyHobbyResult(
@SerialName("hobby") val hobby: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.sopt.and.data.remote.dto.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class ResponseLoginDto (
@SerialName("result") val result: LoginResult
)

@Serializable
data class LoginResult(
@SerialName("token") val token: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.sopt.and.data.remote.dto.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class ResponseUserRegistrationDto(
@SerialName("result") val result: UserRegistrationResult
)

@Serializable
data class UserRegistrationResult(
@SerialName("no") val no: Int
)
21 changes: 21 additions & 0 deletions app/src/main/java/org/sopt/and/data/remote/service/AuthService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.sopt.and.data.remote.service

import org.sopt.and.data.remote.dto.request.RequestLoginDto
import org.sopt.and.data.remote.dto.request.RequestUserRegistrationDto
import org.sopt.and.data.remote.dto.response.ResponseLoginDto
import org.sopt.and.data.remote.dto.response.ResponseUserRegistrationDto
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.POST

interface AuthService {
@POST("user")
fun registerUser(
@Body requestUserRegistrationDto: RequestUserRegistrationDto
): Call<ResponseUserRegistrationDto>

@POST("login")
fun login(
@Body requestLoginDto: RequestLoginDto
): Call<ResponseLoginDto>
}
Comment on lines +11 to +21
Copy link

Choose a reason for hiding this comment

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

API 엔드포인트가 하드코딩되어 있는데 추후 변경 및 유지보수 할 때 BuildConfig를 활용하거나 별도로 상수화하면 편리하다고 합니다!!

13 changes: 13 additions & 0 deletions app/src/main/java/org/sopt/and/data/remote/service/UserService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.sopt.and.data.remote.service

import org.sopt.and.data.remote.dto.response.ResponseGetMyHobbyDto
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Header

interface UserService {
@GET("user/my-hobby")
fun getMyHobby(
@Header("token") token: String
): Call<ResponseGetMyHobbyDto>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.sopt.and.data.repository

interface TokenRepository {
Copy link
Contributor

Choose a reason for hiding this comment

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

이 Repository만 data에 위치시키신 이유가 뭔가요!

Copy link
Contributor

Choose a reason for hiding this comment

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

아 지금 보니까 domain이 따로 없군요 ㅋㅋ

fun getToken(): String
fun setToken(token: String)
fun clearInfo()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.sopt.and.data.repositoryimpl

import android.util.Log
import org.sopt.and.data.local.TokenLocalDataSource
import org.sopt.and.data.repository.TokenRepository
import javax.inject.Inject

class TokenRepositoryImpl @Inject constructor(
private val tokenLocalDataSource: TokenLocalDataSource
) : TokenRepository {
override fun getToken(): String = tokenLocalDataSource.token

override fun setToken(token: String) {
tokenLocalDataSource.token = token
Log.d("TokenDataStore", "Token Saved: $token")
Copy link
Contributor

Choose a reason for hiding this comment

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

불필요한 로그는 지워줍시다

}

override fun clearInfo() {
tokenLocalDataSource.clearInfo()
}
}
38 changes: 38 additions & 0 deletions app/src/main/java/org/sopt/and/di/ApiFactory.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.sopt.and.di

import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.sopt.and.BuildConfig
import org.sopt.and.data.remote.service.AuthService
import org.sopt.and.data.remote.service.UserService
import retrofit2.Retrofit

object ApiFactory {
private const val BASE_URL: String = BuildConfig.BASE_URL

private val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}

private val client = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()

val retrofit: Retrofit by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
.build()
}

inline fun <reified T> create(): T = retrofit.create(T::class.java)
}

object ServicePool {
val authService = ApiFactory.create<AuthService>()
val userService = ApiFactory.create<UserService>()
}
19 changes: 19 additions & 0 deletions app/src/main/java/org/sopt/and/di/RepositoryModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.sopt.and.di

import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import org.sopt.and.data.repository.TokenRepository
import org.sopt.and.data.repositoryimpl.TokenRepositoryImpl
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
@Binds
@Singleton
abstract fun bindTokenRepository(
tokenRepositoryImpl: TokenRepositoryImpl
): TokenRepository
}
Comment on lines +10 to +19
Copy link
Collaborator

Choose a reason for hiding this comment

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

@Bind @provide 의 차이를 알고 계신가요 ?

20 changes: 20 additions & 0 deletions app/src/main/java/org/sopt/and/di/SharedPreferencesModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.sopt.and.di

import android.content.Context
import android.content.SharedPreferences
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 SharedPreferencesModule {
@Provides
@Singleton
fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences {
return context.getSharedPreferences("token_prefs", Context.MODE_PRIVATE)
}
}
20 changes: 20 additions & 0 deletions app/src/main/java/org/sopt/and/di/TokenDataStoreModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.sopt.and.di

import android.content.SharedPreferences
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import org.sopt.and.data.local.TokenLocalDataSource
import org.sopt.and.data.local.TokenLocalDataSourceImpl
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object TokenLocalDataSourceModule {
@Provides
@Singleton
fun provideTokenLocalDataSource(sharedPreferences: SharedPreferences): TokenLocalDataSource {
return TokenLocalDataSourceImpl(sharedPreferences)
}
}
Loading