
![]() |
![]() |
![]() |
---|---|---|
로그인 | 홈 - 필터링 | 홈 - 검색 |
![]() |
![]() |
![]() |
---|---|---|
이벤트 추가 | 상세 - 애니메이션 | 상세 - 댓글쓰기 |
1. 이미지 다운샘플링
2. runCathing을 이용한 예외 넘기기
class SignInUseCase @Inject constructor(
private val userRepository: UserRepository,
private val preferenceRepository: PreferenceRepository,
) {
suspend operator fun invoke(email: String): Result<Unit> = runCatching {
val userId = userRepository.signIn(email)
.getOrElse { exception ->
when (exception) {
is NetworkFailException -> throw exception
else -> throw SignInFailException(exception)
}
}
preferenceRepository.updateUserId(userId)
}
}
3. Filter interface 분리를 통한 확장성 확보
// Filter.kt (domain)
sealed interface Filter {
sealed interface SingleFilter : Filter
sealed interface MultiFilter : Filter
}
sealed class Region(val key: String, val name: String) : Filter.SingleFilter
sealed class Category(val key: String) : Filter.MultiFilter
sealed class EventType(val key: String) : Filter.MultiFilter
// FilterState.kt (presentation)
interface IFilterState<T : Filter> {
val filterList: List<T>
fun selectFilter(filter: T)
fun resetAll()
}
interface ISingleFilterState<T : Filter.SingleFilter> : IFilterState<T> {
var selectedFilter: T?
fun setFilter(filter: T?)
}
interface IMultiFilerState<T : Filter.MultiFilter> : IFilterState<T> {
var selectedFilterList: List<T>
fun setFilter(filterList: List<T>)
}
4. 전략 패턴을 활용한 유연한 DateFormat 매핑
// EventMapper.kt (presentation)
fun EventDetail.toUiModel() =
EventDetailUiModel(
id = id,
name = name,
imgUrl = imgUrl,
region = region.name,
categoryTags = categoryList.joinToString(" ", transform = Category::toTag),
eventTypeTags = eventTypeList.joinToString(" ", transform = EventType::toTag),
date = format(DateFormatStrategy.FullDate(date)),
createdAt = format(DateFormatStrategy.RelativeTime(createdAt)),
likeCount = likeCount,
isLike = isLike,
owner = owner
)
5. derivedState을 통해 애니메이션 flag의 Recomposition 줄이기
@Composable
private fun EventDetailScreen(
modifier: Modifier,
eventDetail: EventDetailUiModel,
...
) {
val lazyListState = rememberLazyListState()
val skipImage by remember {
derivedStateOf {
(lazyListState.firstVisibleItemIndex == 0 &&
lazyListState.firstVisibleItemScrollOffset < 1100).not()
}
}
...
EventDetailTopBar(
skipImage = skipImage,
...
)
}
@Composable
fun EventDetailTopBar(
skipImage: Boolean,
...
) {
val containerColor by animateColorAsState(
targetValue = if (skipImage) {
MaterialTheme.colorScheme.background
} else {
Color.Transparent
},
label = "event-detail-top-bar-container-color-anim"
)
val iconColor by animateColorAsState(
targetValue = if (skipImage) {
MaterialTheme.colorScheme.surfaceContainerHigh
} else {
MaterialTheme.colorScheme.background
},
label = "event-detail-top-bar-icon-color-anim"
)
...
}