본문 바로가기
Flutter/Study

플러터에서 조깅 앱을 만들 때 백그라운드에서 GPS 위치를 계속 저장하는 방법

by Maccrey Coding 2025. 2. 12.
반응형

 

조깅 앱을 개발할 때 가장 중요한 기능 중 하나는 사용자가 스마트폰 화면을 꺼두거나 다른 앱을 사용하더라도 GPS 위치를 지속적으로 저장하는 것입니다.

하지만 안드로이드에서는 배터리 최적화를 위해 백그라운드 작업을 일정 시간이 지나면 자동으로 종료시킵니다.

이를 해결하려면 Foreground Service를 활용해야 합니다.

🚀 Foreground Service란?

Foreground Service는 사용자에게 지속적으로 실행 중임을 알리는 서비스입니다.

예를 들어, 조깅 앱이 실행 중일 때 상태바(Notification)에 "조깅 기록 중"과 같은 메시지를 띄우면, 해당 서비스는 시스템이 강제 종료하지 않습니다.

🎯 WorkManager 대신 Foreground Service 사용하기

WorkManager는 주기적인 작업을 수행하는 데 적합하지만, GPS 데이터를 실시간으로 저장하는 조깅 앱에는 적합하지 않습니다.

대신 Foreground Service를 활용하면 앱이 종료되지 않고 지속적으로 위치를 추적할 수 있습니다.

📌 구현 방식: MVVM 패턴 + Riverpod 활용

조깅 앱을 MVVM 패턴으로 개발하며, 상태 관리는 Riverpod을 사용합니다.

코드 예제: MVVM 패턴으로 조깅 앱 구현하기

1️⃣ 필요한 패키지 추가

dependencies:
  flutter:
    sdk: flutter
  flutter_riverpod: ^2.3.0
  geolocator: ^10.0.0
  hive: ^2.2.3
  hive_flutter: ^1.1.0
  permission_handler: ^11.0.1
  flutter_local_notifications: ^16.1.0
  android_foreground_service: ^1.0.0

2️⃣ Foreground Service 설정

안드로이드에서 Foreground Service를 사용하려면 AndroidManifest.xml에 추가 설정이 필요합니다.

<manifest>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <application>
        <service
            android:name=".ForegroundLocationService"
            android:foregroundServiceType="location"/>
    </application>
</manifest>

3️⃣ Foreground Service 구현

ForegroundLocationService.kt 파일을 만들고 Foreground Service를 정의합니다.

package com.example.joggingapp

import android.app.*
import android.content.Intent
import android.location.Location
import android.os.IBinder
import android.os.Looper
import androidx.core.app.NotificationCompat
import com.google.android.gms.location.*

class ForegroundLocationService : Service() {

    private lateinit var fusedLocationClient: FusedLocationProviderClient
    private lateinit var locationCallback: LocationCallback

    override fun onCreate() {
        super.onCreate()
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)

        locationCallback = object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult) {
                for (location in locationResult.locations) {
                    saveLocation(location)
                }
            }
        }

        val notification = createNotification()
        startForeground(1, notification)

        val locationRequest = LocationRequest.create().apply {
            interval = 5000 // 5초마다 위치 업데이트
            fastestInterval = 2000
            priority = LocationRequest.PRIORITY_HIGH_ACCURACY
        }

        fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
    }

    private fun saveLocation(location: Location) {
        // Hive 또는 다른 저장소에 위치 데이터 저장
    }

    private fun createNotification(): Notification {
        val channelId = "jogging_channel"
        val channel = NotificationChannel(channelId, "조깅 위치 추적", NotificationManager.IMPORTANCE_LOW)
        getSystemService(NotificationManager::class.java).createNotificationChannel(channel)

        return NotificationCompat.Builder(this, channelId)
            .setContentTitle("조깅 앱 실행 중")
            .setContentText("위치 추적 중...")
            .setSmallIcon(R.drawable.ic_location)
            .build()
    }

    override fun onBind(intent: Intent?): IBinder? = null
}

설명

  • Foreground Service를 실행하면 알림(Notification)이 표시되므로 시스템이 강제로 앱을 종료하지 않습니다.
  • requestLocationUpdates()를 통해 5초마다 GPS 데이터를 업데이트하고 Hive에 저장합니다.

4️⃣ 플러터에서 Foreground Service 실행

Foreground Service를 실행하려면 MethodChannel을 활용합니다.

import 'package:flutter/services.dart';

class ForegroundServiceController {
  static const MethodChannel _channel = MethodChannel('foreground_service');

  static Future<void> startService() async {
    await _channel.invokeMethod('startForegroundService');
  }

  static Future<void> stopService() async {
    await _channel.invokeMethod('stopForegroundService');
  }
}

설명

  • startService()를 호출하면 Foreground Service가 실행됩니다.

5️⃣ 위치 데이터 MVVM 패턴으로 관리

📍 Model (데이터 저장)

import 'package:hive/hive.dart';

part 'location_model.g.dart';

@HiveType(typeId: 0)
class LocationModel {
  @HiveField(0)
  final double latitude;
  @HiveField(1)
  final double longitude;
  @HiveField(2)
  final DateTime timestamp;

  LocationModel({required this.latitude, required this.longitude, required this.timestamp});
}

 

📍 ViewModel (데이터 관리)

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:geolocator/geolocator.dart';
import 'package:hive/hive.dart';

final locationProvider = StateNotifierProvider<LocationNotifier, List<LocationModel>>((ref) {
  return LocationNotifier();
});

class LocationNotifier extends StateNotifier<List<LocationModel>> {
  LocationNotifier() : super([]);

  Future<void> addLocation(Position position) async {
    final location = LocationModel(
      latitude: position.latitude,
      longitude: position.longitude,
      timestamp: DateTime.now(),
    );

    final box = await Hive.openBox<LocationModel>('locationBox');
    box.add(location);

    state = [...state, location];
  }
}

설명

  • locationProvider를 통해 위치 데이터를 관리하고 Hive에 저장합니다.

 

조깅 앱이 백그라운드에서 강제 종료되지 않도록 Foreground Service를 사용하여 지속적으로 GPS 데이터를 저장할 수 있습니다. WorkManager보다 실시간 데이터 처리에 적합하며, MVVM 패턴과 Riverpod을 활용하면 구조적으로 깔끔하게 관리할 수 있습니다.

 

📝 정리

  • 백그라운드에서 GPS를 계속 실행하려면? → Foreground Service 사용!
  • WorkManager를 쓰면 안 되는 이유? → 실시간 GPS 데이터 추적에 적합하지 않음.
  • MVVM 패턴으로 상태를 관리하려면? → Riverpod 활용.

이제 이 방법을 활용하여 배터리 최적화에도 영향을 덜 받으면서 백그라운드에서도 안정적으로 GPS 데이터를 저장하는 조깅 앱을 만들어보세요! 🚀

 

구독!! 공감과 댓글,

광고 클릭은 저에게 큰 힘이 됩니다.

 

Starting Google Play App Distribution! "Tester Share" for Recruiting 20 Testers for a Closed Test.

 

Tester Share [테스터쉐어] - Google Play 앱

Tester Share로 Google Play 앱 등록을 단순화하세요.

play.google.com

반응형