diff --git a/app/build.gradle b/app/build.gradle index 96f1576..5bb15d6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,6 +2,9 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' id 'org.jetbrains.kotlin.plugin.serialization' + id 'com.google.dagger.hilt.android' + id 'kotlin-android' + id 'kotlin-kapt' } Properties properties = new Properties() @@ -54,8 +57,8 @@ android { } dependencies { - implementation 'androidx.core:core-ktx:1.13.0' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0' + implementation 'androidx.core:core-ktx:1.13.1' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.0' implementation 'androidx.activity:activity-compose:1.9.0' implementation platform('androidx.compose:compose-bom:2024.05.00') implementation 'androidx.compose.ui:ui' @@ -73,10 +76,10 @@ dependencies { implementation 'androidx.compose.material:material:1.6.7' // Lifecycle Viewmodel - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0' // Fragment && Activity - implementation 'androidx.fragment:fragment-ktx:1.6.2' + implementation 'androidx.fragment:fragment-ktx:1.7.1' implementation 'androidx.activity:activity-ktx:1.9.0' // Retrofit @@ -91,4 +94,13 @@ dependencies { // Coil implementation 'io.coil-kt:coil-compose:2.6.0' + + // Hilt + implementation 'com.google.dagger:hilt-android:2.51' + kapt 'com.google.dagger:hilt-compiler:2.51.1' + implementation 'androidx.hilt:hilt-navigation-compose:1.2.0' +} + +kapt { + correctErrorTypes true } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f73f094..a846894 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,8 +5,8 @@ createBaseRetrofit(): T = baseRetrofit.create() - inline fun createFollowerRetrofit(): T = followerRetrofit.create() -} - -object ServicePool { - val authService = ApiFactory.createBaseRetrofit() - val followerService = ApiFactory.createFollowerRetrofit() + @Provides + @Singleton + fun provideFollowerService(followerRetrofit: Retrofit): FollowerService { + return followerRetrofit.create(FollowerService::class.java) + } } \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/repository/FollowerRepository.kt b/app/src/main/java/com/sopt/now/compose/repository/FollowerRepository.kt new file mode 100644 index 0000000..91a0139 --- /dev/null +++ b/app/src/main/java/com/sopt/now/compose/repository/FollowerRepository.kt @@ -0,0 +1,22 @@ +package com.sopt.now.compose.repository + +import com.sopt.now.compose.data.model.ResponseUserDto +import com.sopt.now.compose.data.network.FollowerService +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import retrofit2.Response +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class FollowerRepository @Inject constructor( + private val followerService: FollowerService, +) { + suspend fun getUserList(page: Int): Result> { + return withContext(Dispatchers.IO) { + runCatching { + followerService.getUserList(page).execute() + } + } + } +} diff --git a/app/src/main/java/com/sopt/now/compose/ui/SoptApp.kt b/app/src/main/java/com/sopt/now/compose/ui/SoptApp.kt deleted file mode 100644 index aedf704..0000000 --- a/app/src/main/java/com/sopt/now/compose/ui/SoptApp.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.sopt.now.compose.ui - -import androidx.compose.runtime.Composable -import com.sopt.now.compose.ui.base.SoptAppNavHost - -@Composable -fun SoptApp() { - SoptAppNavHost() -} \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/ui/base/SoptAppNavHost.kt b/app/src/main/java/com/sopt/now/compose/ui/base/SoptAppNavHost.kt index 58ebb65..51d231e 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/base/SoptAppNavHost.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/base/SoptAppNavHost.kt @@ -13,7 +13,7 @@ import com.sopt.now.compose.ui.signUp.SignUpScreen fun SoptAppNavHost() { val navController: NavHostController = rememberNavController() - NavHost(navController = navController, startDestination = "sign_in") { + NavHost(navController = navController, startDestination = "main") { composable("sign_in") { SignInScreen( onNavigateToHome = navController, diff --git a/app/src/main/java/com/sopt/now/compose/ui/home/HomeScreen.kt b/app/src/main/java/com/sopt/now/compose/ui/home/HomeScreen.kt index ec787af..dc0dded 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/home/HomeScreen.kt @@ -8,11 +8,12 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview -import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.hilt.navigation.compose.hiltViewModel import com.sopt.now.compose.R + @Composable -fun HomeScreen(homeViewModel: HomeViewModel = viewModel()) { +fun HomeScreen(homeViewModel: HomeViewModel = hiltViewModel()) { val followerState by homeViewModel.followerState.collectAsState() LazyColumn(modifier = Modifier.fillMaxSize()) { diff --git a/app/src/main/java/com/sopt/now/compose/ui/home/HomeViewModel.kt b/app/src/main/java/com/sopt/now/compose/ui/home/HomeViewModel.kt index a79e0bb..8bd5773 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/home/HomeViewModel.kt @@ -1,59 +1,66 @@ package com.sopt.now.compose.ui.home -import android.util.Log +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.sopt.now.compose.data.model.Profile -import com.sopt.now.compose.data.model.ResponseUserDto import com.sopt.now.compose.data.model.UserDataDto -import com.sopt.now.compose.data.module.ServicePool +import com.sopt.now.compose.repository.FollowerRepository +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response +import kotlinx.coroutines.launch +import javax.inject.Inject -class HomeViewModel : ViewModel() { - private val followerService by lazy { ServicePool.followerService } +@HiltViewModel +class HomeViewModel @Inject constructor( + private val followerRepository: FollowerRepository, +) : ViewModel() { private val _followerState = MutableStateFlow>(emptyList()) val followerState = _followerState.asStateFlow() - val friendList = mutableListOf() + private var _eventNetworkError = MutableLiveData(false) + + private var _isNetworkErrorShown = MutableLiveData(false) + + private val friendList = mutableListOf() init { fetchFollowerList() } private fun fetchFollowerList() { - followerService.getUserList(page = 0).enqueue(object : Callback { - override fun onResponse( - call: Call, - response: Response, - ) { - if (response.isSuccessful) { - val data = response.body()?.data - if (data != null) { + viewModelScope.launch { + followerRepository.getUserList(0) + .onSuccess { response -> + response.body()?.data?.let { data -> _followerState.value = data mapFollowersToFriendList(data) + _eventNetworkError.value = false + _isNetworkErrorShown.value = false + } ?: run { + _eventNetworkError.value = true } } - } + .onFailure { exception -> + _eventNetworkError.value = true + } + } + } - override fun onFailure(call: Call, t: Throwable) { - Log.e("HomeError", "${t.message}") - } - }) + fun onNetworkErrorShown() { + _isNetworkErrorShown.value = true } - fun mapFollowersToFriendList(followers: List) { - for (follower in followers) { - friendList.add( - Profile( - profileImage = follower.avatar, - name = "${follower.firstName} ${follower.lastName}", - description = follower.email - ) + private fun mapFollowersToFriendList(followers: List) { + friendList.clear() + friendList.addAll(followers.map { follower -> + Profile( + profileImage = follower.avatar, + name = "${follower.firstName} ${follower.lastName}", + description = follower.email ) - } + }) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/sopt/now/compose/ui/myPage/MyPageViewModel.kt b/app/src/main/java/com/sopt/now/compose/ui/myPage/MyPageViewModel.kt index 4a773bc..a1b077c 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/myPage/MyPageViewModel.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/myPage/MyPageViewModel.kt @@ -3,16 +3,10 @@ package com.sopt.now.compose.ui.myPage import androidx.lifecycle.ViewModel import com.sopt.now.compose.data.model.ResponseInfoDto import com.sopt.now.compose.data.model.UserInfoDto -import com.sopt.now.compose.data.module.ServicePool import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response class MyPageViewModel : ViewModel() { - private val authService by lazy { ServicePool.authService } private val _infoState = MutableStateFlow( @@ -23,36 +17,4 @@ class MyPageViewModel : ViewModel() { ) ) val infoState = _infoState.asStateFlow() - - init { - fetchInfo() - } - - private fun fetchInfo() { - authService.memberInfo().enqueue(object : Callback { - override fun onResponse( - call: Call, - response: Response, - ) { - if (response.isSuccessful) { - val data = response.body() - if (data != null) { - _infoState.update { - data - } - } - } - } - - override fun onFailure(call: Call, t: Throwable) { - _infoState.update { - ResponseInfoDto( - code = -1, - message = "${t.message}", - data = UserInfoDto(authenticationId = "", nickname = "", phone = "") - ) - } - } - }) - } } \ No newline at end of file diff --git a/app/src/main/java/com/sopt/now/compose/ui/signIn/SignInScreen.kt b/app/src/main/java/com/sopt/now/compose/ui/signIn/SignInScreen.kt index 1fd0b28..bce922c 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/signIn/SignInScreen.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/signIn/SignInScreen.kt @@ -82,20 +82,7 @@ fun SignInScreen( SoptOutlinedButton( text = R.string.btn_sign_in, onClick = { - if (isSignInButtonEnabled) { - signInViewModel.signIn( - RequestSignInDto( - authenticationId = id, - password = password, - ) - ) - } else { - Toast.makeText( - context, - R.string.sign_up_fail, - Toast.LENGTH_SHORT - ).show() - } + }, enabled = true ) diff --git a/app/src/main/java/com/sopt/now/compose/ui/signIn/SignInViewModel.kt b/app/src/main/java/com/sopt/now/compose/ui/signIn/SignInViewModel.kt index 42f9c72..a39d5b6 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/signIn/SignInViewModel.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/signIn/SignInViewModel.kt @@ -1,19 +1,11 @@ package com.sopt.now.compose.ui.signIn import androidx.lifecycle.ViewModel -import com.sopt.now.compose.data.model.RequestSignInDto -import com.sopt.now.compose.data.model.ResponseSignInDto import com.sopt.now.compose.data.model.SignInState -import com.sopt.now.compose.data.module.ServicePool import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response class SignInViewModel : ViewModel() { - private val authService by lazy { ServicePool.authService } private val _signInState = MutableStateFlow(SignInState(isSuccess = false, message = "")) val signInState = _signInState.asStateFlow() @@ -31,39 +23,4 @@ class SignInViewModel : ViewModel() { fun updatePassword(newPassword: String) { _password.value = newPassword } - - - fun signIn(request: RequestSignInDto) { - authService.signIn(request).enqueue( - object : Callback { - override fun onResponse( - call: Call, - response: Response, - ) { - if (response.isSuccessful) { - val userId = response.headers()["location"] - - _signInState.update { - SignInState( - isSuccess = true, - message = "유저 아이디는 $userId" - ) - } - } else { - val error = response.code() - _signInState.update { - SignInState( - isSuccess = false, - message = "로그인 실패 : $error" - ) - } - } - } - - override fun onFailure(call: Call, t: Throwable) { - _signInState.update { SignInState(isSuccess = false, message = "서버 에러") } - } - } - ) - } } diff --git a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt index 5596261..8a2c19e 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpScreen.kt @@ -29,7 +29,6 @@ import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavHostController import com.sopt.now.compose.R -import com.sopt.now.compose.data.model.RequestSignUpDto import com.sopt.now.compose.ui.base.SoptInputTextField import com.sopt.now.compose.ui.base.SoptOutlinedButton import com.sopt.now.compose.ui.base.SoptPasswordTextField @@ -59,6 +58,10 @@ fun SignUpScreen( onNavigateToSignIn.navigate(context.getString(R.string.route_sign_in)) } + is SignUpSideEffect.Loading -> { + + } + is SignUpSideEffect.Error -> { Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show() } @@ -83,16 +86,6 @@ fun SignUpScreen( } } - val isSignUpButtonEnabled by remember(id, password, nickname, phoneNumber) { - mutableStateOf( - id.length in MIN_LENGTH_LOGIN..MAX_LENGTH_LOGIN - && password.length in MIN_LENGTH_PASSWORD..MAX_LENGTH_PASSWORD - && nickname.isNotEmpty() && !nickname.contains( - " " - ) && phoneNumber.matches(Regex("^010-\\d{4}-\\d{4}\$")) - ) - } - Text( text = stringResource(id = R.string.sign_up_title), fontWeight = FontWeight.Bold, @@ -120,22 +113,7 @@ fun SignUpScreen( } } SoptOutlinedButton(text = R.string.btn_sign_up, onClick = { - if (isSignUpButtonEnabled) { - signUpViewModel.signUp( - RequestSignUpDto( - authenticationId = id, - password = password, - nickname = nickname, - phone = phoneNumber - ) - ) - } else { - Toast.makeText( - context, - R.string.sign_up_fail_message, - Toast.LENGTH_SHORT - ).show() - } + }, enabled = true) } } diff --git a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpSideEffect.kt b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpSideEffect.kt index 6d2bbd4..47176ad 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpSideEffect.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpSideEffect.kt @@ -2,5 +2,6 @@ package com.sopt.now.compose.ui.signUp sealed class SignUpSideEffect { data class Success(val message: String) : SignUpSideEffect() + object Loading : SignUpSideEffect() data class Error(val message: String) : SignUpSideEffect() -} \ No newline at end of file +} diff --git a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpViewModel.kt b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpViewModel.kt index f8c0020..dad7fbc 100644 --- a/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpViewModel.kt +++ b/app/src/main/java/com/sopt/now/compose/ui/signUp/SignUpViewModel.kt @@ -1,19 +1,11 @@ package com.sopt.now.compose.ui.signUp import androidx.lifecycle.ViewModel -import com.sopt.now.compose.data.model.RequestSignUpDto -import com.sopt.now.compose.data.model.ResponseSignUpDto -import com.sopt.now.compose.data.module.ServicePool import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response class SignUpViewModel : ViewModel() { - private val authService by lazy { ServicePool.authService } - private val _signUpEvent = MutableStateFlow(null) val signUpEvent = _signUpEvent.asStateFlow() @@ -45,30 +37,6 @@ class SignUpViewModel : ViewModel() { _phoneNumber.value = phoneNumber } - - fun signUp(request: RequestSignUpDto) { - authService.signUp(request).enqueue( - object : Callback { - override fun onResponse( - call: Call, - response: Response, - ) { - if (response.isSuccessful) { - val userId = response.headers()["location"] - _signUpEvent.value = SignUpSideEffect.Success("가입된 유저 아이디는 $userId") - } else { - val error = response.code() - _signUpEvent.value = SignUpSideEffect.Error("회원가입 실패 : $error") - } - } - - override fun onFailure(call: Call, t: Throwable) { - _signUpEvent.value = SignUpSideEffect.Error("서버 에러: ${t.message}") - } - }, - ) - } - companion object { const val MIN_LENGTH_LOGIN = 6 const val MAX_LENGTH_LOGIN = 10 diff --git a/build.gradle b/build.gradle index 4c76592..dbcc2e3 100644 --- a/build.gradle +++ b/build.gradle @@ -4,4 +4,5 @@ plugins { id 'com.android.library' version '8.3.1' apply false id 'org.jetbrains.kotlin.android' version '1.9.0' apply false id("org.jetbrains.kotlin.plugin.serialization") version "1.9.20" apply false + id 'com.google.dagger.hilt.android' version '2.51' apply false } \ No newline at end of file