본문 바로가기
Android Jetpack Compose/Jetpack Compose

State 를 이해하고 TextField 구현하기 # Jetpack Compose UI Part2

by Developer88 2022. 11. 14.
반응형

지난 글에 이어서 Jetpack Compose 기본 UI Part2에서는 State 에 대해서 다루고,

이를 이용해 TextField를 구현해 보도록 하겠습니다.

 

지난 part1 글은 아래 링크를 참조해주세요.

>> Jetpack Compose UI Part1 # Color Card Modifier Column Row

 

1. State 

1-1. State

State의 의미는 상태인데요. 현재 UI의 상태를 의미합니다. 

UI는 유저나 네트워크의 응답등 따라서 변경된 상태가 반영되어야 하는데요.

Composable 함수는 이렇게 변화된 상태를 나타낼 때,

변경된 Value를 가지고 관찰하고 있는 State를 통해 Notify를 받고 그 값을 이용해 Composable함수를 재호출합니다.

이 과정에서, Composable이 다시 그려지는 recomposition을 거치게 되는 것 입니다.

 

말이 복잡해 보이니 최대한 단순화 하면,

Compose에서는 UI의 상태가 변화할 때 State를 참고한다는 것입니다.

이 말은 뭔가를 변화시킬 때 State가 필요하다는 것과도 동일합니다.

 

1-2. State가 필요하지 않은 경우

참고로 State없이 UI가 구현되거나 적용되야 하는 경우가 있는데요.

바로 일회성 UI이벤트들(One-Time Event) 입니다.

한번 보내면 끝나는 토스트메시지나 다른 글에서 다루고 있는 Navigation이벤트들입니다.

이런 일회성 이벤트의 경우에는 State없이 상태를 관리하지 않고 구현하면 됩니다.

이에 대해서는 5번 일회성이벤트에서 아래에서 좀 더 자세히 보도록 하겠습니다.

 

2. State 구현을 위한 API들

그럼 Compose에서 state을 구현할 때 필요한 API들을 정리해 보겠습니다.

 

2-1. mutableStateOf

MutableState Class는 Compose에 의해서 Observe(관찰)되는, 특정한 값의 홀더입니다.

Generic타입이고, 인자로 value를 넣어주면 됩니다.

여기에 들어가는 value는 MutableState객체를 만들 때 사용하는 초기값입니다.

 

 

아래에서 Red컬러를 MutableState의 초기값으로 지정했습니다.

(그런데 아래와 같이 remember없이 사용하였다고 에러가 나옵니다.

이 부분은 아래에서 remember API를 보면서 정리해 보겠습니다.)

 

 

이 value에 접근해서 값을 얻어 아래와 같이 UI에 적용할 수 있습니다.

이제 mStateValue는 state이 바뀔때 값이 변경되게 됩니다.

 

 

state에 저장된 값을 set할 때도 아래와 같이 value에 접근해서 변경해주면 됩니다.

 

 

2-2. remember API

위에서 Composable함수는 state가 변경되서 업데이트를 해주면,

다시 해당함수를 호출해서 recomposition을 한다고 하였는데요.

이 때 기존에 저장된 값들이 없다면 다시 그릴때 기존UI값들이 없는 상태가 됩니다.

state변경 후 recompose되어서 업데이트될때마다, 최초 이니셜 값들로 다시 그려져버리는 것 이지요.

 

이 문제를 해결하기 위해서 값을 기억해주는 remeber API를 사용해 주어야 합니다.

그래서 mutableStateOf함수를 사용할 때도,

remember 람다함수블록 안에서 사용해 주어야 합니다.

 

 

참고로 위의 코드는 다음과 같이 작성할 수도 있는데요.

by키워드를 이용하여서 작성할 수도 있습니다.

 

 

by키워드에 대한 자세한 이해는 아래의 글을 참조해 주세요.

>> Kotlin By 키워드에 대한 이해 # Property Delegate Pattern

 

2-3. Composable 과 State 그리고 remember

