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

[Refactor] 앱잼 전 세미나 코드 리팩토링 #22

Open
wants to merge 8 commits into
base: develop-compose
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 16 additions & 4 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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'
Expand All @@ -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
Expand All @@ -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
}
3 changes: 2 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@
<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".App"
android:allowBackup="true"
android:usesCleartextTraffic="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.NOWSOPTAndroid"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".MainActivity"
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/java/com/sopt/now/compose/App.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.sopt.now.compose

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

@HiltAndroidApp
class App : Application() {
override fun onCreate() {
super.onCreate()
}
}
20 changes: 5 additions & 15 deletions app/src/main/java/com/sopt/now/compose/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,15 @@ package com.sopt.now.compose
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import com.sopt.now.compose.ui.SoptApp
import com.sopt.now.compose.ui.theme.NOWSOPTAndroidTheme
import com.sopt.now.compose.ui.base.SoptAppNavHost
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
NOWSOPTAndroidTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
SoptApp()
}
}
SoptAppNavHost()
}
}
}
}
43 changes: 18 additions & 25 deletions app/src/main/java/com/sopt/now/compose/data/module/ApiFactory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,35 @@ package com.sopt.now.compose.data.module

import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import com.sopt.now.compose.BuildConfig
import com.sopt.now.compose.data.network.AuthService
import com.sopt.now.compose.data.network.FollowerService
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.create

import javax.inject.Singleton

@InstallIn(SingletonComponent::class)
@Module
object ApiFactory {
private const val BASE_URL: String = BuildConfig.AUTH_BASE_URL
private const val FOLLOWER_URL: String = BuildConfig.FOLLOWER_URL
private val jsonConverterFactory = Json.asConverterFactory("application/json".toMediaType())

private val interceptorClient = OkHttpClient().newBuilder()
.addInterceptor(AuthInterceptor()).build()

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

val followerRetrofit: Retrofit by lazy {
Retrofit.Builder()
@Provides
@Singleton
fun followerRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(FOLLOWER_URL)
.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
.addConverterFactory(jsonConverterFactory)
.build()
}

inline fun <reified T> createBaseRetrofit(): T = baseRetrofit.create()
inline fun <reified T> createFollowerRetrofit(): T = followerRetrofit.create()
}

object ServicePool {
val authService = ApiFactory.createBaseRetrofit<AuthService>()
val followerService = ApiFactory.createFollowerRetrofit<FollowerService>()
@Provides
@Singleton
fun provideFollowerService(followerRetrofit: Retrofit): FollowerService {
return followerRetrofit.create(FollowerService::class.java)
}
Comment on lines +22 to +35
Copy link
Member

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,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,
Comment on lines +12 to +13
Copy link
Member

Choose a reason for hiding this comment

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

굿굿 좋아요! 다만 아직 클린아키텍처가 적용되어 있지 않은 것 같아서 domain 레이어를 추가했을 때 코드가 어떻게 수정될 지 한번 생각해보면 좋을 것 같아요!!

) {
suspend fun getUserList(page: Int): Result<Response<ResponseUserDto>> {
return withContext(Dispatchers.IO) {
runCatching {
followerService.getUserList(page).execute()
}
}
}
}
9 changes: 0 additions & 9 deletions app/src/main/java/com/sopt/now/compose/ui/SoptApp.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 3 additions & 2 deletions app/src/main/java/com/sopt/now/compose/ui/home/HomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Comment on lines +16 to 17
Copy link
Member

Choose a reason for hiding this comment

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

flow를 사용한다면 collectAsStateWithLifecycle로 사용해주는 걸 습관화하면 좋을 것 같아요!

Copy link
Member Author

Choose a reason for hiding this comment

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

헉 꼼꼼하다 넵!!!!


LazyColumn(modifier = Modifier.fillMaxSize()) {
Expand Down
71 changes: 39 additions & 32 deletions app/src/main/java/com/sopt/now/compose/ui/home/HomeViewModel.kt
Original file line number Diff line number Diff line change
@@ -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<List<UserDataDto>>(emptyList())
val followerState = _followerState.asStateFlow()

val friendList = mutableListOf<Profile>()
private var _eventNetworkError = MutableLiveData(false)

private var _isNetworkErrorShown = MutableLiveData(false)
Comment on lines +23 to +25
Copy link
Member

Choose a reason for hiding this comment

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

혹시 두 변수들이 어디서 쓰이고 있는 건가용..? 이 뷰모델에서 true 혹은 false로 바꿔주고 그 이후에 사용되는 부분을 못 찾아서요!!

Copy link
Member Author

Choose a reason for hiding this comment

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

fetchFollowerList에서 네트워크 오류 발생시 쓰이는데, 사실 삭제해도 될 것 같아욥!!!


private val friendList = mutableListOf<Profile>()

init {
fetchFollowerList()
}

private fun fetchFollowerList() {
followerService.getUserList(page = 0).enqueue(object : Callback<ResponseUserDto> {
override fun onResponse(
call: Call<ResponseUserDto>,
response: Response<ResponseUserDto>,
) {
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<ResponseUserDto>, t: Throwable) {
Log.e("HomeError", "${t.message}")
}
})
fun onNetworkErrorShown() {
_isNetworkErrorShown.value = true
}

fun mapFollowersToFriendList(followers: List<UserDataDto>) {
for (follower in followers) {
friendList.add(
Profile(
profileImage = follower.avatar,
name = "${follower.firstName} ${follower.lastName}",
description = follower.email
)
private fun mapFollowersToFriendList(followers: List<UserDataDto>) {
friendList.clear()
friendList.addAll(followers.map { follower ->
Profile(
profileImage = follower.avatar,
name = "${follower.firstName} ${follower.lastName}",
description = follower.email
)
}
})
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -23,36 +17,4 @@ class MyPageViewModel : ViewModel() {
)
)
val infoState = _infoState.asStateFlow()

init {
fetchInfo()
}

private fun fetchInfo() {
authService.memberInfo().enqueue(object : Callback<ResponseInfoDto> {
override fun onResponse(
call: Call<ResponseInfoDto>,
response: Response<ResponseInfoDto>,
) {
if (response.isSuccessful) {
val data = response.body()
if (data != null) {
_infoState.update {
data
}
}
}
}

override fun onFailure(call: Call<ResponseInfoDto>, t: Throwable) {
_infoState.update {
ResponseInfoDto(
code = -1,
message = "${t.message}",
data = UserInfoDto(authenticationId = "", nickname = "", phone = "")
)
}
}
})
}
}
Loading