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

1주차 과제 #3

Merged
merged 27 commits into from
Oct 21, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2ed7755
[add] 그래들 종속성 추가
Sangwook123 Oct 10, 2023
678ccbb
[add] DI 앱진입점
Sangwook123 Oct 10, 2023
2a6afb9
[feat] SharedPref 모듈
Sangwook123 Oct 10, 2023
2228e67
[feat] sharedpref 인터페이스
Sangwook123 Oct 10, 2023
f154dd9
[feat] sharedpref 구현부
Sangwook123 Oct 10, 2023
eed5c4e
[feat] 리포지토리 모듈
Sangwook123 Oct 10, 2023
3ff2daf
[add] hidekeyboard 확장함수
Sangwook123 Oct 10, 2023
2e16d3b
[add] uistate sealed interface
Sangwook123 Oct 10, 2023
7425030
[add] string 리소스 추가
Sangwook123 Oct 10, 2023
97ab0e7
[refactor] viewmodel, databinding
Sangwook123 Oct 10, 2023
68ee441
[feat] viewmodel, databinding, hilt, 자동로그인 구현
Sangwook123 Oct 10, 2023
192bc21
[feat] main viewmodel, databinding, hilt, 로그아웃 및 회원탈퇴 구현
Sangwook123 Oct 10, 2023
6681825
[mod] 함수화
Sangwook123 Oct 10, 2023
d3f60e3
[mod] main 액티비티 intent flag, 함수화
Sangwook123 Oct 10, 2023
ebb9f2b
[feat] login 액티비티 intent flag
Sangwook123 Oct 10, 2023
4950087
[chore] 주석
Sangwook123 Oct 10, 2023
be1b533
[chore] 자동로그인 로직 수정
Sangwook123 Oct 10, 2023
5c2515a
[chore] 코드정렬
Sangwook123 Oct 10, 2023
00037f5
[mod] 코드수정
Sangwook123 Oct 11, 2023
7ecebe3
[add] kotlin serialization
Sangwook123 Oct 13, 2023
e854340
[mod] UserModel
Sangwook123 Oct 13, 2023
f5bde1a
[feat] DataSource
Sangwook123 Oct 13, 2023
4cb3607
[refactor] repository
Sangwook123 Oct 13, 2023
1cd9faf
[add] UserDto, User Data class
Sangwook123 Oct 13, 2023
bfc61b5
[refactor] code
Sangwook123 Oct 13, 2023
2e48b6f
[chore] package
Sangwook123 Oct 13, 2023
d6b1089
Merge branch 'develop' into feature/seminar1-advanced-challenge
Sangwook123 Oct 21, 2023
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
18 changes: 14 additions & 4 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-parcelize'
id 'kotlin-kapt'
id 'com.google.dagger.hilt.android'
}

