배경
이번 졸업 작품 프로젝트에서 Flutter를 활용하여 애플리케이션을 제작하였는데, 기능 중 하나로 사용자의 텍스트를 받아 텍스트로 반환해 주는 기능을 제작해야 했다. 사용자가 펜으로 텍스트를 입력하면 해당 텍스트를 인식하여 사용자에게 다시 지정된 폰트로 반환해 주는 기능인데, 이걸 구현하려면 서버에서 전달받은 텍스트를 앱 실행 도중 화면에 띄워 주는 식으로 구현을 해야 했다. 해당 기능을 구현하고 나서 과정을 기록으로 남겨 놓고 싶어 글을 작성하게 되었다.
기능 설명
결과적으로 구현한 결과를 먼저 설명하자면 아래 영상과 같이 텍스트가 입력되고, 버튼을 누르면 지정한 영역에 대해 서버로 텍스트 인식 요청을 보내 받은 텍스트를 위젯 형태로 관리하여 화면에 띄워주는 방식으로 구현하였다.
구현 과정
영상에 보이는 것처럼 버튼을 누르면 버튼 이벤트가 발생하여 텍스트를 변환하고 화면에 띄워 준다. 이번 글에서는 위젯을 화면에 띄우는 부분에 대한 글이기 때문에 영역을 지정하고, 해당 영역에 대해 텍스트로 바꿔주는 부분에 대한 설명은 생략하려 한다. 텍스트를 위젯으로 만드는 부분부터 설명하자면 먼저 sharedPreferences에 텍스트 값을 저장해 주는 함수를 만들고 버튼 이벤트로 해당 함수를 불러와 서버로부터 받은 텍스트 값을 저장해 주었다.
첫 번째 코드가 sharedPreferences에 저장하는 함수, 두 번째 코드가 버튼이 동작하는 부분이다.
Future<void> saveImageUrl(String text) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs
.setStringList('texts', [...?prefs.getStringList('texts'), text]);
}
const Divider(),
TextButton(
child: const Text('OCR'),
onPressed: () async {
// 서버로 텍스트 변환 요청 과정
// 서버에서 받은 텍스트
String getOcrText = await uploadImageToServer(croppedBytes);
// shared_preference을 활용해 서버로부터 받은 값 저장
saveImageUrl(getOcrText);
},
),
이렇게 shared_preference에 추가한 값을 실행 중인 화면에 띄워줘야 했다.
플러터에서는 위젯을 미리 생성해 놓고 그 위젯을 띄워주는 방식인 걸로 알고 있는데 여기서 어떻게 새로운 위젯을 추가해야 할지 고민이 많았다. 그렇게 고민하다 생각한 방법은 위젯 리스트를 미리 생성해 놓고 이를 상태로 관리하고 shared_preference에 값이 추가될 때마다 위젯을 선언하고 이를 위젯 리스트에 추가해 주는 방식으로 구현을 하면 어떨까 해서 구현해 보았는데 생각한 대로 작동하였다.
아래는 구현에 사용한 코드이다.
@override
Widget build(BuildContext context) {
final textWidgets = useState<List<Widget>>([]);
final textPositions =
useState<Map<String, Offset>>({}); // 각 텍스트의 위치를 저장하는 상태
final fontSize = useState(30.0); // 폰트 초기값
final screenSize = MediaQuery.of(context).size;
final initialOffset = Offset(
screenSize.width / 2, // 화면 가로 중앙
screenSize.height / 2, // 화면 세로 중앙
);
// 텍스트 위젯을 불러오는 함수
// shared_preferences에 저장된 텍스트를 불러와서 textWidgets에 추가해준다
// 1초마다 갱신
useEffect(
() {
final timer = Timer.periodic(const Duration(seconds: 1), (timer) async {
SharedPreferences.getInstance().then((prefs) {
final loadedTexts = prefs.getStringList('texts') ?? [];
textWidgets.value = loadedTexts.map((text) {
// 텍스트 위치가 없으면 초기 위치(중앙)으로 설정
textPositions.value[text] =
textPositions.value[text] ?? initialOffset;
return buildDraggableText(
context,
fontSize,
text,
textPositions,
textPositions.value[text]!,
);
}).toList();
});
});
return () => timer.cancel();
},
[],
);
return MouseRegion(
//마우스 이벤트 감지 후 마우스 커서 변경
cursor: SystemMouseCursors.precise, //precise 마우스 커서로 변경
child: Stack(
//여러 위젯을 겹쳐서 표시할 수 있는 위젯임
children: [
buildAllSketches(context),
buildCurrentPath(context),
// textWidgets에 저장된 위젯들을 화면에 추가해준다
...textWidgets.value,
],
),
);
}
앞서 말했던 것처럼 shared_preference에서 값을 가져와 상황에 맞는 위젯으로 바꿔준 다음 textWidgets에 저장해준다음
코드 아래쪽에 보이는... textWidgets.value을 선언해 주어 textWidgets에 저장된 widget들을 화면에 출력해 주는 식으로 구현하였다.