나만의 학습 기록

최종 목적은 기술 블로그💩

개발일지

만보기 API 연동하기(1)

밈밍민믹 2026. 5. 26. 22:03

안녕하세요. 요즘 개발하는게 다시 재밌어져서 이전에는 생각하지 못했던 것에 대한 기획이나 계획 등 재미를 다시 느끼는 것 같습니다. 이번에는 저의 프로젝트에서 필요한 만보기와 지도의 현재 위치, 마커 표시 등에 대해서 API를 가져오는 과정을 얘기해보고자 합니다. 그럼 시작해봅시다.


프로젝트 구조

현재 제 프로젝트의 구조는 백엔드는 Java/Spring Boot, 프론트엔드는 Flutter(Dart), 서버 및 데이터베이스는 Ubuntu + Nginx/MySQL로 구성되어 있습니다. 안드로이드 앱을 기준으로 개발을 시작했지만, 포트폴리오 목적으로 웹을 먼저 배포했습니다. 이후 기능이 어느 정도 완성되면 원스토어 배포까지 진행하는 것이 목표입니다.

 

그렇기 때문에 API를 추가하기 전에 먼저 고민해야 할 부분이 있었습니다.

 

첫 번째는 웹에서는 만보기 기능이 필요하지 않다는 점입니다.
만보기는 사용자가 실제로 휴대폰을 들고 이동할 때 의미가 있는 기능이기 때문에, 웹 환경에서는 직접적인 만보기 측정 기능을 구현할 필요가 없습니다.

 

다만 웹에서도 앱과 완전히 분리된 화면이 아니라면, 서버에 저장된 오늘 걸음 수나 캐릭터 상태를 조회하는 정도는 필요할 수 있습니다. 즉, 측정은 앱에서만 수행하고, 조회는 웹에서도 가능하도록 구성하는 방식이 적절하다고 판단했습니다.

 

Flutter에서는 kIsWeb 등을 이용해 실행 환경을 구분할 수 있기 때문에, 만보기 센서 연동은 앱 환경에서만 동작하도록 분기 처리할 수 있습니다.

if (!kIsWeb) {
  // Android 앱에서만 만보기 센서 실행
}

 

두 가지로 나뉜 만보기 기능

처음에는 "만보기 기능" 하나로 생각했지만, 실제로는 성격이 다른 두 기능으로 나눌 필요가 있었습니다.

  1. 캐릭터 페이지의 오늘 걸음 수
  2. 지도 페이지의 러닝/산책 경로 기록

캐릭터 페이지의 만보기는 오늘 하루 동안 얼마나 걸었는지를 보여주는 기능입니다. 

반면 지도 페이지의 러닝 기록은 특정 시간 동안 어디를 걸었고, 그 경로와 당시 걸음 수가 어땠는지 보여주는 기능입니다.

 

그래서 같은 만보기처럼 보여도 저장 방식과 API 구조를 분리하는 것이 더 적절하다고 판단했습니다.

 

앞으로 추가할 API

앞서 언급했듯이 만보기에 대해서 외에도 지도 ..뭐뭐를 위해서 추가할 예정이며 이번 편은 캐릭터 페이지의 오늘 걸음 수에 대한 글만을 정리할 예정입니다.

기능 추천 패키지 역할
만보기/걸음 수 pedometer 또는 health 걸음 수 측정
현재 위치/이동 경로 geolocator GPS 좌표 수집
지도 표시 google_maps_flutter 지도, 마커, Polylien 표시
백그라운드 기록 flutter_foreground_task 또는 flutter_background_geolocation 앱이 내려가도 위치/걸음 기록 유지

 

캐릭터 페이지용 일일 만보기 API

캐릭터 페이지에서는 오늘 걸음수를 보여줘야 합니다.

오늘의 걸음 수 / 목표 걸음수(보통 10,000으로 설정 예정)

해당 기능은 하루 단위로 관리하는 것이 좋기에 DailyStep 구조를 사용하겠습니다.

 

DailyStep
- user_id
- step_date
- step_count
- goal_steps
- created_at
- updated_at

여기서 중요한 점은 매일 00시에 DB 값을 직접 0으로 초기화하지 않는다는 점입니다. 대신 날짜 기준으로 조회합니다.

2026-05-26 → 2026-05-26의 DailyStep 조회
2026-05-27 → 2026-05-27의 DailyStep 조회

즉, 날짜가 바뀌면 자연스럽게 오늘 걸음 수가 0부터 시작하는 구조입니다.

 

구현하기

저는 안드로이드 앱을 기준으로 만보기 기능을 구현할 예정이기에 바로 백엔드 API부터 만들기보다는 Flutter에서 실제 기기 센서값이 정상적으로 들어오는지 먼저 확인하기로 했습니다.

 