android {
Expand All @@ -25,11 +27,11 @@ android {
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '11'
jvmTarget = '17'
}

buildFeatures {
Expand All @@ -39,12 +41,20 @@ android {
}

dependencies {

implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.google.code.gson:gson:2.10.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation "com.google.dagger:hilt-android:2.44"
kapt "com.google.dagger:hilt-compiler:2.44"
implementation 'androidx.activity:activity-ktx:1.2.2'
implementation 'androidx.fragment:fragment-ktx:1.3.3'
}
Copy link
Member

Choose a reason for hiding this comment

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

Gson도 좋지만 Kotlin serialization 이라는 라이브러리도 있습니다!

관련 글은 인간 안드로이드님의 블로그를 가져와봤어요 참고해보시면 좋을 것 같습니다!

Copy link
Member Author

Choose a reason for hiding this comment

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

넵 반영해보겠습니다 ㅎㅎ


kapt {
correctErrorTypes true
}
11 changes: 8 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">

<application
android:name=".DoSoptApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand All @@ -15,20 +16,24 @@
<activity
android:name=".presentation.main.MainActivity"
android:exported="false">

</activity>

<activity
android:name=".presentation.login.LoginActivity"
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.signup.SignupActivity"
android:exported="false">
<activity
android:name=".presentation.signup.SignupActivity"
android:exported="false"
android:windowSoftInputMode="adjustResize">

</activity>
</application>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.sopt.dosopttemplate

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class DoSoptApplication : Application() {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.sopt.dosopttemplate.data.repository

import android.content.SharedPreferences
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import org.sopt.dosopttemplate.data.entity.User
import org.sopt.dosopttemplate.domain.repository.SharedPrefRepository
import javax.inject.Inject

class SharedPrefRepositoryImpl @Inject constructor(
private val sharedPref: SharedPreferences
) : SharedPrefRepository {
val gson: Gson = GsonBuilder().create()
override fun saveUserInfo(user: User?) {
val value = gson.toJson(user)
sharedPref.edit().putString("user", value).apply()
}

override fun getUserInfo(): User? {
val value = sharedPref.getString("user", "")
if (value.isNullOrBlank()) return null
return gson.fromJson(value, User::class.java)
}

override fun setAutoLogin() {
sharedPref.edit().putBoolean("autoLogin", true).apply()
}

override fun isAutoLogin(): Boolean {
return sharedPref.getBoolean("autoLogin", false)
}

override fun clearAutoLogin() {
sharedPref.edit().putBoolean("autoLogin", false).apply()
}

override fun clearSharedPref() {
sharedPref.edit().clear().apply()
}
}
19 changes: 19 additions & 0 deletions app/src/main/java/org/sopt/dosopttemplate/di/RepositoryModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.sopt.dosopttemplate.di

import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import org.sopt.dosopttemplate.data.repository.SharedPrefRepositoryImpl
import org.sopt.dosopttemplate.domain.repository.SharedPrefRepository
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
@Singleton
@Binds
abstract fun bindsSharedPrefRepository(
sharedPrefRepository: SharedPrefRepositoryImpl
): SharedPrefRepository
}
Copy link
Member

Choose a reason for hiding this comment

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

@provides 대신 @BINDS 를 활용하신 이유가 있나요?

Copy link
Member Author

Choose a reason for hiding this comment

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

viewmodel에서 domain 레이어의 repository를 data의 구현부와 연결하여 사용하기위해 @BINDS 어노테이션을 활용했습니다

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

import org.sopt.dosopttemplate.data.entity.User

interface SharedPrefRepository {

Choose a reason for hiding this comment

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

진짜 mvvm 적용 폼 미쳤네,,,

fun saveUserInfo(user: User?)
fun getUserInfo(): User?
fun setAutoLogin()
fun isAutoLogin(): Boolean
fun clearAutoLogin()
fun clearSharedPref()
}
Copy link
Member

Choose a reason for hiding this comment

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

domain layer이기 때문에 domain 의존성만을 갖고있어야 할 것 같습니다! domain 패키지의 User Model 을 생성 후 Data Layer 의 User를 mapping하는 것을 추천드립니다!

Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,32 @@ import android.content.Intent
import android.os.Bundle
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import dagger.hilt.android.AndroidEntryPoint
import org.sopt.dosopttemplate.R
import org.sopt.dosopttemplate.data.entity.User
import org.sopt.dosopttemplate.databinding.ActivityLoginBinding
import org.sopt.dosopttemplate.presentation.main.MainActivity
import org.sopt.dosopttemplate.presentation.signup.SignupActivity
import org.sopt.dosopttemplate.util.activity.hideKeyboard
import org.sopt.dosopttemplate.util.binding.BindingActivity
import org.sopt.dosopttemplate.util.context.toast
import org.sopt.dosopttemplate.util.intent.getParcelable
import org.sopt.dosopttemplate.util.view.UiState
import org.sopt.dosopttemplate.util.view.snackBar

@AndroidEntryPoint
class LoginActivity : BindingActivity<ActivityLoginBinding>(R.layout.activity_login) {
private lateinit var resultLauncher: ActivityResultLauncher<Intent>

private val viewModel by viewModels<LoginViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

binding.vm = viewModel
initResultLauncher()
initSignupButtonClickListener()
initLoginButtonClickListener()
initloginStateObserver()
initHideKeyboard()
}

private fun initResultLauncher() {
Expand All @@ -31,7 +38,7 @@ class LoginActivity : BindingActivity<ActivityLoginBinding>(R.layout.activity_lo
) { result ->
if (result.resultCode == RESULT_OK) {
val user = result.data?.getParcelable(USER_KEY, User::class.java)

viewModel.setAutoLogin(user)
binding.root.snackBar { getString(R.string.login_success_signup) }
setLoginButtonClickListener(user)
}
Expand All @@ -47,23 +54,41 @@ class LoginActivity : BindingActivity<ActivityLoginBinding>(R.layout.activity_lo

private fun initLoginButtonClickListener() {
binding.btnLoginLogin.setOnClickListener {
binding.root.snackBar { getString(R.string.login_fail_login) }
viewModel.login()
}
}

private fun setLoginButtonClickListener(user: User?) {
binding.btnLoginLogin.setOnClickListener {
if (isValidLogin(user)) {
val intent = Intent(this, MainActivity::class.java)
intent.putExtra(USER_KEY, user)
this.toast(getString(R.string.login_success_login))
startActivity(intent)
viewModel.login(user)
}
}

private fun initloginStateObserver() {
viewModel.loginState.observe(this) { state ->
when (state) {
is UiState.Success -> {
navigateToMainScreenWithUserData(state.data)
}

is UiState.Empty -> {}
else -> binding.root.snackBar { getString(R.string.login_fail_login) }
}
}
}

private fun isValidLogin(user: User?): Boolean =
binding.etLoginId.text.toString() == user?.id && binding.etLoginPw.text.toString() == user.pw
private fun navigateToMainScreenWithUserData(user: User?){
val intent = Intent(this, MainActivity::class.java)
intent.putExtra(USER_KEY, user)
this.toast(getString(R.string.login_success_login))
viewModel.setAutoLogin(user)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
}
Copy link
Member

Choose a reason for hiding this comment

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

intent에 .apply{} 로 한번에 연결된 코드를 만들면 더 깔끔한 코드가 될 것 같습니다 !

Suggested change
}
this.toast(getString(R.string.login_success_login))
viewModel.setAutoLogin(user)
Intent(this, MainActivity::class.java).apply{
putExtra(USER_KEY, user)
addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK)
startActivity(this)
}


private fun initHideKeyboard() {
binding.root.setOnClickListener { hideKeyboard() }
}

companion object {
private const val USER_KEY = "user"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.sopt.dosopttemplate.presentation.login

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import org.sopt.dosopttemplate.data.entity.User
import org.sopt.dosopttemplate.domain.repository.SharedPrefRepository
import org.sopt.dosopttemplate.util.view.UiState
import javax.inject.Inject

@HiltViewModel
class LoginViewModel @Inject constructor(
val sharedPrefRepository: SharedPrefRepository
) : ViewModel() {
private val _loginState = MutableLiveData<UiState<User?>>(UiState.Empty)
val loginState: LiveData<UiState<User?>> get() = _loginState

val id = MutableLiveData<String>()
val pw = MutableLiveData<String>()

init {
autoLogin()
}
Comment on lines +22 to +24
Copy link
Member

Choose a reason for hiding this comment

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

init Block 활용 너무 좋아욥~!!


private fun isValidLogin(user: User? = sharedPrefRepository.getUserInfo()): Boolean =
id.value == user?.id && pw.value == user?.pw

fun login(user: User? = null) {
when (if (user == null) isValidLogin() else isValidLogin(user)) {
true -> _loginState.value = UiState.Success(sharedPrefRepository.getUserInfo())
false -> _loginState.value = UiState.Failure(CODE_FAILURE)
}
Copy link
Member

Choose a reason for hiding this comment

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

오호 when 문이 신기하네요 이거

}

private fun autoLogin() {
if (sharedPrefRepository.isAutoLogin()) _loginState.value =
UiState.Success(sharedPrefRepository.getUserInfo())
}

fun setAutoLogin(user: User?) {
sharedPrefRepository.saveUserInfo(user)
sharedPrefRepository.setAutoLogin()
}

companion object {
const val CODE_FAILURE = "fail"
}
}
Loading