FlutterDartMobileGeolocationTutorial
Flutter & Dart IP Geolocation: Detect Country from IP in Mobile Apps
ApogeoAPI5 min read
Important: don't embed API keys in Flutter apps
Flutter app bundles are publicly inspectable — never put your API key directly in the Flutter/Dart code. The recommended pattern is: Flutter → your own backend endpoint → ApogeoAPI. Your backend holds the key and the Flutter app calls your endpoint.
Backend endpoint (Dart shelf / any server)
// server/geo_handler.dart (runs on your server, not in the app)
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shelf/shelf.dart';
final _cache = <String, Map<String, dynamic>>{};
Response geoHandler(Request request) async {
final ip = request.headers['x-forwarded-for']?.split(',').first.trim()
?? request.context['shelf.io.connection_info'].remoteAddress.address;
final subnet = ip.split('.').take(3).join('.');
if (_cache.containsKey(subnet)) return Response.ok(json.encode(_cache[subnet]!));
final res = await http.get(
Uri.parse('https://api.apogeoapi.com/v1/ip/$ip'),
headers: {'Authorization': 'Bearer ${const String.fromEnvironment("APOGEOAPI_KEY")}'},
);
final data = json.decode(res.body) as Map<String, dynamic>;
_cache[subnet] = data;
return Response.ok(json.encode(data), headers: {'Content-Type': 'application/json'});
}
Flutter service calling your backend
// lib/services/geo_service.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
class GeoData {
final String countryCode;
final String? city;
final String? currencyCode;
final String? timezone;
const GeoData({
required this.countryCode,
this.city,
this.currencyCode,
this.timezone,
});
factory GeoData.fromJson(Map<String, dynamic> j) => GeoData(
countryCode: j['country_code'] as String? ?? 'US',
city: j['city'] as String?,
currencyCode: j['currency_code'] as String?,
timezone: j['timezone'] as String?,
);
}
class GeoService {
static const _cacheKey = 'geo_country';
static const _cacheTtlKey = 'geo_ts';
static const _cacheTtl = Duration(hours: 1);
final String backendUrl;
const GeoService({required this.backendUrl});
Future<GeoData> detect() async {
// Check SharedPreferences cache first
final prefs = await SharedPreferences.getInstance();
final ts = prefs.getInt(_cacheTtlKey) ?? 0;
final cachedJson = prefs.getString(_cacheKey);
if (cachedJson != null &&
DateTime.now().millisecondsSinceEpoch - ts < _cacheTtl.inMilliseconds) {
return GeoData.fromJson(json.decode(cachedJson) as Map<String, dynamic>);
}
try {
final res = await http
.get(Uri.parse('$backendUrl/api/geo'))
.timeout(const Duration(seconds: 3));
if (res.statusCode == 200) {
final data = json.decode(res.body) as Map<String, dynamic>;
await prefs.setString(_cacheKey, json.encode(data));
await prefs.setInt(_cacheTtlKey, DateTime.now().millisecondsSinceEpoch);
return GeoData.fromJson(data);
}
} catch (_) {
// Network error — return default
}
return const GeoData(countryCode: 'US');
}
}
Using GeoData in a Flutter widget
// lib/screens/pricing_screen.dart
import 'package:flutter/material.dart';
import '../services/geo_service.dart';
class PricingScreen extends StatefulWidget {
const PricingScreen({super.key});
@override
State<PricingScreen> createState() => _PricingScreenState();
}
class _PricingScreenState extends State<PricingScreen> {
final _geo = const GeoService(backendUrl: 'https://api.yourapp.com');
GeoData? _geoData;
@override
void initState() {
super.initState();
_geo.detect().then((d) => setState(() => _geoData = d));
}
@override
Widget build(BuildContext context) {
final country = _geoData?.countryCode ?? 'US';
final currency = _geoData?.currencyCode ?? 'USD';
final (symbol, amount) = switch (currency) {
'USD' => ('$', 29),
'GBP' => ('£', 25),
'EUR' => ('€', 27),
'AUD' => ('A$', 45),
'BRL' => ('R$', 149),
_ => ('$', 29),
};
return Scaffold(
appBar: AppBar(title: const Text('Pricing')),
body: Center(
child: Column(children: [
Text('Your region: $country', style: Theme.of(context).textTheme.headlineSmall),
Text('$symbol$amount / month', style: Theme.of(context).textTheme.displaySmall),
]),
),
);
}
}
pubspec.yaml dependencies
dependencies:
flutter:
sdk: flutter
http: ^1.2.0
shared_preferences: ^2.2.2
Resources
- ApogeoAPI IP Geolocation — free tier, JSON response
- http package
- shared_preferences
Try ApogeoAPI free
1,000 requests/month forever. 14-day full-access trial. No credit card.
Get your free API key