시작하며
Compose의 TextField로는 String과 TextFieldValue를 사용하는 두 개의 버전이 존재한다. TextFieldValue는 selection을 포함하고 있는 클래스로 String을 사용하는 기본 TextField에 커서 위치를 조절할 수 있는 기능이 추가된 버전이다.
하지만 일반 TextField는 커스텀이 어려운 편이라 커스텀하기 용이한 BasicTextField를 사용하게 되는데 BasicTextField에는 기존 두 가지 버전 이외에도 TextFieldState를 사용하는 버전이 추가되어 총 세 가지 버전이 존재한다.
이 글에서는 그중에서 TextFieldState이 사용되는 BasicTextField와 TextFieldState에 대해 알아본다.
TextFieldState
초기화할 때 initialText와 initialSelection을 입력할 수 있으며, saver가 포함된 rememberTextFieldState()를 호출해 초기화하면 된다.(뷰모델에서는 TextFieldState() 사용)
// Composable 함수에서 val textFieldState = rememberTextFieldState() // ViewModel에서 val textFieldState = TextFieldState()
- public properties
• text : TextFieldState는 특이하게 CharSequence를 사용한다. CharSequence는 String의 상위 인터페이스로 toString() 등의 함수로 String으로 변환해 사용할 수 있다.
• selection : TextFieldValue에 있던 selection과 동일한 TextRange 타입으로 기존 TextFieldValue와는 edit 함수를 사용해 프로그래밍식으로 변경할 수 있다는 차이가 있다.
• composition : 마찬가지로 TextFieldValue에 있던 것과 동일하며 ime에서 관리하는 구성 범위로 예를 들어 한자나 일본어, 이모티콘을 입력할 때 ime가 일부를 잠시동안 유지해 사용자가 선택할 수 있게 한다.
• undoState : TextFieldState에서 새롭게 추가된 기능으로 되돌리기 기능을 지원한다. undo, redo, canUndo, canRedo, cleanHistory 등의 함수를 호출해 사용하면 된다.
- function
TextFieldState은 수신 객체 지정 람다를 사용하는 edit 함수와 TextFieldState의 확장 함수를 사용해 편집할 수 있다.
먼저 edit은 TextFieldBuffer의 확장 함수를 람다로 받아와서 실행하는 함수로 사용할 수 있는 사용할 수 있는 대표적인 함수로는 insert, delete, replace, append 등이 있다.
// 확장 함수 fun TextFieldBuffer.insert(index: Int, text: String) { replace(index, index, text) } fun TextFieldBuffer.delete(start: Int, end: Int) { replace(start, end, "") } // 멤버 함수 override fun append(char: Char): Appendable = apply { onTextWillChange(length, length, 1) buffer.replace(buffer.length, buffer.length, char.toString()) }
내부 코드를 확인해 보면 append는 맨 끝에 공백을 한 개 추가해 주는 함수로 TextFieldBuffer 클래스의 멤버 함수인 반면 insert와 delete는 TextFieldBuffer의 확장 함수로 약간의 차이가 있지만 동일하게 edit 내부에서 사용할 수 있다. 이때 대부분의 함수가 replace를 사용하는 함수인 것을 알 수 있는데 replace에 대해 더 자세히 알아보면
internal fun replace( start: Int, end: Int, text: CharSequence, textStart: Int = 0, textEnd: Int = text.length ) { require(start <= end) { "Expected start=$start <= end=$end" } require(textStart <= textEnd) { "Expected textStart=$textStart <= textEnd=$textEnd" } onTextWillChange(start, end, textEnd - textStart) buffer.replace(start, end, text, textStart, textEnd) } fun replace(start: Int, end: Int, text: CharSequence) { replace(start, end, text, 0, text.length) }
시작과 끝, 그리고 text를 입력받아 내부에서 length를 사용해 처리하는 함수인 것을 알 수 있다. 이때 두 가지 replace 모두 TextFieldBuffer의 멤버 함수이다.
그 외에 두 개의 TextFieldBuffer 확장 함수가 더 있는데 placeCursorEnd(커서를 마지막에 배치), selectAll(모두 선택)이 있다.
다음으로 TextFieldState의 확장 함수는 자주 사용하는 기능을 사용하기 편리하게 확장 함수로 구현해 둔 것으로 세 가지 함수가 존재한다. 필자는 setText라는 함수를 추가로 만들어서 커서와 선택 관련 기능이 필요하지 않을 때 호출해 사용하고 있다.
// 새롭게 텍스트를 설정 후 커서를 마지막에 위치 fun TextFieldState.setTextAndPlaceCursorAtEnd(text: String) { edit { replace(0, length, text) placeCursorAtEnd() } } // 새롭게 텍스트를 설정 후 모두 선택 fun TextFieldState.setTextAndSelectAll(text: String) { edit { replace(0, length, text) selectAll() } } // 텍스트 모두 삭제 fun TextFieldState.clearText() { edit { delete(0, length) placeCursorAtEnd() } } // 커스텀 함수, 텍스트 설정 fun TextFieldState.setText(text: String) { edit { replace(0, length, text) } }
BasicTextField
androidx.compose.foundation:foundation 1.7.0 버전 중요 변경사항에 아래와 같이 설명되어 있다.
이제 TextFieldState를 사용하는 BasicTextField가 안정화되었으므로 모든 호출 사이트에서 BasicTextField(value, onValueChange)에서 BasicTextField(TextFieldState)로 이전을 시작하는 것이 좋습니다.
그렇다면 왜 TextFieldState를 사용하는 BasicTextField를 사용하는 것이 좋을까?
아래는 각각 String/TextFieldValue/TextFieldState를 사용하는 BasicTextField에 간단한 단어를 입력하고 있는 영상이다.

