importPlaces static method
Imports places from a JSON file selected by the user.
Returns an ImportResult containing validated places and any errors. Validation rules:
- lat and lng are REQUIRED - items without these are skipped
- id: auto-generated if missing (UUID)
- timestamp: uses current time if missing
- note: defaults to empty string if missing
- address: defaults to "Unknown Location" if missing.
Implementation
static Future<ImportResult> importPlaces() async {
final result = ImportResult();
try {
final pickResult = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['json'],
withData: true,
);
if (pickResult == null || pickResult.files.isEmpty) {
result.cancelled = true;
return result;
}
final file = pickResult.files.first;
if (file.bytes == null) {
result.errors.add('Failed to read file data');
return result;
}
final jsonString = utf8.decode(file.bytes!);
final dynamic decoded;
try {
decoded = jsonDecode(jsonString);
} catch (e) {
result.errors.add('Invalid JSON format: $e');
return result;
}
if (decoded is! List) {
result.errors.add('Expected a JSON array of place objects');
return result;
}
final uuid = const Uuid();
for (int i = 0; i < decoded.length; i++) {
final item = decoded[i];
if (item is! Map<String, dynamic>) {
result.errors.add('Item ${i + 1}: Not a valid object, skipped');
result.skippedCount++;
continue;
}
// Validate required fields: lat and lng.
final lat = item['lat'];
final lng = item['lng'];
if (lat == null || lng == null) {
result.errors.add(
'Item ${i + 1}: Missing required lat/lng fields, skipped',
);
result.skippedCount++;
continue;
}
if (lat is! num || lng is! num) {
result.errors.add('Item ${i + 1}: lat/lng must be numbers, skipped');
result.skippedCount++;
continue;
}
// Validate lat/lng ranges.
if (lat < -90 || lat > 90) {
result.errors.add(
'Item ${i + 1}: lat must be between -90 and 90, skipped',
);
result.skippedCount++;
continue;
}
if (lng < -180 || lng > 180) {
result.errors.add(
'Item ${i + 1}: lng must be between -180 and 180, skipped',
);
result.skippedCount++;
continue;
}
// Auto-complete missing optional fields.
// Note: address field is IGNORED from JSON - will be auto-generated via geocoding.
final place = Place(
id: (item['id'] as String?) ?? uuid.v4(),
lat: lat.toDouble(),
lng: lng.toDouble(),
note: (item['note'] as String?) ?? '',
timestamp:
(item['timestamp'] as String?) ??
DateTime.now().toUtc().toIso8601String(),
address: null, // Address will be fetched via reverse geocoding.
isLocal: false,
);
result.places.add(place);
}
return result;
} catch (e) {
result.errors.add('Unexpected error: $e');
return result;
}
}