flutter_places_sdk 0.1.0
flutter_places_sdk: ^0.1.0 copied to clipboard
Flutter plugin wrapping the native Google Places SDK (iOS & Android) with support for Places API (New). Works with API keys restricted by bundle ID / SHA-1 fingerprint.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_places_sdk/flutter_places_sdk.dart';
void main() => runApp(const ExampleApp());
/// Fill in with a key that has **Places API (New)** enabled. The key may
/// be restricted by iOS bundle ID / Android package + SHA-1 — that's the
/// whole point of this plugin.
///
/// Run with:
/// ```
/// flutter run --dart-define=PLACES_API_KEY=AIza…
/// ```
const _apiKey = String.fromEnvironment('PLACES_API_KEY');
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) => MaterialApp(
title: 'flutter_places_sdk example',
theme: ThemeData(colorSchemeSeed: Colors.deepPurple, useMaterial3: true),
home: const HomePage(),
);
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final _places = FlutterPlacesSdk(_apiKey);
final _controller = TextEditingController();
Timer? _debounce;
List<AutocompletePrediction> _predictions = const [];
Place? _selected;
String? _error;
@override
void dispose() {
_debounce?.cancel();
_controller.dispose();
super.dispose();
}
void _onChanged(String value) {
_debounce?.cancel();
_debounce = Timer(const Duration(milliseconds: 300), () async {
if (value.trim().length < 3) {
setState(() => _predictions = const []);
return;
}
try {
final predictions = await _places.findAutocompletePredictions(value);
if (!mounted) return;
setState(() {
_predictions = predictions;
_error = null;
});
} on PlacesException catch (e) {
if (!mounted) return;
setState(() => _error = '${e.code}: ${e.message}');
}
});
}
Future<void> _select(AutocompletePrediction prediction) async {
try {
final place = await _places.fetchPlace(
prediction.placeId,
fields: const [
PlaceField.id,
PlaceField.name,
PlaceField.address,
PlaceField.location,
],
);
if (!mounted) return;
setState(() {
_selected = place;
_error = null;
});
} on PlacesException catch (e) {
if (!mounted) return;
setState(() => _error = '${e.code}: ${e.message}');
}
}
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text('flutter_places_sdk')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextField(
controller: _controller,
onChanged: _onChanged,
decoration: const InputDecoration(
labelText: 'Search places',
border: OutlineInputBorder(),
),
),
if (_apiKey.isEmpty)
const Padding(
padding: EdgeInsets.only(top: 8),
child: Text(
'Pass PLACES_API_KEY via --dart-define to enable requests.',
style: TextStyle(color: Colors.orange),
),
),
if (_error != null)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(_error!, style: const TextStyle(color: Colors.red)),
),
const SizedBox(height: 12),
Expanded(child: _PredictionsList(_predictions, _select)),
if (_selected != null) _SelectedPlaceCard(_selected!),
],
),
),
);
}
class _PredictionsList extends StatelessWidget {
const _PredictionsList(this.predictions, this.onTap);
final List<AutocompletePrediction> predictions;
final void Function(AutocompletePrediction) onTap;
@override
Widget build(BuildContext context) => ListView.builder(
itemCount: predictions.length,
itemBuilder: (context, index) {
final p = predictions[index];
return ListTile(
title: Text(p.primaryText),
subtitle: Text(p.secondaryText),
onTap: () => onTap(p),
);
},
);
}
class _SelectedPlaceCard extends StatelessWidget {
const _SelectedPlaceCard(this.place);
final Place place;
@override
Widget build(BuildContext context) => Card(
margin: const EdgeInsets.only(top: 8),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
place.name ?? '—',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 4),
Text(place.formattedAddress ?? ''),
if (place.latLng != null)
Text('${place.latLng!.lat}, ${place.latLng!.lng}'),
],
),
),
);
}