-
Notifications
You must be signed in to change notification settings - Fork 0
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/#14] 7차 필수과제 #15
base: develop
Are you sure you want to change the base?
Changes from all commits
9aa519a
ddd277a
d8a6034
a8f8a85
2d4651c
73e5734
c8a4913
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package org.sopt.and.core.preference | ||
|
||
import android.content.Context | ||
import androidx.compose.runtime.staticCompositionLocalOf | ||
import dagger.hilt.android.qualifiers.ApplicationContext | ||
|
||
class PreferenceImpl( | ||
@ApplicationContext private val context: Context | ||
) { | ||
private val preference = context.getSharedPreferences( | ||
PREF_NAME, Context.MODE_PRIVATE | ||
) | ||
|
||
var token: String | ||
get() = preference.getString(TOKEN, "").toString() | ||
set(value) = preference.edit().putString(TOKEN, value).apply() | ||
|
||
companion object{ | ||
private const val PREF_NAME = "wavve_prefs" | ||
private const val TOKEN = "token" | ||
|
||
val LocalPreference = staticCompositionLocalOf<PreferenceImpl> { | ||
error("Preference Failed") | ||
} | ||
} | ||
} | ||
Comment on lines
+7
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 친구에 대해 자세히 설명해주실 수 있을까요 ?? sharedpreference를 관리하는 친구인거같은데 동작법이 궁금합니다 ! |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package org.sopt.and.core.preference | ||
|
||
import android.content.Context | ||
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 PreferenceModule { | ||
@Provides | ||
@Singleton | ||
fun providePreferenceImpl( | ||
@ApplicationContext context: Context | ||
) = PreferenceImpl(context) | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package org.sopt.and.core.util | ||
|
||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.viewModelScope | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.MutableSharedFlow | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.SharedFlow | ||
import kotlinx.coroutines.flow.StateFlow | ||
import kotlinx.coroutines.flow.asSharedFlow | ||
import kotlinx.coroutines.flow.asStateFlow | ||
import kotlinx.coroutines.launch | ||
|
||
abstract class BaseViewModel<State : UiState, SideEffect : UiSideEffect, Event : UiEvent>() : | ||
ViewModel() { | ||
private val initialState: State by lazy { createInitialState() } | ||
abstract fun createInitialState(): State | ||
|
||
private val _uiState = MutableStateFlow<State>(initialState) | ||
val uiState: StateFlow<State> | ||
get() = _uiState.asStateFlow() | ||
val currentState: State | ||
get() = uiState.value | ||
|
||
private val _event: MutableSharedFlow<Event> = MutableSharedFlow() | ||
val event: SharedFlow<Event> | ||
get() = _event.asSharedFlow() | ||
|
||
private val _sideEffect: MutableSharedFlow<SideEffect> = MutableSharedFlow() | ||
val sideEffect: Flow<SideEffect> | ||
get() = _sideEffect.asSharedFlow() | ||
|
||
fun setState(reduce: State.() -> State) { | ||
_uiState.value = currentState.reduce() | ||
} | ||
|
||
open fun setEvent(event: Event) { | ||
dispatchEvent(event) | ||
} | ||
|
||
private fun dispatchEvent(event: Event) = viewModelScope.launch { | ||
handleEvent(event) | ||
} | ||
|
||
protected abstract suspend fun handleEvent(event: Event) | ||
|
||
fun setSideEffect(sideEffect: SideEffect) { | ||
viewModelScope.launch { _sideEffect.emit(sideEffect) } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package org.sopt.and.core.util | ||
|
||
interface UiEvent |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package org.sopt.and.core.util | ||
|
||
interface UiSideEffect |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package org.sopt.and.core.util | ||
|
||
interface UiState |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,12 +4,10 @@ import android.os.Bundle | |
import androidx.activity.ComponentActivity | ||
import androidx.activity.compose.setContent | ||
import androidx.activity.enableEdgeToEdge | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.tooling.preview.Preview | ||
import androidx.compose.runtime.CompositionLocalProvider | ||
import dagger.hilt.android.AndroidEntryPoint | ||
import org.sopt.and.core.designsystem.theme.ANDANDROIDTheme | ||
import org.sopt.and.core.preference.PreferenceImpl | ||
|
||
@AndroidEntryPoint | ||
class MainActivity : ComponentActivity() { | ||
|
@@ -19,24 +17,12 @@ class MainActivity : ComponentActivity() { | |
setContent { | ||
val navigator: MainNavigator = rememberMainNavigator() | ||
ANDANDROIDTheme { | ||
MainScreen(navigator) | ||
CompositionLocalProvider( | ||
PreferenceImpl.LocalPreference provides PreferenceImpl(this) | ||
Comment on lines
+20
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 친구를 통해 preference를 전체적으로 관리하는걸까용 |
||
) { | ||
MainScreen(navigator) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
@Composable | ||
fun Greeting(name: String, modifier: Modifier = Modifier) { | ||
Text( | ||
text = "Hello $name!", | ||
modifier = modifier | ||
) | ||
} | ||
|
||
@Preview(showBackground = true) | ||
@Composable | ||
fun GreetingPreview() { | ||
ANDANDROIDTheme { | ||
Greeting("Android") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package org.sopt.and.feature.my | ||
|
||
import org.sopt.and.core.util.UiState | ||
|
||
class MyContract { | ||
data class MyUiState( | ||
val hobby: String = "" | ||
) : UiState | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,5 @@ | ||
package org.sopt.and.feature.my | ||
|
||
import android.content.Context | ||
import androidx.compose.foundation.Image | ||
import androidx.compose.foundation.background | ||
import androidx.compose.foundation.layout.Column | ||
|
@@ -20,47 +19,35 @@ import androidx.compose.ui.Alignment | |
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.draw.clip | ||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.platform.LocalContext | ||
import androidx.compose.ui.res.painterResource | ||
import androidx.compose.ui.res.stringResource | ||
import androidx.compose.ui.tooling.preview.Preview | ||
import androidx.compose.ui.unit.dp | ||
import androidx.compose.ui.unit.sp | ||
import androidx.hilt.navigation.compose.hiltViewModel | ||
import androidx.lifecycle.compose.LocalLifecycleOwner | ||
import androidx.lifecycle.compose.collectAsStateWithLifecycle | ||
import androidx.navigation.NavHostController | ||
import org.sopt.and.R | ||
import org.sopt.and.core.designsystem.theme.ANDANDROIDTheme | ||
import org.sopt.and.core.state.UiState | ||
import org.sopt.and.core.preference.PreferenceImpl.Companion.LocalPreference | ||
import org.sopt.and.feature.my.component.MyPageContent | ||
import org.sopt.and.feature.my.component.MyPageTextButton | ||
|
||
@Composable | ||
fun MyRoute( | ||
paddingValues: PaddingValues, | ||
navController: NavHostController, | ||
viewModel: MyViewModel = hiltViewModel() | ||
) { | ||
val context = LocalContext.current | ||
val lifecycleOwner = LocalLifecycleOwner.current | ||
val sharedPreferences = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE) | ||
|
||
val homeState by viewModel.myState.collectAsStateWithLifecycle() | ||
val uiState by viewModel.uiState.collectAsStateWithLifecycle() | ||
val preference = LocalPreference.current | ||
|
||
LaunchedEffect(true) { | ||
viewModel.getHobby(sharedPreferences) | ||
viewModel.getHobby(preference.token) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저도 이번에 mvi패턴을 처음 다뤄봐서 확신은 없지만,, 이부분을 sideEffect로 처리해야하지 않나? 하는 생각이 들었습니다! |
||
} | ||
|
||
when (homeState.hobby) { | ||
is UiState.Success -> { | ||
MyScreen( | ||
hobby = (homeState.hobby as? UiState.Success<String>)?.data ?: "" , | ||
) | ||
} | ||
|
||
else -> {} | ||
} | ||
MyScreen( | ||
hobby = uiState.hobby, | ||
modifier = Modifier.padding(bottom = paddingValues.calculateBottomPadding()) | ||
) | ||
} | ||
|
||
@Composable | ||
|
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,26 @@ | ||
package org.sopt.and.feature.my | ||
|
||
import android.content.SharedPreferences | ||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.viewModelScope | ||
import dagger.hilt.android.lifecycle.HiltViewModel | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.launch | ||
import org.sopt.and.core.state.UiState | ||
import org.sopt.and.core.util.BaseViewModel | ||
import org.sopt.and.domain.usecase.GetMyHobbyUseCase | ||
import javax.inject.Inject | ||
|
||
@HiltViewModel | ||
class MyViewModel @Inject constructor( | ||
private val getMyHobbyUseCase: GetMyHobbyUseCase, | ||
) : ViewModel() { | ||
var myState: MutableStateFlow<MyState> = MutableStateFlow(MyState()) | ||
private set | ||
) : BaseViewModel<MyContract.MyUiState, Nothing, Nothing>() { | ||
override fun createInitialState(): MyContract.MyUiState = | ||
MyContract.MyUiState() | ||
|
||
fun getHobby(sharedPreferences: SharedPreferences) { | ||
val token = sharedPreferences.getString("token", null) ?: "" | ||
override suspend fun handleEvent(event: Nothing) {} | ||
|
||
fun getHobby(token: String) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요 부분도 위 리뷰랑 연결되어서 sideEffect로 처리하고 private가시성을 사용할 수 있지 않나? 하는 생각이 들긴 하는데,, 단순 생각입니다!! mvi 어려워서 아직 저도 확신은 안생깁니다 ㅎㅎ,, |
||
viewModelScope.launch { | ||
getMyHobbyUseCase(token) | ||
.onSuccess { response -> | ||
myState.value = myState.value.copy( | ||
hobby = UiState.Success(response.hobby) | ||
) | ||
setState { copy(hobby = response.hobby) } | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package org.sopt.and.feature.signin | ||
|
||
import androidx.annotation.StringRes | ||
import org.sopt.and.core.util.UiEvent | ||
import org.sopt.and.core.util.UiSideEffect | ||
import org.sopt.and.core.util.UiState | ||
|
||
class SignInContract { | ||
data class SignInUiState( | ||
val username: String = "", | ||
val password: String = "", | ||
): UiState | ||
|
||
sealed interface SignInSideEffect : UiSideEffect { | ||
data class ShowToast(@StringRes val message: Int) : SignInSideEffect | ||
data object NavigateToSignUp : SignInSideEffect | ||
data class NavigateToHome(val token: String) : SignInSideEffect | ||
} | ||
|
||
sealed class SignInEvent : UiEvent { | ||
data class OnUsernameChanged(val username: String) : SignInEvent() | ||
data class OnPasswordChanged(val password: String) : SignInEvent() | ||
data object OnSignInButtonClicked : SignInEvent() | ||
data object OnSignUpButtonClicked : SignInEvent() | ||
Comment on lines
+21
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 대현님께도 여쭤봣지만 네이밍에 On이 붙는게 일반적인걸까요 ?? |
||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
core에 위치시키신 이유가 궁금합니다 !