Compose의 API를 사용하면 할수록, State의 중요성을 느끼게 되는데요.

무언가 변경된 상태를 Compose에 반영할려면, State에 그와 관련된 데이터가 저장되어야 합니다.

이것은 저희가 변수명을 State이라고 한다고 되는것이 아니라, State클래스의 객체이거나 그 클래스를 상속한 것이어야 한다는 것 입니다.

그리고, remember라는 키워드도 계속 따라다닐텐데요.

그렇게 변경되어진 State을 기억해놓을 필요가 있을 때입니다.

 

3. Click에 따라서 UI바꾸기

아래에서는 mutableStateOf함수를 이용해서 Red컬러로 초기화한 Box레이아웃을 클릭할 경우,

Blue컬러로 변경하도록 하는 코드를 작성하였습니다.

 

 

실행해보면 다음과 같이 나오는 것을 볼 수 있습니다.

 

 

여기서 구현하지는 않았지만,

MutableState 의 value를 여러개의 UI들이 참조하고 있다면,

그 객체들의 값을 동시에 바꿀수도 있겠지요.

 

4. Composable의  State vs StateFlow

Jetpack Compose의 state을 이용해서 state을 저장했다가 다시 사용할수도 있지만,

코루틴 Flow의 API인 StateFlow를 이용할수도 있는데요.

StateFlow가 코루틴의 API이다보니, 코루틴 Flow의 여러가지 오퍼레이터들도 사용할수 있다는 장점이 있습니다.

좀 더 구체적인 내용에 관해서는 아래 글을 참조해주세요.

>> Kotlin Coroutine StateFlow 정리 # Android getStateFlow StateIn

 

5. ViewModel 

viewModel 에서도 아래와 같이 mutableStateOf()함수를 이용해 사용할 수 있습니다.

참고로 아래의 by 키워드에 대해서는 아래 글을 참조해 주세요.

>> By 키워드에 대한 이해 # Property Delegate Pattern

 

var showBotSelectDialog by mutableStateOf(false)

 

viewModel에 놓고 쓰느냐, 아니면 Composable 안에서 사용하느냐는 개발자가 판단을 해야하는데요.

다음과 같은 기준정도를 넣고 선택을 하면 될 것 같습니다.

  • data 나 비즈니스 로직에 관련된 것이라면 viewModel
  • 여러 Composable에서 공유하는 데이터라면 viewModel 
  • 데이터베이스나 네트워크 콜에 관련된 것이라면 viewModel

예를 들어서, LazyColumn과 같이 유아이를 그리는데,

스크롤의 끝에까지 같는지등에 대한 값을 저장하는 UI에만 종속되는 값들은,

굳이 viewModel에 넣어서 작업할 필요가 없는 것 이지요.

6. 일회성 이벤트 ( One-Time Event )

State은 Composable의 상태를 저장하고,

변경이 되면 Recompose하게 해주는 기준점이 되는데,

동시에 변화된 상태를 유지하는 역할도 합니다.

그런데 이 역할에 부작용도 있는데요.

한번만 실행되고 끝나야 할 이벤트들이 다시 좀비처럼 살아날 수 있다는 것 입니다.

 

한번만 실행되어야 할, One-Time Event에는 다음과 같은 것들이 있습니다.

  • 스낵바
  • 토스트
  • 에러메시지
  • 성공메시지
  • 삭제된 아이템들

이러한 것들을 State에 담아서 사용하다가 좀비처럼 살아나는 것을 경험할 수 있다는 것 입니다.

StateFlow를 사용해도 마찬가지이구요.

 

많이 예로 드는 것이, 가로에서 세로로 화면전환인데요.

화면이 전환될 경우, 안드로이드는 화면을 새로 다시 그립니다.

상태를 유지하기 위해서 State에 저장된 값을 기준으로 Recompose되는데요.

이 과정에서 위의 Event들이 다시 실행되버릴수도 있다는 말 입니다.

 

이를 피하기 위해서는 State가 아닌 값으로 받아야 합니다.