만보기 기능은 일반적인 CRUD 기능과 달리 사용자의 기기 센서, 권한, 실행 환경에 영향을 많이 받습니다. 따라서 백엔드에 저장 구조를 먼저 만들기 전에 프론트엔드에서 다음 항목들을 먼저 검증할 필요가 있습니다.

 

1. 실제 Android 기기에서 걸음 수가 측정되는가?
2. 신체 활동 권한 요청이 정상적으로 동작하는가?
3. 위치 좌표가 정상적으로 수집되는가?
4. 수집된 좌표를 리스트 형태로 관리할 수 있는가?
5. 이후 서버로 전송할 데이터 형태를 만들 수 있는가?

 

이 과정을 통해 실제 앱 환경에서 만보기 기능이 동작하는지 확인한 뒤, 그 결과를 바탕으로 백엔드 API와 DB 구조를 설계하고 프론트엔드에 적용하는 순서로 진행했습니다.

프론트엔드 테스트 → 백엔드 API 구현 서버 배포 및 API 확인→캐릭터 페이지 연동

 

프론트엔드 테스트하기

먼저 Flutter에서 만보기와 위치 정보가 정상적으로 수집되는지 확인하기 위해 임시 데이터 페이지를 만들었습니다. 이 테스트 페이지의 목적은 최종 UI를 만드는 것이 아닌 실제 Android 기기에서 센서 데이터가 들어오는지 확인하는 것이었습니다.

 

1. pubspec.yaml 설정하기

pedometer: 4.1.1 #기기의 걸음 수 센서 값 수신
geolocator: 10.1.1 # 현재 위치 및 위치 변환 감지
google_maps_flutter: 2.3.0 # 지도 표시 및 경로 시각화 테스트
permission_handler: 10.4.5 #신체 활동 권한 요청

 

2. AndroidManifest.xml에 권한 요청

  <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

 

3. 테스트 페이지 작성

추후 지도에서의 만보기도 같이 추가할 예정이기에 테스트 페이지에서는 다음 정보를 화면에 표시했습니다.

  • 현재 걸음 수
  • 현재 위치 위도/경도
  • 수집된 좌표 개수
  • 측정 상태

처음에는 지도까지 함께 표시하려고 했지만, Google Map 발급과 함께 초기화 과정에서 문제가 발생할 수 있기에 일단은 제거하고, 걸음 수와 좌표가 들어오는지 먼저 확인했습니다.

 

신체 활동 권한 요청

Future<bool> requestActivityPermission() async {
  final status = await Permission.activityRecognition.status;

  if (status.isGranted) {
    return true;
  }

  final result = await Permission.activityRecognition.request();

  if (result.isGranted) {
    return true;
  }

  return false;
}

이 코드는 사용자가 이미 권한을 허용했다면 바로 true를 반환하고, 아직 허용하지 않았다면 권한 요청 창을 띄웁니다.

이 과정을 추가한 뒤에야 Pedometer.stepCountStream에서 걸음 수가 정상적으로 들어오는 것을 확인할 수 있었습니다.

 

걸음 수 측정

StreamSubscription<StepCount>? _stepSubscription;

int? startStepCount;
int currentSteps = 0;

_stepSubscription = Pedometer.stepCountStream.listen(
  (StepCount event) {
    startStepCount ??= event.steps;

    setState(() {
      currentSteps = event.steps - startStepCount!;
    });

    debugPrint('시작 기준 걸음 수: $startStepCount');
    debugPrint('현재 운동 걸음 수: $currentSteps');
  },
  onError: (error) {
    debugPrint('걸음 수 측정 오류: $error');
  },
);

권한 확인 후 Pedometer.stepCountStream을 구독했습니다.

여기서 중요한 점은 event.steps가 "이번 측정에서 걸은 걸음 수"가 아니라, 거기 기준 누적 걸음 수처럼 들어올 수 있다는 점입니다.

 

그래서 측정을 시작한 순간의 값을 startStepCount로 저장하고, 이후 값에서 빼는 방식으로 현재 측정 중 걸음 수를 계산했습니다.

현재 측정 걸음 수 = 현재 센서 걸음 수 -  시작 시점 센서 걸음 수

 

위치 좌표 수집

StreamSubscription<Position>? _positionSubscription;

Position? currentPosition;
final List<LatLng> routePoints = [];

const LocationSettings locationSettings = LocationSettings(
  accuracy: LocationAccuracy.high,
  distanceFilter: 5,
);

_positionSubscription = Geolocator.getPositionStream(
  locationSettings: locationSettings,
).listen(
  (Position position) {
    final point = LatLng(
      position.latitude,
      position.longitude,
    );

    setState(() {
      currentPosition = position;
      routePoints.add(point);
    });

    debugPrint('위치: ${position.latitude}, ${position.longitude}');
  },
  onError: (error) {
    debugPrint('위치 측정 오류: $error');
  },
);

