Flutter 혼자 앱 만들기: iOS 걷기 미션 개발 일기

오늘은 혼자 Flutter로 앱을 만들면서 iOS에서 걷기 미션 기능을 구현했던 이야기를 남겨볼까 한다. 시작은 정말 단순했다. 사용자가 매일 일정 걸음수를 채우면 보상을 주는 기능인데 Flutter에선 health 패키지를 이용하면 금방 될 줄 알았다. 그런데 막상 iOS HealthKit과 맞닥뜨리니 이게 말처럼 쉽지 않았다.

나는 먼저 iOS의 HealthKit 권한 문제부터 부딪혔다. Flutter에서 HealthKit을 쓰려면 iOS 프로젝트의 Info.plist에 권한 설명을 추가해야 한다. 여기에 아래와 같이 설정했다.

<key>NSHealthShareUsageDescription</key>
<string>걷기 미션을 위해 Health 데이터 접근이 필요합니다.</string>
<key>NSMotionUsageDescription</key>
<string>사용자의 걸음수를 기록하기 위해 Motion 데이터를 사용합니다.</string>

이 설정 없이 앱을 실행하면 아예 HealthKit API 호출 단계에서 오류가 발생했다. 그리고 Xcode의 Signing & Capabilities 탭에서 HealthKit을 활성화하지 않으면 역시 빌드가 막힌다.

그 다음 Flutter 코드에서는 권한 요청과 걸음수 가져오기 로직을 StepMissionService라는 클래스로 분리했다. 왜냐하면 권한 처리와 데이터 로드가 꼬이면 앱 진입부터 사용자 경험이 무너질 수 있기 때문이다. 코드의 핵심 부분은 이렇게 작성했다.

class StepMissionService {
  static final Health _health = Health();

  static Future<bool> requestAuthorization() async {
    final types = [HealthDataType.STEPS];
    bool granted = await _health.requestAuthorization(types);
    return granted;
  }

  static Future<int> getTodaySteps() async {
    final now = DateTime.now();
    final startOfDay = DateTime(now.year, now.month, now.day);
    try {
      List<HealthDataPoint> healthData = await _health.getHealthDataFromTypes(
        startTime: startOfDay,
        endTime: now,
        types: [HealthDataType.STEPS],
      );
      int totalSteps = healthData.fold(0, (sum, e) => sum + (e.value as int));
      return totalSteps;
    } catch (e) {
      debugPrint("걸음수 가져오기 실패: $e");
      return 0;
    }
  }
}

이 코드에서 중요한 포인트는 requestAuthorization으로 권한을 받은 후 데이터를 가져오는데, iOS에서는 권한을 거부하면 getHealthDataFromTypes가 아무 데이터도 주지 않거나 예외를 던진다. 그래서 try-catch로 예외를 처리하고, 걸음수가 없을 경우 사용자에게 명확한 안내를 띄워주기로 했다. 이걸 위해 아래와 같은 다이얼로그도 추가했다.

void _showPermissionHelpDialog(BuildContext context) {
  showDialog(
    context: context,
    builder: (_) => AlertDialog(
      title: Text('건강 데이터 권한 필요'),
      content: Text(
        '걷기 미션을 사용하려면 설정 > 개인정보 보호 > 건강 > 앱 이름 에서 걸음수 접근을 허용해야 합니다.'
      ),
      actions: [
        TextButton(onPressed: () => Navigator.pop(context), child: Text('확인')),
      ],
    ),
  );
}

실제 기기에서 테스트할 때는 시뮬레이터가 HealthKit을 지원하지 않아서 반드시 iPhone을 연결해서 테스트했다. 이 부분에서 많은 초보자가 막히는 걸 봤는데, 시뮬레이터에선 애초에 Health 데이터가 없기 때문에 무조건 실제 기기 테스트가 필요하다는 걸 기억해야 한다.

그리고 이 작업을 하면서 iOS 빌드 환경 문제로도 고생했다. CocoaPods 관련 에러가 발생했는데, 아래 명령어 순서대로 입력해 해결했다.

cd ios
pod deintegrate
pod install --repo-update
cd ..
flutter clean
flutter pub get
flutter build ios

이 과정을 마치고 앱을 빌드해보니 걸음수 데이터가 잘 올라왔다. 하루 목표인 만 보를 채웠을 때는 로컬 알림으로 보상을 알려주고 다음 미션으로 넘어가도록 처리했다.

마지막으로 UI는 사용자 친화적으로 바꿨다. 미완료 미션이 상단에, 완료된 미션은 하단에 표시하도록 했다. 코드에선 이렇게 간단하게 정렬했다.

missions.sort((a, b) => a.isCompleted ? 1 : -1);

오늘의 교훈

혼자 Flutter로 앱을 만들다 보면 별거 아닌 기능도 구현 과정에서 엄청난 함정을 품고 있다는 걸 매번 느낀다. 이번 HealthKit 연동도 그렇다. 간단히 걸음수만 가져오면 된다고 생각했지만, 실제론 권한 처리, 데이터 접근 예외 처리, 빌드 환경 문제까지 다양한 요소가 얽혀 있었다. 하지만 이런 과정을 기록해두면 나중에 같은 문제를 만난 개발자들에게 좋은 참고가 될 것 같다.

Similar Posts