아래에서는 그 방법을 알아보도록 하겠습니다.

 

6-1. State가 아닌 값으로 받기

State가 아닌 값을 UI에서 받아서 업데이트 하려면 어떻게 해야할까요?

먼저 할 것은, 한번만 실행되야 하는 One-Time Event들을 UIEvent로 정의해 놓는 것입니다.

아래처럼, sealed class로 정의하거나,

Toast나 Back하는 정도라면 enum으로 정의해도 되겠지요.

 

 

이제 ViewModel에서 Channel객체를 만들고,

채널객체에 send()함수를 통해서 이벤트를 전달할 때 Flow로 받아서 실행하는 방법을 사용하면 됩니다.

 

참고로, Channel은 Coroutine 들간에 데이터를 주고받는 방법인데요.

아래의 예제와 같이 채널객체를 만들어서, 하나의 코루틴에서 데이터를 send하면,

다른 코루틴에서는 보낸 횟수만큼 receive()함수를 통해서 받을 수 있습니다.

파이프 패턴을 구현할 수 있도록 해 주는 코루틴 API이지요.

 

 

(참고로, subscribe하는 구독채널이 여러개인경우에는 SharedFlow를 사용할수도 있습니다.)

 

다시 ViewModel로 돌아가 보겠습니다.

ViewModel에서 mutable로 아래와 같이 UiEvent타입의 채널객체를 선언해주구요.

immutable로는 이 mutable객체로부터 receive한 값을 받도록 연결해 줍니다.

아래와 같이 receiveAsFlow()함수를 사용해주고, 이것을 UI에서 구독해서 받으면 되는 것 입니다.

 

 

receiveAsFlow()함수는 channel에서 데이터를 받을 때, 

hot flow로서 cold하지 않고, 바로 아이템들을 흘려서 받게 된다는 점이 특징이구요.

아래 주석에 나온 것 처럼, 오로지 하나의 콜렉터에 하나의 엘리먼트만 emit되는 특징을 가지고 있습니다.

One-time Event를 처리하기에 적절한 API라고 할 수 있겠습니다.

 

 

Event를 보낼때는 채널객체의 send함수를 사용하는데요.

send함수는 suspend함수이므로 코루틴스코프에서 실행이 되어야 합니다.

viewModel안에서 실행하므로 ViewModelScope를 사용해서 launch해 주면 되겠지요.

 

 

이렇게 State가 아닌, Channel을 통해서 데이터를 받도록 하고 UI에서 subscribe 하면, 

state에 저장되어서 생기는 문제들을 피할 수 있습니다.

 

7. State를 이용해 TextField 구현하기

TextField는 유저로부터 텍스트입력을 받을 수 있는데요.

유저가 입력을 하면 그 값을 State에 저장해서,

입력한 값을 저장했다가, 다른 UI에 반영시킬 수 있습니다.

 

TextField에서 사용하는 State는 아래와 같이 간단하게 만들 수 있습니다.

 

 

이제 TextField를 구현해 볼 텐데요.

onValueChange로 유저가 입력하여 변화된 값들을 textFieldState에 저장하구요.

singleLine은 true로 해 줍니다.

 

 

이제 버튼을 만들어서 입력시에 Toast메세지로 입력내용을 보여주도록 하겠습니다.

textFieldState이 있으니 이렇게 편합니다.

 

 

실행해 보면 다음과 같은 값이 나오는 것을 볼 수 있습니다.

 

 

이번에는 실시간으로 입력한 값을 하단의 TextView에 보이도록 하겠습니다.

아래와 같이 Text()뷰에 textFieldState만 넣어주면 끝입니다.

정말 간단하네요.

 

 

이번 글에서는 State와 이를 활용한 TextField에 대해서 정리해 보았습니다.

 

참고로 Jetpack Compose UI 의 지난 part1은 아래 링크를 참조해주시면 됩니다.

>> Jetpack Compose UI Part1 # Color Card Modifier Column Row

728x90

댓글