- 안드로이드 스튜디오의 layout inspector를 사용해 재구성 횟수를 확인할 수 있으며 왼쪽 영상의 파랗게 표시되는 게 재구성(recomposition)되고 있다는 뜻이고 오른쪽 트리의 숫자 중 왼쪽이 실제 재구성 횟수, 오른쪽이 건너뛰어진 횟수이다.
- Strng/TextFieldValue를 사용하는 BasicTextField(value, onValueChange)는 value가 텍스트를 직접 관리하기 때문에 텍스트를 입력해 값이 변경될 때마다 TextField 전체가 재구성된다.
- TextFieldState를 사용하는 BasicTextField는 TextFieldState가 텍스트를 관리하기 때문에 텍스트를 입력해 값이 변경되어도 재구성되지 않는다.
이처럼 불필요한 재구성을 줄여 성능 최적화에 도움이 되며 앞에서 설명한 것처럼 커서, 텍스트 선택, undo 기능 캡슐화 및 보존/복원 용이 등 여러 장점이 많기 때문에 BasicTextField(TextFieldState)를 사용하는 것을 추천한다.
해당 BasicTextField를 사용하는 방법은 아래 글에서 이어서 설명한다.
[Jetpack Compose] BasicTextField(TextFieldState) 사용하기
시작하며BasiceTextField에는 value/onValueChange를 사용하는 버전과 TextFieldState를 사용하는 버전이 존재한다. 이 글에서는 TextFieldState를 사용하는 BasicTextField에 대해 자세히 알아본다. TextFieldState에 대한
developuzzle.tistory.com
Reference
https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldState
TextFieldState | Android Developers
androidx.appsearch.builtintypes.properties
developer.android.com
https://developer.android.com/jetpack/androidx/releases/compose-foundation?hl=ko#1.7.0
Compose 기초 | Jetpack | Android Developers
이 페이지는 Cloud Translation API를 통해 번역되었습니다. 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Compose 기초 즉시 사용 가능한 구성요소를 사용해 Jetpack
developer.android.com
https://developer.android.com/develop/ui/compose/tooling/layout-inspector?hl=ko
Layout Inspector | Jetpack Compose | Android Developers
이 페이지는 Cloud Translation API를 통해 번역되었습니다. Layout Inspector 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Layout Inspector를 사용하면 에뮬레이터나 실
developer.android.com
Effective state management for TextField in Compose
TL;DR — The Compose roadmap reflects the work that the team is doing on multiple fronts, in this case Text Editing Improvements and…
medium.com