올해의 마지막 행사라고 생각하고 신청한 GDG Songdo 2023이다.
송도라 좀 멀긴하지만 발표 주제도 다양하고 알차보여서 신청했다.
난 도전적인 문제를 해결하고 싶은데, 안드로이드 개발을 하면서 화면만 뽑아대니 회의감이 들었던 시절도 있었다. 화면빼고 정말 문제들만 남는 DevOps나 백엔드로 일하고 싶었다.
하지만 최근에 문득 생각이 든게...
"잘하는 걸 즐기지 못한다는 건 인생을 하드모드로 살아가는 것"이라는 생각이 들었다.
정말 길을 걷다가 뿅-하고 떠올랐다. 잘하는 걸 더 잘해서 잘하는 자체로 재미있게 즐겨도 좋지않을까?
그래서 이번 발표는 안드로이드/KMP 관련 발표만 있는 gradle clean
방에서 썩어보았다.
모두 양질의 발표로 정말 유용하게 잘 들었지만, 코드샘플이 있었던 몇 가지 발표에 대한 메모들을 적어보려한다.
유광무 : KMP로 번역기 만들기
기억은 확실히 나지 않지만 어떤 대회에서 출품하기 위해 1주일만에 번역기 앱인 Transer를 개발하셨고, 해당 프로젝트를 구현하면서 고민했던 점들을 공유해주셨다. PROCESS_TEXT를 이용해서 다른 앱 사용 중에도 번역이 가능하도록 한 아이디어가 좋았다! 회사에서 만드는 제품에도 어디에 적용하면 좋을까...잠깐 고민을 했다.
SQLDelight
그리고 SQLDelight라는 라이브러리의 존재도 여기서 처음 알았다. 이후 발표에서도 소개되었는데 KMP쪽에서는 Room마냥 Local DB를 다룰때 보편적으로 쓰이는 듯 하다. sq
파일에 Query를 적어주면 빌드시에 Kotlin API로 만들어준다. Query는 Flow로도 받을 수 있고...
produceState
다음은 코드에서 produceState / awaitDispose가 잠깐 나왔는데 처음보는 것들이라 간략히 찾아봤다.
유광무님은 ViewModel을 iOS나 Desktop등 비 Android 플랫폼에서 사용하기 위해서 이를 사용했다. awaitDispose는 produceState 블록 안에서 Dispose할때 쓰는 것이고, produceState가 조금 더 중요하다.
아래는 Github에서 랜덤하게 가져온 하나의 예시 코드다. produceState는 State<T>
를 반환한다.
이렇듯 Flow, LiveData, RxJava, Repository처럼 Composable이 아닌 것들, 즉 외부의 상태를 가져와 Compose 상태로 변환할때 사용할 수 있다. 일반적으로 많이들 쓰는 Flow<T>.collectAsState
의 내부 구현 역시 produceState로 되어있고, 이 예시코드와 얼추 비슷하다.
val issPosition by produceState(initialValue = IssPosition(0.0, 0.0), repo) {
repo.pollISSPosition().collect { value = it }
}
UseCase
아직 클린 아키텍쳐가 체화되지 않아서...켁켁 남들이 해놓은 것을 답습하고 있는데 종종 UseCase에서 다른 UseCase를 호출하고 싶을때가 있었다. 생각해보면 Domain Layer에 있으니 당연히 그걸 가져다 쓰면되는데 좀 생각을 덜했나보다... UseCase 구현 시 다른 UseCase를 이렇게 사용할 수 있다는 예시로서 이 코드가 눈에 들어왔다.
class TranslateUseCase(
private val translationRepository: TranslationRepository,
private val detectLanguageUseCase: DetectLanguageUseCase,
private val getPreferencesUseCase: GetPreferencesUseCase
) {
suspend operator fun invoke(q: String) =
detectLanguageUseCase(q).language.let { language ->
val (source, target) = getPreferencesUseCase().firstOrNull()
?: throw NullPointerException("Preferences is null")
makeSourceTargetPair(language, target.language, source.language).let { (target, source) ->
translationRepository.translate(q = q, target = target, source = source)
}
}
private fun makeSourceTargetPair(language: String, target: String, source: String): Pair<String, String> =
when(language) {
"und" -> target to source
target -> source to target
!in listOf(target, source) -> source to language
else -> target to source
}
}
이상훈 : KMP 개발을 위한 알아두면 좋은 라이브러리 소개 / DI 프레임워크 찍먹하기
KMP 개발시에 많이들 쓰는 라이브러리들을 하나씩 설명해주셨다. KMP의 기초를 오늘 이 행사에서 처음 접해서 대부분은 이런게 있구나~하고 넘어갔는데 상태관리 라이브러리라고 해야할지...Orbit Multiplatform 예시코드가 좀 기억에 남았다. Orbit 공식 문서에 이 코드가 있어서 지금 같이 정리를 해보려고한다.
data class CalculatorState(
val total: Int = 0
)
sealed class CalculatorSideEffect {
data class Toast(val text: String) : CalculatorSideEffect()
}
먼저 상태와 SideEffect를 이렇게 정의를 해두었다.
개발하면서 SideEffect는 다 개별로 처리했는데 이렇게 모아둔게 나름의 충격이었다. ViewModel에서 String 접근 문제만 해결하면 정말 깔끔하게 SideEffect를 처리할 수 있는 방법일 것 같다.
class CalculatorViewModel: ContainerHost<CalculatorState, CalculatorSideEffect>, ViewModel() {
// Include `orbit-viewmodel` for the factory function
override val container = container<CalculatorState, CalculatorSideEffect>(CalculatorState())
fun add(number: Int) = intent {
postSideEffect(CalculatorSideEffect.Toast("Adding $number to ${state.total}!"))
reduce {
state.copy(total = state.total + number)
}
}
}
만들어진 상태와 SideEffect는 ContainerHost라는 인터페이스에 따르도록 한다.
interface Container<STATE : Any, SIDE_EFFECT : Any>
Orbit MVI 시스템의 심장이라고하는군...
상태(Input)가 바뀌면 SideEffect(Output)이 생긴다. ViewModel 예시코드처럼 SideEffect를 postSideEffect
로 뱉을 수 있다.
class CalculatorActivity: AppCompatActivity() {
// Example of injection using koin, your DI system might differ
private val viewModel by viewModel<CalculatorViewModel>()
override fun onCreate(savedState: Bundle?) {
...
addButton.setOnClickListener { viewModel.add(1234) }
// Use the one-liner from the orbit-viewmodel module to observe when
// Lifecycle.State.STARTED
viewModel.observe(state = ::render, sideEffect = ::handleSideEffect)
// Or observe the streams directly
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
viewModel.container.stateFlow.collect { render(it) }
}
launch {
viewModel.container.sideEffectFlow.collect { handleSideEffect(it) }
}
}
}
}
private fun render(state: CalculatorState) {
...
}
private fun handleSideEffect(sideEffect: CalculatorSideEffect) {
when (sideEffect) {
is CalculatorSideEffect.Toast -> toast(sideEffect.text)
}
}
}
Activity나 Fragment에서는 이렇게 사용할 수 있다.
kotlinx.datetime
솔직히 어느 언어든 시간 다루는건 좀 복잡하다고 생각했다. JS의 Moment나 python의 datetime, timedelta는 솔직히 편하긴하다. Java/Kotlin은 GG치고 손놓고 있었는데 괜찮은 녀석이 등장했다. kotlinx-datetime
은 시간 계산과 표기, 파싱을 편하게 도와주는 라이브러리다. 주의할점은 Kotlin 1.9.0
이하 버전과는 호환되지 않으며, Android Min API가 26이하라면 AGP 4.0
이상을 사용해야하고 core library desugaring을 활성화시켜주어야한다.
노현석 : 우리모두 삽질한다
채팅방, Github에서 애니캐로만 봤던 프루님을 실제로 봤다. 연예인 보는 기분...ㅋㅋㅋ 저렇게 잘하시는분이 삽질 콘텐츠를 들고와서 나름의 위안이 되는 부분도 있었다. (?) 문제들을 보면 나도 겪을 수 있을법한 것들이 많았는데 다른 사람이 문제를 풀어가는 모습을 트래킹하는 재미도 있었다. Glide에서 이미지 비율이 1:1이 아닐때 circleCrop()
의 기본 동작으로인해 이미지가 흐려질 수 있다는 삽질기는 언젠가 나도 유의해서 쓸 수 있겠다는 생각...
같은 시간을 줘도 많은 일을 해내는 사람들을 보면 반복되는 작업을 어떻게 쳐낼수 있을까 고민을 많이하는 것 같다. 울 팀장님도 그렇고...ㅋㅋㅋ 그리고 이것저것 뚝딱뚝딱 만든다. feature module을 생성하는 Android Studio Plugin 상상만해봤는데 시연보니까 정말 편해보인다. 저것까지는 아니어도 LiveTemplate / FileTemplate을 몇 가지 만들어서 팀원들과 공유해봐야겠다.
강경완 : Compose Animation
말 그대로 Compose Animation에 관련한 발표였다! 평소에 Animation을 구현하면서도 어떤 방법으로 구현할지 선택에 대한 고민이 있었는데 덜어주신듯... 발표자료가 아직 안올라온거같은데 대략 이런 의사결정 구조가 있었다.
레이아웃 전체가 애니메이션 O?
├── 등장 / 퇴장 O --> AnimatedVisibility
└── 등장 / 퇴장 X --> AnimatedContent
레이아웃 전체가 애니메이션 X?
└── animate*AsState
머릿 속에서 잊혀지고 있던 updateTransition
에 대해서도 다시 상기하고...
더 로우레벨로 애니메이션을 구현할 수 있는 Animatable의 존재도 알게되었다.
그리고 마지막에 Let's Try++에 나오는 예시 코드가 있는데 rememberDraggableState
를 사용해서 사용자의 드래그가 임계치에 닿지 않으면 패널을 닫지않고 다시 애니메이팅을 하는 부분이 인상적이었다. 구현하기는 다소 복잡하더라도 종료같은 부분에서 임계치를 두는 부분은 UX에 좋다. 자료 올라오면 회사에 적용할 수 있는 부분은 없는지 다시 보고싶다.
'나 > 이슈' 카테고리의 다른 글
K-DEVCON 2024를 다녀와서... (2) | 2024.10.12 |
---|---|
[리썰컴퍼니] 세이브파일 다운로드 - $1200 가지고 시작 (0) | 2024.02.25 |
방예담 <하나만 해(Only One)>에 숨은 저스틴 비버의 노래들 (49) | 2023.12.03 |
DASHI 채팅, 영상 Preview 업데이트 (2) | 2023.12.03 |
DASHI 관리자 업데이트 (3) | 2023.11.20 |