- jetpack compose를 이용한 상태관리에서 MutableState와 MutableStateFlow의 차이가 궁금하다.
-
이해에 앞서 주로 MVVM에서 사용되는 LiveData는 안드로이드 컴포넌트의 생명주기를 인식하여 끝나는 즉시 관찰을 멈추어 memory leak을 방지한다. 하지만 이는 UI없는 환경에서 사용하기 어렵다는 단점이 있다.
-
Flow는 코루틴 기반 비동기 데이터 스트림 처리 라이브러리로 반응형 프로그래밍(생산자, 중간연산자, 소비자)에서 사용된다. Cold Stream으로 상태가 없어 새로운 데이터가 입력될 때 마다 새로운 스트림이 생성된다.
-
StateFlow는 Hot Stream으로 상태를 가지며 데이터 스트림의 역활을 수행한다. 이로서 화면이 돌아가도 데이터를 다시 불러올 필요가 없다.
-
SharedFlow는 Hot Stream으로 초기값이 없어도 되며 캐시를 가진다.
-
MutableStateFlow는 상태를 비동기 데이터 스트림으로 처리하기 위해 사용한다.
https://oliveyoung.tech/blog/2022-12-14/Android-State-Flow/
- Kotlin에서 Unit타입이란? 반환되는 람다 타입이 Unit인 경우가 많아 궁금하다.
- kotlinx.coroutines.withContext함수는 어떤 역활을 하는가? Toast메시지 띄우기 전에 사용되기에 궁금하다.
- 코틀린은 동시성 프로그래밍을 지원한다. 병렬적으로 실행해도 되는 작업을 순차적인 아닌 동시에 진행하여 최적화를 진행하는 것이다. 코틀린은 스레드 관리를 CoroutineDispatcher(스레드 간 코루틴 분산 오케스트레이터)가 수행하기에 이를 생성 후 이를 사용하는 코루틴을 시작할 수 있다. 이때 결과 반환을 위한 목적으로 코루틴을 시작하면 async()를 사용해야하는데, 아래와 같이 사용하며 .join과 .await는 예외처리 전파를 하지 않고 하는 것의 차이가 있다.
runBlocking{
val task=GlobalScope.async{ doSomething() }
(1) task.join()
(2) task.await()
}
fun doSomething(){
throw UnsupportedException("Can't do")
}
다음으로 .launch는 결과를 반환하지 않는 코루틴에 사용한다.
- job은 Fire and Forget즉 시작하면 예외없는 한 대기하지 않고 시작하는 작업이다. 생성->활성->취소중->취소의 생애주기를 가지며 한 방향으로만 이동한다.
- 이제야 withContext가 나오는데, 이는 임시 컨텍스트 스위치로 async와 동일한 역활을 하지만 await를 호출할 필요 없이 마지막 구문의 결과가 리턴될 때 까지 기다린다. 즉, 프로세스에 job을 포함시키지 않고 다른 컨텍스트로 전환할 수 있게 해주는 일시중단 함수이다.
https://velog.io/@jshme/kotlin-coroutines-basic
- MutableStateFlow를 가져올 때 collectAsState()는 어떤 역활을 수행하는가? VM의 속성상태를 가져올 때 그냥 ViewModel.state1이 아닌 ViewModel.state1.collectAsState().value를 수행하는지가 궁금하다.
-
가져온 값의 상태가 Flow이기에 이를 State로 변환할 때 사용한다. 이를 온전히 이해하기 위해서는 flow와 stateflow의 차이를 알아야하는데, flow는 데이터의 흐름을 발생시키기만 할 뿐 데이터를 저장하지 않는다(데이터 생성-> 데이터 변환 -> 데이터 소비) 고로 잠시 값을 저장하는 변수를 두어 flow의 값을 저장할 수 있지만 이는 보일러 플레이트 코드(비슷비슷 반복)를 만들어내기에 StateFlow를 이용하여 데이터 저장소의 역활과 데이터 스트림Flow의 역활도 수행한다.
-
그렇다면 왜 로그인에 사용되는 email과 password는 일반 변수가 아니고 상태 변수도 아니고 왜 Flow를 사용했을까? 데이터의 흐름이라는 개념은 비동기 데이터에 사용된다. 로그인 과정에서 email과 password가 비동기적으로 변경될 경우(사용자 입력 변경 혹은 폼 업데이트) Flow를 사용하여 이를 쉽게 처리할 수 있다. 또 StateFlow를 사용하면 상태가 변경될 때 UI를 자동으로 업데이트 하기에 간편한 상태관리가 가능하다.
-
비동기적으로 email과 password값의 상태변경을 쉽게 처리하기위해 StateFlow를 사용했고, 이를 사용하기 위해 State로 변경하는 .collectAsState()함수를 사용한다.
https://ohdbjj.tistory.com/73
https://kotlinworld.com/232
- 텍스트 입력을 위한 BasicTextField에서 사용되는 기본 인자 속성들이 궁금하다. 특히 decorationBox가 궁금하다.
- Compose는 TextField와 BasicTextField를 제공하는데 후자의 경우 디자인을 커스텀할 수 있다. 이때 사용하는 것이 decorationBox이며 Box()안에 modifier와 contentAlignment등을 이용하여 커스텀한다.
- 추가적으로 decoreationBox{}내부에는 여러 조건식들로 상황에 따른 문구를 출력할 수 있으며, innerTextField()와 같은 함수로 키보드 커서 노출여부 등을 설정할 수 있다. 즉, ()안은 디자인을 {}안은 간단한 로직 처리를 수행한다.
https://dev-inventory.com/32
- BasicTextField 생성인자 중 keyboardOptions는 어떤 역활을 하는가?
- KeyboardOptions의 속성으로 imeAction이 있는데, 이는 사용자가 키보드에서 특정 버튼(다음)을 눌렀을 때 어떤 동작을 수행할지 미리 설정할 수 있다.
TextField(
...,
keyboardOptions=KeyBoarOptions(
imeAction=ImeAction.Next // 다음 입력 필드로 포커스 이동
),
keyboardActions=KeyboardActions(
onNext={
//다음 필드로 이동하는 동작
}
)
)
TextField(
...,
keyboardOptions=KeyBoardOptions(
imeAction=ImeAction.Done
),
keyBoardActions=KeyboardActions(
onDone={
//입력 완료 후 동작(위는 id 밑은 pw)
}
)
)
- Password와 로그인 등 나머지 화면을 구성하기 전에, 테스트를 수행해보았는데 MainActivity에서 SignInScreen으로 렌더링이 되지 않는 문제를 발견했다.
- MainActivity에서 바로 Screen으로 넘어가는 것이 아니라 중간에 App파일을 통해서 Screen을 매핑시켜보겠다. 하지만 해결하지 못했는데, 이유는 App에서 해주는 역활이 Animation과 화면 분배 정도가 전부였고 실질적인 매핑에 관한 처리를 수행하고 있진 않았다. 다시 중간의 App을 제외하고 디버깅 메시지를 분석해보겠다.
- 이때 문제가 SignInScreen.kt에서 발생한 것을 확인할 수 있었다.
즉, 매핑은 문제가 없었고 Screen내부에서 문제가 발생했다는 뜻이다.
- 전반적인 코드를 검토하면서, MainActivity에 @AndroidEntryPoint가 없음을 알게되었다. 그리하여 추가했지만 이번에는 gradle이 제대로 설치되지 않았다는 오류 메시지를 발견할 수 있었다.
- 여러 자료를 찾아보았는데, @AndroidEntryPoint를 붙이고 발생하는 오류를 해결하는 것이 맞아보인다.
https://www.youtube.com/watch?v=uGjT5fAhBF8
- Hilt는 안드로이드 클래스에 생명주기를 고려한 의존성 주입을 할 수 있는 라이브러리인데, dagger를 추가적으로 사용하면 여러 보일러-플레이트 코드를 줄여준다. 기본 프로젝트 세팅으론 @HiltAndroidApp, Container, @AndroidEntryPoint, @Inject가 필요하다.
우선 @HiltAndroidApp을 만들어줬다.
package com.example.instagram_clone_try2
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
import android.content.Context
@HiltAndroidApp
class InstaApplication: Application() {
init{
instance = this
}
companion object{
lateinit var instance: InstaApplication
fun applicationContext(): Context{
return instance.applicationContext
}
}
}
다음으로 hilt-component는 module들과 injection을 묶어주는 역활을 하는데, constructor injectino할 경우 알아서 component를 생성해주고 module을 만들때는 직접 선언한 scope에 맞게 component를 만들어준다. SignInViewModel에서 @Inject constructor을 수행했기에 SignIngViewModel이 hilt-component를 가진다.
다음으로 @AndroidEntryPoint는 안드로이드 클래스의 생명주기를 따르는 의존성 컨테이너(hilt-component)를 만든다. 이는 현재 MainActivity에 추가해두었다.
https://f2janyway.github.io/android/hilt/
https://velog.io/@leeyjwinter/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-Hilt-3-Component%EC%99%80-Scope
- 위에서 공부한 Hilt요건들을 확인한 후 다시 실행을 해보니, 아래와 같은 오류가 발생하였다.
- 이 시점에서 사수님이 와주셔서 현 상황을 질문을 했는데 @AndroidEntryPoint는 꼭 있어야 하기에 기존의 오류는 해당 어노테이션의 누락이 원인이 맞고, 현재는 디버깅 메시지를 보니 Repository에서 @Inject constructor나 @Provides-annotated가 있어야 하는 내용. 즉 SignInRepository에서 문제가 발생하고 있음을 알 수 있었다. 하지만 현재 SignInRepository는 모든 메서드가 비어있어 실질적인 기능을 하지 않는다(UI가 목적이었기에) 고로 이 Repository를 아예 삭제해보자는 피드백을 받았다.
- SignInVieModel에서 Repository 사용 부분을 삭제 후 연관된 메서드도 삭제하려했는데, 문제의 원인을 찾을 수 있었다. repository에서 입력한 email과 password를 확인하는 메서드 getSignInResult()를 수행하는데 Repository내의 getSignInResult()는 아무 값도 반환하지 않는다. 임시로 false를 반환하게..처리를 하려는 순간 이미 result값의 null값 확인을 SignInViewModel에서 진행하고 있음을 알 수 있었다. 미련없이 삭제해버리자.
- 그러자 Repository관련 에러는 사라졌고, Gradle관련 오류만이 남게되었다. 마찬가지로 MainActivity에 @AndroidEntryPoint에 값이 있어야 한다는 오류였다. 이는 gradle 플러그인 문제로 보인다.
public final class MainActivity extends androidx.activity.ComponentActivity {
^
Expected @AndroidEntryPoint to have a value. Did you forget to apply the Gradle Plugin? (com.google.dagger.hilt.android)
See https://dagger.dev/hilt/gradle-setup.html
[Hilt] Processing did not complete. See error above for details.
- 그런데 사내에서 사용중인 gradle세팅과 현재의 세팅과 build.gradle(데이터), 프린터기 모듈 등 차이가 있어 gradle build가 되지 않았다.
- 이 시점에 사수님에게 질문하여 프린터기 모듈과 data관련 모듈 일부 제거 등의 방법으로 해결하였다. 참고로 프린터기 모듈에 사용하는 파일의 확장자는 aar이었는데. 이는 Axis Archive의 약어이다. 프린터기의 소스가 Java를 기반으로 작성되었는데, 이 API를 프린터기 연결 및 출력에 사용하기 위해 설치하는 라이브러리라는 것을 알 수 있었다.
- gradle세팅만 완료된 후에 실행시켜보았는데, 컴파일러가 특정 tool을 추가하라고 해서 추가하였다.
AndroidManifest.xml
tools:replace="android:theme"
- 그 후 android:name=".MainActivity"를 못찾아서 File->Invalidate Caches->Invalidate and Restart를 수행해보았다.
https://velog.io/@glass101/AndroidStuidoandroid-studio-no-usages-found-in-project-file-unresolved-class-mainactivity
퇴근시간이 다 되어 우선 여기까지만 하겠다. 현재 Gradle세팅이 완료되었고, 내일은 try2의 파일을 옮겨 실행해보고 정상적으로 실행이 된다면 마저 SignInScreen을 구현하면 될 듯 하다.