Compose TextField에서 한글 자소분리 해결하기
메모장 앱을 만들어 배포했는데, 스토어에서 다운로드 받아 테스트 해보니 한글 입력 시 자소 분리가 되는 문제를 발견했다...
원인은 TextFieldValue다.
formatting을 지원하기 위해 viewmodel에 TextFieldValue를 사용했는데, 입력이 발생하는 도중에 IME가 처리해야될 composition 정보가 계속 초기화되고 있었다.
Android의 입력기(IME)는 한글처럼 조합이 필요한 언어를 처리할 때 Composition(조합 중인 상태) 정보를 TextFieldValue 객체 안에 담아서 전달하기 때문에 조합 상태가 끊기면 자소가 분리되는 것이다.
TextFieldValue(
text = finalContent,
selection = finalSelection,
composition = finalComposition,
)
이렇게 composition 정보를 새 객체 생성 시 copy 해주는 것으로 해결했다. TextFieldValue를 계속 사용할 지는 조금 더 고민해봐야될 것 같다. data 레이어에 전달되지는 않지만, viewmodel에서 compose의 context를 알고있는 것과 비슷해지기 때문에 개선이 필요해 보인다.
TextFieldValue
TextFieldValue는 단순한 문자열이 아니라, 다음 세 가지 정보를 담고 있는 스냅샷(Snapshot) 객체다.
- text (String): 현재 화면에 보이는 전체 문자열
- selection (TextRange): 커서의 위치 또는 드래그 선택 영역
- composition (TextRange?): 현재 IME(키보드)가 "조합 중"이라고 표시하고 있는 영역 (밑줄이 그어진 부분)
알파벳은 키 하나가 글자 하나(a -> a)지만, 한글은 자소(초성 중성 종성)의 결합(ㅎ+ㅏ+ㄴ)이 필요한 구조다. IME는 글자가 완성되기 전까지 해당 글자를 composition 상태로 두는데 이게 유지되어야만 다음 자음/모음이 들어왔을 때 기존 글자와 합칠 수 있다.
문제 상황을 다시 자세히 적어보겠다.
sequenceDiagram
participant User
participant IME
participant ViewModel
participant TextField
Note over User, TextField: [문제 상황: composition 누락]
User->>IME: 'ㄴ' 입력 (현재 '하' 상태)
IME->>ViewModel: text="한", composition=(0,1)
Note right of IME: "아직 조립 중"
ViewModel->>ViewModel: 객체 재생성 (composition 누락!)
ViewModel->>TextField: text="한", composition=null
TextField->>IME: "현재 상태: '한', 조립중 아님"
Note right of IME: "commit"
User->>IME: 다음 글자 입력
IME->>TextField: 새 글자로 시작 (자소 분리 발생)
사용자가 "한"을 입력하기 위해 ㅎ을 치고 ㅏ를 쳤을 때라고 가정하자.
IME가 App으로 올바른 신호를 전달하는 시나리오다.
- 사용자가 ㅏ를 입력
- IME는 "ㅎ"을 "하"로 바꾸고, Compose에게 전달
- 전달된 데이터:
- text: "하"
- composition: (0, 1) (0번부터 1번 인덱스까지는 아직 조립 중이라는 뜻)
반면 문제 상황에서는 입력이 발생하고 UI가 Recomposition되면서 TextField는 ViewModel이 준 상태를 IME에게 다시 알려주는데, IME는 composition 정보를 null로 인식한다.
- 사용자가 받침 ㄴ을 입력
- IME는 앞선 "하"가 이미 끝난 글자라고 판단. 그래서 ㄴ을 앞 글자에 붙이지 않고, 새로운 글자 시작으로 처리한다
- 결과: "하" + "ㄴ" -> "하ㄴ" (분리됨)
IME(Input Method Editor)?
알파벳은 IME의 고마움을 모른다. 하지만 한국어, 중국어, 일본어는 다르다.
물리적인 키의 개수보다 표현해야 할 문자가 훨씬 많기 때문에, 여러 번의 키 입력을 받아서 하나의 문자로 완성해 주는 '조립 공정'이 필요하고 이 조립 공정을 담당하는 소프트웨어가 바로 IME이다.
libhangul을 보면 한글 IME는 내부적으로 오토마타(Finite State Machine) 알고리즘을 사용하는데 상태(State)에 따라 다음 입력이 어떻게 처리될지 결정된다.
stateDiagram-v2
[*] --> Start
Start --> 초성: 자음 입력 (ㄱ)
초성 --> 중성: 모음 입력 (ㅏ)
중성 --> 종성: 자음 입력 (ㄴ)
종성 --> [*]: 다른 자음 입력 (commit)
note right of 종성
이 상태에서 composition이 끊기면 '한'이 확정되고 다음 자음은 새 글자의 초성이 됨
end note
- 초성 상태: 자음이 들어오면 -> 중성 상태로 이동
- 중성 상태: 모음이 들어오면 -> 종성 상태로 이동하거나, 이중 모음(ㅘ, ㅞ)으로 합침
- 종성 상태: 자음이 들어오면 -> 받침으로 쓸지, 다음 글자의 초성으로 넘길지 결정
이전 composition 정보가 없으면 초성 대기상태로 머무른다.