말랑한 하루

[Flutter] (Project) MapleApp: 32. 비공개 테스트-1 본문

개발/Flutter

[Flutter] (Project) MapleApp: 32. 비공개 테스트-1

지수는말랑이 2024. 2. 29. 13:24
반응형

비공개 테스트는 약 20명의 테스터를 선발하여 2주 동안 진행하는 과정이다. 신입 개발자 계정은 바로 프로모션으로 출시할 수 없고 이 과정을 반드시 거쳐야 한다는 흠이 있다.

 

2023년 12월 이후 개정된 사안으로, 애플리케이션 출시 전 앱 자체의 성능과 개선 사항을 해결하면서 사용자에게 맞는 앱으로 탄생할 수 있게 하자는 취지이다.

 

이런 비공개 테스트를 약 2주 동안 진행하면서, 내가 고쳐왔던 그리고 고치고 싶었던 내용과 보안 이슈 등에 관하여 서술할 예정이고, 추후 고도화를 위한 작업을 진행하면서 새로운 버전의 앱을 출시하고, 사용자들로 하여금 새로운 버전을 다운 받을 수 있게 하는 행위까지 경험해보려 한다.

🐇 dio request exception

CRUD를 진행하면서 한 요청에 하나의 쿼리를 진행하는 과정엔 AsyncValue.guard를 사용하여 각 행동에 대해 value/error/loading을 지정할 수 있다.

 

하지만 내 프로젝트 같은 경우 Read request 밖에 없기 때문에, 한 함수에 모든 get 요청이 들어가 있다. 이 경우, 각 request에 대해 try/catch를 삽입하며 잘못된 경우 에러를 반환하고자 한다.

 

try/catch를 적용하기 전 코드는 이렇다. 다양한 구간이 포함되어 있지만, 간략하게 보여드리기 위함이다.

//AsyncNotifier<MainData>
Future<MainData> _fetch() async {
    Response response = await dio.get(dotenv.get('PATH'));
    final data = Data.fromJson(response.data);

    return MainData(data: data);
}

// outpage
final asyncMainData = ref.watch(asyncCharacterProvider);

return switch (asyncMainData) {
  AsyncData(:final value) => DataInfo(info: value),
  AsyncError(:final error) => MainErrorPage(message: error),
  _ => const LoadingSpinner(),
};

함수의 특성 상 MainData 객체를 반환해야 하고, ref.watch로 Provider를 감시하는 경우 MainData가 반환 되기 전 까지 switch 구문의 _ ⇒ 상태에 해당하며, MainData가 반환되면 AsyncData를 표출한다.

 

그러나 이 구간에서는 AsyncError 개체를 반환하는 구간이 없어, AsyncNotifier에서 request 요청에 실패하는 경우 앱 구동이 멈추게 된다. 이 경우를 try/catch를 적용하여 error가 생기는 경우 AsyncError에 맞는 데이터로 반환하여 MainErrorPage를 표출하려 한다.

 

AsyncError가 수신하기 위한 Future _fetch 함수의 반환 방법은 다음을 사용하면 됨을 찾았다.

 

🍒 Future.error('message')

 

그러나 우리는 dio 객체에서 Response를 가로채 모든 오류에 대해 DioException을 반환하도록 하였고, Future 객체를 반환하는 Notifier를 AsyncValue를 활용하여 관리하고 있기 때문에 Future.error에 어떤 객체를 실어 보내도 AsyncError로 넘겨줄 수 없었다.

 

그 이유는 해당 값을 바라보는 NotifierProvider가 fetch 함수에서 에러가 발생할 때, 우리는 Future에 특정 개체를 담아서 보내기 때문이다. 그래서 AsyncValue는 개체에 대한 정보가 없는 상태이므로, AsyncError에서 Exception: variable is not found 오류를 표출하게 된다.

 

그래서 AsyncError의 error 객체를 사용하지 않고, 최대한 각 요청 페이지를 분리하여 메세지를 넘겨주는 방식으로 처리했다.

