티스토리 뷰
결과화면
Structure
- Common
Resource : response를 emit할 때 쓰이며 본 프로젝트에서는 UseCase에서 기술됨.
❓타프로젝트할 때 동일한 클래스를 usecase가 아닌 viewmodel에서 적용하다가 MutableLiveData 변수와 타입 오류가 계속 났는데 아직 원인을 못 찾음
sealed class Resource<T>(val data: T? = null, val message: String? = null){
class Success<T>(data: T): Resource<T>(data)
class Error<T>(message: String, data: T? = null): Resource<T>(data, message)
class Loading<T>(data: T? = null) : Resource<T>(data)
}
- Data
di -> AppModule
⚡api를 변수로 받고 repository를 impl하는 싱글톤 추가
@Provides
@Singleton
fun provideCoinRepository(api: CoinPaprikaApi): CoinRepository {
return CoinRepositoryImpl(api)
}
dto -> 직접 활용하는 data class의 경우 objectify함수를 하기처럼 작성
fun CoinDto.toCoin(): Coin {
return Coin(
id = id,
isActive = isActive,
name = name,
rank = rank,
symbol = symbol
)
}
fun CoinDetailDto.toCoinDetail(): CoinDetail {
return CoinDetail(
coinId = id,
name = name,
description = description,
symbol = symbol,
rank = rank,
isActive = isActive,
tags = tags.map { it.name },
team = team
)
}
잠깐 비교 - CoinRepositoryImpl(data - repository)와 CoinRepository(domain - repository)
⚡스프링에 비유하자면 전자는 DaoImpl, 후자는 Service로 이해 중...
class CoinRepositoryImpl @Inject constructor(
private val api: CoinPaprikaApi
): CoinRepository {
override suspend fun getCoins(): List<CoinDto> {
return api.getCoins()
}
override suspend fun getCoinById(coinId: String): CoinDetailDto {
return api.getCoinById(coinId)
}
}
interface CoinRepository {
suspend fun getCoins(): List<CoinDto>
suspend fun getCoinById(coinId: String): CoinDetailDto
}
- Domain
Usecase: repository와 viewModel간의 데이터 송수신을 담당하며 flow의 emit을 통하여 데이터 방출. -> Flow에 대해선 별도 학습&포스팅 예정
❓내가 알고 있는 usecase...
❗이 프로젝트서 의미하는 usecase
// only single function for each usecase
class GetCoinUseCase @Inject constructor(
private val repository: CoinRepository
) {
operator fun invoke(coinId: String): Flow<Resource<CoinDetail>> = flow {
try{
emit(Resource.Loading<CoinDetail>())
val coin = repository.getCoinById(coinId).toCoinDetail()
emit(Resource.Success<CoinDetail>(coin))
} catch(e: HttpException){
emit(Resource.Error<CoinDetail>(e.localizedMessage ?: "An unexpected error occurred"))
} catch(e: IOException) {
emit(Resource.Error<CoinDetail>("Couldn't reach server. Check your connection."))
}
}
}
class GetCoinsUseCase @Inject constructor(
private val repository: CoinRepository
) {
operator fun invoke(): Flow<Resource<List<Coin>>> = flow {
try{
emit(Resource.Loading<List<Coin>>())
val coins = repository.getCoins().map { it.toCoin() }
emit(Resource.Success<List<Coin>>(coins))
} catch(e: HttpException){
emit(Resource.Error<List<Coin>>(e.localizedMessage ?: "An unexpected error occurred"))
} catch(e: IOException) {
emit(Resource.Error<List<Coin>>("Couldn't reach server. Check your connection."))
}
}
}
- Presentation
DetailState
data class CoinDetailState(
val isLoading: Boolean = false,
val coin: CoinDetail? = null,
val error: String = ""
)
data class CoinListState(
val isLoading: Boolean = false,
val coins: List<Coin> = emptyList(),
val error: String = ""
)
ViewModel: 상기 domain의 usecase를 생성자로 받아 초기화 + State상태를 private 변수로 받은 후 각 상황에 따른 data 출력
@HiltViewModel
class CoinListViewModel @Inject constructor(
private val getCoinsUseCase: GetCoinsUseCase
): ViewModel(){
private val _state = mutableStateOf(CoinListState())
val state: State<CoinListState> = _state
init{
getCoins()
}
// override invoke function as function
private fun getCoins() {
getCoinsUseCase().onEach { result ->
when(result) {
is Resource.Success -> {
_state.value = CoinListState(
coins = result.data ?: emptyList()
)
}
is Resource.Error -> {
_state.value = CoinListState(
error = result.message ?: "An unexpected error occurred."
)
}
is Resource.Loading -> {
_state.value = CoinListState(isLoading = true)
}
}
}.launchIn(viewModelScope)
}
}
@HiltViewModel
class CoinDetailViewModel @Inject constructor(
private val getCoinUseCase: GetCoinUseCase,
savedStateHandle: SavedStateHandle
): ViewModel(){
private val _state = mutableStateOf(CoinDetailState())
val state: State<CoinDetailState> = _state
init{
savedStateHandle.get<String>(Constants.PARAM_COIN_ID)?.let { coinId ->
getCoin(coinId)
}
}
// override invoke function as function
private fun getCoin(coinId: String) {
getCoinUseCase(coinId).onEach { result ->
when(result) {
is Resource.Success -> {
_state.value = CoinDetailState(
coin = result.data
)
}
is Resource.Error -> {
_state.value = CoinDetailState(
error = result.message ?: "An unexpected error occurred."
)
}
is Resource.Loading -> {
_state.value = CoinDetailState(isLoading = true)
}
}
}.launchIn(viewModelScope)
}
}
⚡실제 screen에서 적용한 예
- UI
Screen
sealed class Screen(val route: String) {
object CoinListScreen: Screen("coin_list_screen")
object CoinDetailScreen: Screen("coin_detail_screen")
}
MainActivity: Nav관련 import 주의할 것. 자꾸 다른 이상한 것들이 import됨
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
CryptocurrencyAppYTTheme {
Surface(color = MaterialTheme.colors.background) {
val navController = rememberNavController()
NavHost(
navController = navController ,
startDestination = Screen.CoinListScreen.route
) {
composable(
route = Screen.CoinListScreen.route
){
CoinListScreen(navController)
}
composable(
route = Screen.CoinDetailScreen.route + "/{coinId}"
){
CoinDetailScreen()
}
}
}
}
}
}
}
Ref: https://www.youtube.com/watch?v=EF33KmyprEQ&t=2s
'App > Android Native' 카테고리의 다른 글
Unsplash API w MVVM/Retrofit2/Hilt/Navigation (0) | 2022.01.14 |
---|---|
KOTLIN - Flows (0) | 2022.01.10 |
Clone project - Book App - Step 9 (comments) (0) | 2021.12.29 |
Clone project - Book App - Step 3 (update/delete book info) (0) | 2021.11.01 |
Clone project - Book App - Step 2 (uploading/loading pdf w firebase cloud) (0) | 2021.11.01 |
- Total
- Today
- Yesterday
- android_app_link_domain_works_with_adb_but_not_works_with_browser
- Android
- flutter
- querydslKotlinError
- unsplashAPI
- phplaravel
- flutter_android_app_links
- Laravel
- flutter_storage_not_working_for_the_first_time
- flutter_secure_storage_issue_iOS
- futter_api
- KotlinFlow
- laravel9
- AndroidWirelessDebug
- FirebaseConfigurationForMultipleBuildTypes
- dagger-hilt
- retrofit_generator
- Kotlin
- querydsl5.0.0jakarta
- retrofit_generator_conflicts_with_freezed
- android_domain
- mvvm
- RunAFlutterProjectIniPhoneFromVSCode
- android_app_links_domain
- FlutterWirelessDebuginAOS
- WirelessDebug
- querydslQclass
- android_app_links
- retrofit_toJson()_error
- MultipleFirebaseEnvironments
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |