A Flutter autocomplete TextField widget and controller for the LocationIQ Autocomplete API.
- Ready-to-use autocomplete TextField with an overlay dropdown
- Debounced requests to reduce API calls
- In-memory LRU cache for repeated queries
- Rate-limit handling (HTTP 429) with cooldown state
- Customizable UI builders (loading, empty, error, rate-limited, option item)
- Reusable controller (share one controller across screens/widgets)
- Optional
FormFieldwrapper for validation and forms
This package is not affiliated with, endorsed by, or sponsored by LocationIQ.
LocationIQ is a trademark of its respective owner.
Use of the LocationIQ API is subject to LocationIQ’s Terms of Service. You must use your own API key.
Add the package from pub.dev:
dependencies:
locationiq_autocomplete_flutter: ^0.1.0Then run:
flutter pub getImport:
import 'package:locationiq_autocomplete_flutter/locationiq_autocomplete_flutter.dart';Create an API key from your LocationIQ account, then pass it to the API client. Do not commit production API keys into your app repository.
import 'package:flutter/material.dart';
import 'package:locationiq_autocomplete_flutter/locationiq_autocomplete_flutter.dart';
class DemoPage extends StatefulWidget {
const DemoPage({super.key});
@override
State<DemoPage> createState() => _DemoPageState();
}
class _DemoPageState extends State<DemoPage> {
late final LocationIQAutocompleteApi _api;
@override
void initState() {
super.initState();
_api = LocationIQAutocompleteApi(
apiKey: 'YOUR_LOCATIONIQ_KEY',
userAgent: 'com.example.myapp/1.0',
);
}
@override
void dispose() {
_api.close(); // Close only if you created this instance.
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('LocationIQ Autocomplete')),
body: Padding(
padding: const EdgeInsets.all(16),
child: LocationIQAutocompleteField(
api: _api,
onSelected: (result) {
// Use result.lat, result.lon, result.displayName, etc.
debugPrint('Selected: ${result.displayName} (${result.lat}, ${result.lon})');
},
),
),
);
}
}You can configure request parameters using LocationIQAutocompleteRequest:
LocationIQAutocompleteField(
api: _api,
request: const LocationIQAutocompleteRequest(
limit: 8,
countrycodes: 'id', // e.g. "id" or "id,sg"
normalizecity: 1,
acceptLanguage: 'id',
// viewbox: '106.6,-6.1,107.0,-6.4', // left,top,right,bottom
// bounded: 1,
// tag: 'place',
),
onSelected: (r) {},
)LocationIQAutocompleteField(
api: _api,
optionBuilder: (context, option) {
return ListTile(
title: Text(option.title),
subtitle: option.subtitle.isEmpty ? null : Text(option.subtitle),
trailing: Text('${option.lat.toStringAsFixed(5)}, ${option.lon.toStringAsFixed(5)}'),
);
},
onSelected: (r) {},
)LocationIQAutocompleteField(
api: _api,
loadingBuilder: (_) => const Padding(
padding: EdgeInsets.all(12),
child: Text('Searching...'),
),
emptyBuilder: (_) => const Padding(
padding: EdgeInsets.all(12),
child: Text('No results'),
),
errorBuilder: (_, err) => Padding(
padding: const EdgeInsets.all(12),
child: Text('Error: $err'),
),
rateLimitedBuilder: (_, until) => Padding(
padding: const EdgeInsets.all(12),
child: Text(
until == null ? 'Rate limited. Please try again.' : 'Rate limited. Try again later.',
),
),
onSelected: (r) {},
)If you want to reuse the same controller instance (for caching or cross-widget sharing), provide a controller:
class PageWithController extends StatefulWidget {
const PageWithController({super.key});
@override
State<PageWithController> createState() => _PageWithControllerState();
}
class _PageWithControllerState extends State<PageWithController> {
late final LocationIQAutocompleteApi _api;
late final LocationIQAutocompleteController _controller;
@override
void initState() {
super.initState();
_api = LocationIQAutocompleteApi(apiKey: 'YOUR_LOCATIONIQ_KEY');
_controller = LocationIQAutocompleteController(
api: _api,
minChars: 3,
debounce: const Duration(milliseconds: 300),
cacheSize: 50,
rateLimitCooldown: const Duration(seconds: 3),
);
}
@override
void dispose() {
_controller.dispose();
_api.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return LocationIQAutocompleteField(
controller: _controller,
onSelected: (r) {},
);
}
}You can also clear cache manually:
_controller.clearCache();Use LocationIQAutocompleteFormField for form validation:
final _formKey = GlobalKey<FormState>();
Form(
key: _formKey,
child: LocationIQAutocompleteFormField(
api: _api,
onSelected: (r) {
// Save selection if needed.
},
validator: (value) {
if (value == null || value.trim().isEmpty) return 'Please select a location';
return null;
},
),
);- This package uses an in-memory cache (LRU). It does not persist results to disk.
- You must provide your own LocationIQ API key.
- Requests are sent to LocationIQ’s autocomplete endpoint. Usage is subject to LocationIQ’s limits and terms.
- If you pass a custom
http.ClientintoLocationIQAutocompleteApi, you are responsible for closing it.
See the example/ folder in the repository for a runnable sample app.
MIT License. See LICENSE.