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

Week4 필수과제 #8

Open
wants to merge 26 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a198153
[refactor] #5 4개로 분리된 Greeting text 1개로 통합
kamja0510 Nov 7, 2024
a99a92c
[refactor] #5 wavveBottomNavigationItem List를 NavigationUiState로 빼냄.
kamja0510 Nov 8, 2024
213c47f
[refactor] #5 HomeScreen의 스크롤 가능한 Column을 LazyColumn으로 변경.
kamja0510 Nov 8, 2024
7f30c00
[refactor] #5 HomeTopBar의 장르 표시를 그냥 Row에서 LazyRow로 변경
kamja0510 Nov 9, 2024
b2c94b6
[refactor] #5 collectAsState()를 collectAsStateWithLifecycle()로 변경
kamja0510 Nov 9, 2024
a667532
[feat] #5 로그인, 회원가입 화면 이동 시 navigation 스택 관리 구현
kamja0510 Nov 11, 2024
2c4b75c
[refactor] #5 SignInBtn의 Context, CoroutineScope 의존성 제거
kamja0510 Nov 11, 2024
7134570
[feat] #5 signup 과정의 비동기 처리
kamja0510 Nov 12, 2024
8d825df
[feat] #5 signin 과정의 비동기 처리
kamja0510 Nov 12, 2024
3da55e3
[fix] #5 bottom navigation 으로 화면 이동시 MyInfo 화면의 email이 사라지는 현상 수정
kamja0510 Nov 12, 2024
b872844
[feat] #7 회원가입 시 hobby도 받을 수 있도록 UI 및 기능 구현
kamja0510 Nov 13, 2024
389fae7
[chore] #7 BASEURL 숨기기
kamja0510 Nov 13, 2024
ca84c9f
[add] #7 통신을 위한 Manifest파일 코드 추가
kamja0510 Nov 13, 2024
c160499
[add] #7 통신을 위한 dependency 코드 추가
kamja0510 Nov 13, 2024
12771f5
[feat] #7 통신을 통한 유저 등록 구현
kamja0510 Nov 13, 2024
fa459e9
[chore] #7 주석 제거
kamja0510 Nov 13, 2024
e0afbc9
[feat] #7 로그인 요청 DTO 생성
kamja0510 Nov 13, 2024
6b1f0ab
[feat] #7 로그인 응답 DTO 생성
kamja0510 Nov 13, 2024
33303b8
[feat] #7 취미 조회 DTO 생성
kamja0510 Nov 13, 2024
f626982
[feat] #7 서버통신을 이용한 로그인 구현
kamja0510 Nov 13, 2024
771208b
[add] #7 preferences datastore를 사용하기 위한 dependency 추가
kamja0510 Nov 13, 2024
c21eb75
[feat] #7 preferences datastore를 사용해 token을 관리할 TokenManager구현
kamja0510 Nov 13, 2024
b86bab9
[feat] #7 header에 token을 붙일 수 있게 AuthInterceptor 구현
kamja0510 Nov 13, 2024
8262c6f
[refactor] #7 navigation 구조 변경
kamja0510 Nov 13, 2024
7f0d641
[feat] #7 서버통신 시 header에 token 붙이기 및 MyInfoScreen에 조회한 내 취미 띄우기
kamja0510 Nov 13, 2024
2d46b36
[refactor] #7 코드 다듬기
kamja0510 Nov 13, 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
Empty file.
18 changes: 17 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import java.util.Properties

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.kotlin.serialization)
}

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

