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

[FEAT/#14] 7차 필수과제 #15

Open
wants to merge 7 commits into
base: develop
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
26 changes: 26 additions & 0 deletions app/src/main/java/org/sopt/and/core/preference/PreferenceImpl.kt
Copy link
Contributor

Choose a reason for hiding this comment

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

core에 위치시키신 이유가 궁금합니다 !

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

Choose a reason for hiding this comment

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

이 친구에 대해 자세히 설명해주실 수 있을까요 ?? sharedpreference를 관리하는 친구인거같은데 동작법이 궁금합니다 !

19 changes: 19 additions & 0 deletions app/src/main/java/org/sopt/and/core/preference/PreferenceModule.kt
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)
}
15 changes: 0 additions & 15 deletions app/src/main/java/org/sopt/and/core/state/UiState.kt

This file was deleted.

50 changes: 50 additions & 0 deletions app/src/main/java/org/sopt/and/core/util/BaseViewModel.kt
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) }
}
}
3 changes: 3 additions & 0 deletions app/src/main/java/org/sopt/and/core/util/UiEvent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.sopt.and.core.util

interface UiEvent
3 changes: 3 additions & 0 deletions app/src/main/java/org/sopt/and/core/util/UiSideEffect.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.sopt.and.core.util

interface UiSideEffect
3 changes: 3 additions & 0 deletions app/src/main/java/org/sopt/and/core/util/UiState.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.sopt.and.core.util

interface UiState
28 changes: 7 additions & 21 deletions app/src/main/java/org/sopt/and/feature/main/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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

Choose a reason for hiding this comment

The 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")
}
}
1 change: 0 additions & 1 deletion app/src/main/java/org/sopt/and/feature/main/MainScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ fun MainScreen(
)
myNavGraph(
paddingValues = paddingValues,
navHostController = navigator.navController,
)

signInNavGraph(
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/java/org/sopt/and/feature/my/MyContract.kt
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
}
29 changes: 8 additions & 21 deletions app/src/main/java/org/sopt/and/feature/my/MyRoute.kt
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
Expand All @@ -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)

Choose a reason for hiding this comment

The 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
Expand Down
7 changes: 0 additions & 7 deletions app/src/main/java/org/sopt/and/feature/my/MyState.kt

This file was deleted.

19 changes: 7 additions & 12 deletions app/src/main/java/org/sopt/and/feature/my/MyViewModel.kt
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) {

Choose a reason for hiding this comment

The 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) }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import androidx.compose.animation.ExitTransition
import androidx.compose.foundation.layout.PaddingValues
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.NavOptions
import androidx.navigation.compose.composable
import kotlinx.serialization.Serializable
Expand All @@ -21,7 +20,6 @@ fun NavController.navigateToMy(navOptions: NavOptions? = null) {

fun NavGraphBuilder.myNavGraph(
paddingValues: PaddingValues,
navHostController: NavHostController,
) {
composable<My>(
exitTransition = {
Expand All @@ -39,7 +37,6 @@ fun NavGraphBuilder.myNavGraph(
) {
MyRoute(
paddingValues = paddingValues,
navController = navHostController
)
}
}
Expand Down
27 changes: 27 additions & 0 deletions app/src/main/java/org/sopt/and/feature/signin/SignInContract.kt
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

Choose a reason for hiding this comment

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

대현님께도 여쭤봣지만 네이밍에 On이 붙는게 일반적인걸까요 ??

}

}
Loading