distanceFilter: 5는 사용자가 약 5m 정도 이동했을 때 위치 업데이트를 받도록 설정한 값입니다.

이렇게 수집한 좌표들은 이후 지도 페이지에서 마커와 선으로 경로를 표시할 때 사용할 수 있습니다.

 

백엔드 API 구현

프론트엔드 테스트를 통해 실제 Android 기기에서 걸음 수와 위치 좌표를 수집할 수 있음을 확인했습니다. 이후에는 이 데이터를 서버에 저장하고, 캐릭터 페이지에서 다시 조회할 수 있도록 백엔드 API를 구현했습니다.

 

필요한 데이터 구조

캐릭터 페이지의 만보기는 사용자별로 하루에 하나의 기록만 있으면 됩니다. 따라서 DailStep 엔티티는 다음 정보를 갖도록 설계했습니다.

DailyStep
- user
- stepDate
- stepCount
- goalSteps
- createdAt
- updatedAt

여기서 stepDate를 둔 이유는 매일 00시에 데이터를 직접 초기화하지 않고, 날짜 기준으로 오늘의 데이터를 조회하기 위해서입니다.

또한 한 사용자에게 같은 날짜의 데이터가 여러 개 생기면 오늘 걸음 수가 중복될 수 있기 때문에, user_id + step_date 조합은 유니크하게 관리하는 것이 좋습니다.

 

DTO는 크게 두 개만 필요했습니다.

StepUpdateRequest
→ 앱에서 서버로 현재 걸음 수를 보낼 때 사용

TodayStepResponse
→ 캐릭터 페이지에 오늘 걸음 수와 목표 걸음 수를 내려줄 때 사용

 

서비스 로직 작성

해당 서비스 로직에서는 담당하는 역할은 크게 두 가지입니다.

1. 앱에서 전달한 오늘 걸음 수를 저장하거나 갱신한다.

2. 캐릭터 페이지에 표시할 오늘 걸음 수를 조회한다.

@Service
@RequiredArgsConstructor
public class DailyStepService {

    private final DailyStepRepository dailyStepRepository;
    private final UserRepository userRepository;

    @Transactional
    public TodayStepResponse updateTodaySteps(
            StepUpdateRequest request,
            Authentication authentication
    ) {
        String email = authentication.getName();

        User user = userRepository.findByEmail(email)
                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다."));

        LocalDate today = LocalDate.now();

        DailyStep dailyStep = dailyStepRepository
                .findByUserIdAndStepDate(user.getId(), today)
                .orElseGet(() -> DailyStep.create(user, today, 0));

        dailyStep.updateStepCount(request.getStepCount());

        DailyStep saved = dailyStepRepository.save(dailyStep);

        return new TodayStepResponse(
                saved.getStepCount(),
                saved.getGoalSteps()
        );
    }

    @Transactional(readOnly = true)
    public TodayStepResponse getTodaySteps(Authentication authentication) {
        String email = authentication.getName();

        User user = userRepository.findByEmail(email)
                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다."));

        LocalDate today = LocalDate.now();

        return dailyStepRepository.findByUserIdAndStepDate(user.getId(), today)
                .map(dailyStep -> new TodayStepResponse(
                        dailyStep.getStepCount(),
                        dailyStep.getGoalSteps()
                ))
                .orElseGet(() -> new TodayStepResponse(0, 10000));
    }
}

로그인한 사용자를 확인한 후 → 오늘 날짜 기준 DailyStep 조회 → 있으면 걸음 수 갱신 → 없으면 새 DailyStep 생성 → 캐릭터 페이지에 필요한 todaySteps, goalSteps 반환

 

이후 프론트엔드와 연동하면서 캐릭터 페이지에서 실제 걸음수가 표시되고, 사용자가 걸을 때마다 숫자가 증가하는 것을 확인할 수 있었습니다.


이번 시간에는 만보기 API를 연동하고 직접 센서가 작동하는지 확인하는 시간을 가졌습니다. 원래라면 현재 언급한 모든 기능을 넣은 후에 다음 기능으로 넘어갈 예정이였습니다만 그렇게되면 앱스토어에 배포하는데까지 오랜 시간이 걸릴 것으로 판단되어 만보기 API는 초기 기획했던 현재의 정도로만 넣은 후 앱스토어에 배포한 다음 추가할 예정입니다.

아마 다음 기능으로 친구 추가를 통해 웹소켓 기능을 더욱 편하게 만들도록 할 것 같아요. 몇 가지 오류나 UI/UX측면에서 불편한 점까지 수정하면 앱스토어에 배포도 할 것 같습니다. 다음 시간에는 또 다른 주제의 개발일지로 돌아오며 만보기 API도 꼭 끝마치도록 하겠습니다. 감사합니다.