android {
namespace = "org.sopt.and"
compileSdk = 34
Expand All @@ -17,6 +23,7 @@ android {
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
buildConfigField("String", "BASE_URL", properties["base.url"].toString())
}

buildTypes {
Expand All @@ -37,11 +44,11 @@ android {
}
buildFeatures {
compose = true
buildConfig = true
}
}

dependencies {

implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
Expand All @@ -53,6 +60,15 @@ dependencies {
implementation(libs.lifecycle.viewmodel.compose)
implementation(libs.kotlinx.serialization.json)
implementation(libs.androidx.compose.navigation)
implementation(libs.androidx.runtime.livedata)

Choose a reason for hiding this comment

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

요기 아래에 한 줄만 띄워주세요 ㅎ
주석이랑 코드랑 딱 붙어있어서..

// network
implementation(platform(libs.okhttp.bom))
implementation(libs.okhttp)
implementation(libs.okhttp.logging.interceptor)
implementation(libs.retrofit)

Choose a reason for hiding this comment

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

버전 카탈로그에서 bundles로 묶는 것에 대해 찾아보시면 좋을 것 같아요! 디펜던시 추가할 때 훨씬 코드가 간결해진답니다

implementation(libs.retrofit.kotlin.serialization.converter)

implementation(libs.androidx.datastore.preferences)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
Expand Down
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:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
Expand All @@ -11,6 +13,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ANDANDROID"
android:usesCleartextTraffic="true"
tools:targetApi="31">

<activity
Expand Down
57 changes: 30 additions & 27 deletions app/src/main/java/org/sopt/and/WavveUtils.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package org.sopt.and

import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Search
import android.content.Context
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.compose.material3.SnackbarHostState
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import org.sopt.and.navigation.Routes
import org.sopt.and.navigation.WavveBottomNavigationItem
import androidx.core.content.ContextCompat.getString
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

object WavveUtils {
const val MIN_PASSWORD_LENGTH = 8
Expand All @@ -17,27 +18,6 @@ object WavveUtils {
const val SEARCH_SCREEN_INDEX = 1
const val HOME_SCREEN_INDEX = 0

val wavveBottomNavigationItems = listOf<WavveBottomNavigationItem>(
WavveBottomNavigationItem(
label = R.string.bottom_navigation_home_label,
icon = Icons.Default.Home,
route = Routes.Home,
index = 0
),
WavveBottomNavigationItem(
label = R.string.bottom_navigation_search_label,
icon = Icons.Default.Search,
route = Routes.Search,
index = 1
),
WavveBottomNavigationItem(
label = R.string.bottom_navigation_my_info_label,
icon = Icons.Default.AccountCircle,
route = Routes.MyInfo(""),
index = 2
)
)

val linkableSNS = listOf<Pair<Int, Int>>(
Pair(R.drawable.kakao_talk_icon, R.string.link_kakao_icon_description),
Pair(R.drawable.t_world_icon, R.string.link_tworld_icon_description),
Expand All @@ -48,4 +28,27 @@ object WavveUtils {

fun transformationPasswordVisual(isVisible: Boolean): VisualTransformation =
if (isVisible) VisualTransformation.None else PasswordVisualTransformation()

fun showToast(

Choose a reason for hiding this comment

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

미쵸따 토스트 스낵바 둘다 확장함수화
짱이네요

Copy link
Contributor

Choose a reason for hiding this comment

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

fun Context.showToast()의 형태로 만들어도 좋을 것 같ㄴㅔ요!
그리고 둘이 뭐가 다른지 비교해봅시다

context: Context,
@StringRes message: Int
) = Toast.makeText(
context,
context.getString(message),
Toast.LENGTH_SHORT
).show()

fun showSnackbar(

Choose a reason for hiding this comment

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

토스트만 확장함수로 만들어 뒀는데 스낵바도 추가해 봐야겠습니다.

scope: CoroutineScope,
context: Context,
snackbarHostState: SnackbarHostState,
@StringRes message: Int
) = scope.launch {
snackbarHostState.showSnackbar(
message = getString(
context,
message
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ import org.sopt.and.ui.theme.Grey200

@Composable
fun SignInOrSignUpTextField(
emailOrPassword: String,
information: String,
onValueChange: (String) -> Unit,
placeholder: Int,
visualTransformation: VisualTransformation = VisualTransformation.None,
trailingIcon: @Composable (() -> Unit)? = null
) {
TextField(
value = emailOrPassword,
value = information,
onValueChange = onValueChange,
modifier = Modifier.fillMaxWidth(),
colors = TextFieldDefaults.colors(
Expand Down
43 changes: 25 additions & 18 deletions app/src/main/java/org/sopt/and/home/HomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import org.sopt.and.R
import org.sopt.and.home.components.HomeBannerPager
Expand All @@ -31,10 +31,8 @@ import org.sopt.and.ui.theme.Grey100
fun HomeScreen(
innerPadding: PaddingValues
) {
val scrollState = rememberScrollState()

val homeViewModel = viewModel<HomeViewModel>()
val homeUiState by homeViewModel.uiState.collectAsState()
val homeUiState by homeViewModel.uiState.collectAsStateWithLifecycle()

Column(
modifier = Modifier
Expand All @@ -44,23 +42,32 @@ fun HomeScreen(
) {
HomeTopBar(genres = homeUiState.genres)

Column(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(scrollState)
LazyColumn(
modifier = Modifier.fillMaxWidth(),
state = rememberLazyListState()

Choose a reason for hiding this comment

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

HomeScreen에서 rememberLazyListState 객체를 사용할 게 아니라면 굳이 안적어주셔도 되어요. 기본 생성자로 알아서 생성합니다.

Copy link
Contributor Author

@kamja0510 kamja0510 Nov 18, 2024

Choose a reason for hiding this comment

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

사용하는 함수를 뜯어보는 습관을 들이겠습니다! 들어가보니 기본값으로 제공하네요

) {
HomeBannerPager(homeUiState.banners)
item {
HomeBannerPager(homeUiState.banners)
}

Spacer(modifier = Modifier.height(20.dp))
item {
Spacer(modifier = Modifier.height(20.dp))
}

RecommendList(
title = stringResource(R.string.home_picks_of_editor_title),
items = homeUiState.recommends
)
item {
RecommendList(
title = stringResource(R.string.home_picks_of_editor_title),
items = homeUiState.recommends
)
}

Spacer(modifier = Modifier.height(20.dp))
item {

Choose a reason for hiding this comment

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

scrollState를 설정해준 Column 안에 LazyRow, LazyColumn들을 선언해주는 방식에서 LazyColumn 안에 items로 LazyRow, LazyColumn들을 선언해주는 것으로 변경한 이유가 있나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

제대로 만들면 화면에 출력하는 아이템들이 많아져 스크롤이 길어질꺼 같은데 이때 LazyColumn의 보여지는 composable만 화면에 띄우는게 성능적으로 좋을 것 같았습니다.

Spacer(modifier = Modifier.height(20.dp))
}

Top20List(homeUiState.rankers)
item {
Top20List(homeUiState.rankers)
}
}

HomeBottomCoupon()
Expand Down
10 changes: 7 additions & 3 deletions app/src/main/java/org/sopt/and/home/components/HomeTopBar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
Expand Down Expand Up @@ -63,15 +64,18 @@ fun HomeTopBar(
)
}

Row(
LazyRow(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 10.dp),
horizontalArrangement = Arrangement.spacedBy(14.dp)
) {
genres.forEach { genre ->
items(
count = genres.size,
key = { genres[it] }
) { index ->
Comment on lines +73 to +76

Choose a reason for hiding this comment

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

items 확장함수 중에 List 타입을 인자로 넣으면 T 타입을 그대로 뱉어주는 함수가 있어요. 아래처럼 해도 같은 동작할 수 있습니다. (import 주의)

Suggested change
items(
count = genres.size,
key = { genres[it] }
) { index ->
items(genres) { genre ->

Copy link
Contributor Author

Choose a reason for hiding this comment

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

세미나에서 이렇게 배웠었는데 안되었던 이유가 import를 잘못해서였군요 ㅠ

Text(
text = stringResource(genre),
text = stringResource(genres[index]),
color = Grey200,
fontSize = 14.sp
)
Expand Down
9 changes: 6 additions & 3 deletions app/src/main/java/org/sopt/and/myinfo/MyInfoScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import org.sopt.and.ui.theme.Black100
@Composable
fun MyInfoScreen(
paddingValues: PaddingValues,
myEmail: String,
myInfoViewModel: MyInfoViewModel,
myInfoUiState: MyInfoUiState,
modifier: Modifier = Modifier
) {
Column(
Expand All @@ -33,7 +34,8 @@ fun MyInfoScreen(
.padding(paddingValues)
) {
MyInfoProfile(
myEmail = myEmail,
myInfoViewModel = myInfoViewModel,

Choose a reason for hiding this comment

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

뷰모델을 직접적으로 넘겨주는 것보다는 인자들을 분리해서 람다 등을 이용해 필요한 것들만 넘겨주시면 좋을 것 같습니다~!
이유가 뭘까용?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

첫번째로는 서로 하는 역할을 분리해놨는데 인자로 주면 종속된다(?)라는 점 일 것 같고,
두번째는 컴포즈에서 상태 호이스팅을 권장하고
세번째는 밑에서 지적받은 부분처럼 Preview시에 문제가 있을 수 있다는 점 인 것 같습니다.
정답을 알려줘~

myInfoUiState = myInfoUiState,
modifier = Modifier.weight(0.16f)
)

Expand Down Expand Up @@ -73,7 +75,8 @@ fun MyScreenPreview() {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
MyInfoScreen(
paddingValues = innerPadding,
myEmail = ""
myInfoViewModel = TODO(),
myInfoUiState = TODO()
Comment on lines +78 to +79

Choose a reason for hiding this comment

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

Preview 안보일 것 같네요. Preview 대상 Composable 에는 ViewModel 인자를 아예 제거하심이 좋습니다.

)
}
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/org/sopt/and/myinfo/MyInfoUiState.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package org.sopt.and.myinfo

data class MyInfoUiState(
val myEmail: String = ""
val myHobby: String = ""

Choose a reason for hiding this comment

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

모든 곳에서 공통으로 사용할 수 있는 UiState를 만들어서 활용하면 좋을 것 같아요! UiState에는 통신이 성공했다는 의미의 Success, Loading, Fail 정도만 있으면 될 것 같구용

그리고 MyInfo는 Entity로 정의해서 활용하고 UiState 이런 식으로 사용하면 좋지 않을까 싶어욤

설명하기 어렵네요ㅜ 이거 이해 잘 안되시면 연락 주세욤

Copy link
Contributor Author

Choose a reason for hiding this comment

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

우리팀 코드 보면서 많이 본 코드긴한데 연락 드릴게요 ㅎ

)
38 changes: 33 additions & 5 deletions app/src/main/java/org/sopt/and/myinfo/MyInfoViewModel.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,44 @@
package org.sopt.and.myinfo

import androidx.lifecycle.ViewModel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import org.sopt.and.myinfo.dto.GetHobbyResponseDto
import org.sopt.and.services.ServicePool
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

class MyInfoViewModel(application: Application) : AndroidViewModel(application) {

Choose a reason for hiding this comment

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

AndroidViewModel(application)에 대해 설명해주실 수 있나용 ㅋ.ㅋ

Choose a reason for hiding this comment

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

뷰모델에 Application을 넣으면 어떻게 되나요??

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ViewModel안에서는 context를 사용하지 못하잖아요?
AndroidViewModel은 ViewModel 서브클래스인데 context를 사용하고 싶을때 쓴다고해요.
그래서 저기 application이 어플리케이션의 context를 가지는 것 입니다.
사실 좋은 코드인지는 모르겠네요 꾸역꾸역 구현했습니다

Copy link
Contributor

Choose a reason for hiding this comment

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

Context는 뭘까요?
그리고 ViewModel이 Context를 가진다는 것,,은! 어떤 의미가 될까요? (MVVM 관점에서 생각해보세요)

private val userService by lazy { ServicePool.userService(application) }

class MyInfoViewModel(
) : ViewModel() {
private val _uiState = MutableStateFlow(MyInfoUiState())
val uiState: StateFlow<MyInfoUiState> = _uiState.asStateFlow()

fun setMyEmail(email: String) {
_uiState.value = _uiState.value.copy(myEmail = email)
fun setMyHobby(myHobby: String) {
_uiState.value = _uiState.value.copy(myHobby = myHobby)
}

fun getMyHobby() {
userService.getMyHobby().enqueue(
object : Callback<GetHobbyResponseDto> {
override fun onResponse(
call: Call<GetHobbyResponseDto>,
response: Response<GetHobbyResponseDto>
) {
if (response.isSuccessful) {
response.body()?.result?.hobby?.let { setMyHobby(it) }
} else {
setMyHobby("오류")

Choose a reason for hiding this comment

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

하드코딩.

}
}

override fun onFailure(call: Call<GetHobbyResponseDto>, t: Throwable) {
// 어떤 처리를 할까요?

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
Expand Up @@ -19,14 +19,19 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import org.sopt.and.R
import org.sopt.and.myinfo.MyInfoUiState
import org.sopt.and.myinfo.MyInfoViewModel
import org.sopt.and.ui.theme.Grey100
import org.sopt.and.ui.theme.White100

@Composable
fun MyInfoProfile(
myEmail: String,
myInfoViewModel: MyInfoViewModel,
myInfoUiState: MyInfoUiState,
modifier: Modifier = Modifier
) {
myInfoViewModel.getMyHobby()

Row(
modifier = modifier
.fillMaxWidth()
Expand All @@ -43,8 +48,8 @@ fun MyInfoProfile(

Spacer(modifier = Modifier.width(5.dp))

Text(
text = myEmail,
Text( // 만약에 네트워크 통신이 늦었을 때 얘가 recomposition이 되나요??
text = if (myInfoUiState.myHobby.isNotEmpty()) myInfoUiState.myHobby else "Loading...",

Choose a reason for hiding this comment

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

flow로 만들어두셨기 때문에 compose가 관찰할 수 있는 데이터가 되었잖아요?! 따라서 새로운 데이터로 변경될 시 해당 Text 컴포넌트는 리컴포지션됩니다! 상태가 변경되면 컴포저블은 해당 컴포넌트만 유아이 업데이트를 하니까요
flow나 remember를 이용하지 않았다면 변경되지 않아요

Copy link
Contributor Author

Choose a reason for hiding this comment

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

캬 확실히 알았습닌다

Copy link
Contributor

Choose a reason for hiding this comment

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

우리 지은이 언제 이렇게 컸지,,

color = White100
)

Expand Down
Loading