🐇 TextField 입력 제한

Play Console에 appbundle을 등록하면, 사전 출시 보고서를 받을 수 있다. 안정성/실적/접근성/스크린샷/보안 및 신뢰에 관하여 오류/경고/사소한 문제로 나누어 알려준다. 그 중, TextField에 이모지가 입력되면서, 이모지에 대해 콘텐츠 라벨이 지정되지 않는 문제에 대해 경고했다.

 

이모지는 446개가 넘고, TextField에 입력해야 할 정보는 영대소문자, 숫자, 한글이 끝이기 때문에 이외 문자는 입력하지 못하게 막아야했다. 그래서 찾아낸 속성이 inputFormatter이다.

 

※ reference : https://api.flutter.dev/flutter/services/TextInputFormatter-class.html

 

inputFormatter는 TextInputFormatter class를 List로 받는 개체이다. 지정한 TextInputFormatter에 따라 달라지는데, TextInputFormatter에서 제공되는 withFunction method를 사용하면 TextFiled에 입력되는 oldValue/newValue를 비교하고 사용자의 요구 조건에 따라 처리할 수 있다.

 

※ reference : https://api.flutter.dev/flutter/services/FilteringTextInputFormatter-class.html

 

비슷한 행위를 진행하는 class의 예제를 보면 withFunction method의 활용을 볼 수 있다. 내가 지정한 패턴에 맞춰 newValue를 판단하고, old/new Value를 반환하도록 설정할 수 있다. 내가 사용한 코드는 다음과 같다.

TextInputFormatter.withFunction(
    (oldValue, newValue) {
  return newValue.text.contains(
          RegExp(r'[^0-9a-zA-Zㄱ-ㅎ가-힣]'))
      ? oldValue
      : newValue;
})

🐇 콘텐츠 라벨 지정

아이콘/이미지에는 스크린 리더가 읽을 수 있는 라벨이 있어야 합니다. 대표적인 뒤로 가기 버튼인 경우 해당됩니다. 애플리케이션 내 아이콘/이미지를 사용하고 있다면 모든 Widget에 관해 진행하는 것을 추천합니다.

 

※ reference : https://api.flutter.dev/flutter/widgets/Image/semanticLabel.html

 

콘텐츠 라벨 지정은 Icon/Image Widget의 semanticLabel 속성을 활용하여 다음과 같이 할 수 있습니다.

Icon(
  Icons.navigate_before_rounded,
  color: Colors.white,
  semanticLabel: '뒤로 가기 버튼',
),

또한 콘텐츠 라벨은 Semantic Widget을 활용하여 설정할 수 있다.

 

※ reference : https://api.flutter.dev/flutter/widgets/Semantics-class.html

 

Image나 Icon과 같은 Widget은 내부 semanticLabel을 활용하여 지정할 수 있지만, 대게 많은 요소는 존재하지 않고 설명할 수 있는 방법도 많지 않다.

 

그래서 Flutter 에서는 Semantic Widget을 제공하는 것이다. 내부 label 속성을 활용하여 요소에 대해 설명하고, 해당 요소가 button인지 readOnly인지 등의 내용을 assitive technologies에게 알려줄 수 있다.

🐇 GestureDetector 활용하기

애플리케이션을 사용하다 보면, NavigationBar는 접근성이 뛰어나지만 상단 SelectTabBar는 높은 위치에 존재하여 접근성에 매우 불리하다. 사용자로 하여금 ViewArea에 존재해야 하기 때문이다. 그래서 사용자 제스쳐를 통해 간단하게 SelectTabBar를 조정할 수 있도록 만드려고 한다.

GestureDetector(
    onPanUpdate: (details) {
      if (details.delta.dx < -3) {
        ref.read(skillSelectTabProvider.notifier).update((state) => 'v');
      }
    },
)

onPanUpdate 속성은 사용자가 포인터를 변경 시킬 때마다 호출된다. 원하는 임계값에 도달했을 때, SelectTabBar의 속성 값을 변경하여 주면 된다.

반응형
Comments