ug_locations 0.3.0
ug_locations: ^0.3.0 copied to clipboard
Offline Flutter/Dart library for Uganda's administrative-unit hierarchy: village, parish, subcounty, and district lookup with fuzzy search, backed by SQLite.
ug_locations #
A fast, offline Flutter/Dart library for Uganda's administrative hierarchy, forked from ug_locations, with data sourced from the uganda npm package by kakandemanwell (browsable at uganda-omega.vercel.app).
Search villages, get complete administrative paths, and traverse village → parish → subcounty → county → district — fully offline via a bundled SQLite database.
Features #
- Fully offline — bundled SQLite database, no network calls
- Smart search — substring/prefix matching with relevance ranking
- Complete hierarchy traversal across all administrative levels
- Ready-made widgets —
LocationPicker(cascading selector) andLocationSearchField(autocomplete) — no UI to hand-roll - Full null-safety, typed models
Installation #
flutter pub add ug_locations
Migrating from 0.1.x:
UgandaLocation.constituencywas renamed tocountyin 0.2.0 and now comes from a different data source (a county, not an electoral constituency) — update field access, there's no compatibility shim.
Quick Start #
import 'package:ug_locations/ug_locations.dart';
Future<void> main() async {
final ug = await UgandaLocations.getInstance();
final location = await ug.getLocationByVillage('KASAMBYA I');
// UgandaLocation(village: KASAMBYA I, parish: KATEREIGA,
// subcounty: BUHANIKA, county: BUGAHYA COUNTY, district: HOIMA,
// region: WESTERN, subRegion: BUNYORO)
print(await ug.getPath('KASAMBYA I'));
// "HOIMA → BUHANIKA → KATEREIGA → KASAMBYA I"
}
Platform setup #
-
Android / iOS: works out of the box.
-
Desktop (Linux/macOS/Windows) or
dart test:sqfliteneeds the FFI implementation. Initialize once before calling any method:import 'package:sqflite_common_ffi/sqflite_ffi.dart'; void main() { sqfliteFfiInit(); databaseFactory = databaseFactoryFfi; runApp(const MyApp()); } -
Web: works out of the box — no setup needed.
sqflitehas no built-in web backend, so on web the package transparently switches to asqlite3-wasm backend instead, loading the bundled database into an in-memory filesystem on each page load.
Usage #
// Districts and hierarchy traversal
final districts = await ug.getDistricts();
final subcounties = await ug.getSubcountiesInDistrict('HOIMA');
final parishes = await ug.getParishesInSubcounty('HOIMA', 'BUHANIKA');
final villages = await ug.getVillagesInParish('HOIMA', 'BUHANIKA', 'KATEREIGA');
final parent = await ug.getParent('KASAMBYA I');
// UgandaLocationParent(parish: KATEREIGA, subcounty: BUHANIKA, district: HOIMA)
// Search — ranked, exact/prefix matches first
final results = await ug.search('KABANDA', limit: 5);
for (final loc in results) {
print('${loc.village} (${loc.district})');
}
Common patterns this API supports: cascading district → subcounty → parish → village selectors, and village-name input validation via getLocationByVillage(name) != null. See example/lib/main.dart for a full working Flutter app — it has a "Bundled widgets" tab demonstrating both widgets below, alongside hand-rolled equivalents built directly on the API.
Region and sub-region lookups #
Every UgandaLocation also carries region and subRegion, and three lookup methods let you drive a region-first cascade if you want one:
final regions = await ug.getRegions(); // WESTERN, CENTRAL, EASTERN, NORTHERN
final subRegions = await ug.getSubRegionsInRegion('WESTERN'); // BUNYORO, ANKOLE, ...
final districts = await ug.getDistrictsInSubRegion('BUNYORO'); // HOIMA, ...
Widgets #
Two ready-made Flutter widgets ship alongside the lookup API, so you don't have to hand-roll cascading dropdowns or an autocomplete field yourself. Both are exported from the same package:ug_locations/ug_locations.dart import used above — no separate import needed. Both open the shared UgandaLocations instance automatically (via UgandaLocations.getInstance()); pass ug: someInstance to inject a specific instance instead, e.g. in tests.
LocationPicker #
A cascading District → Subcounty → Parish → Village selector. Four DropdownButtonFormFields, each populated from the previous selection; onSelected fires once a village is chosen, with the full resolved UgandaLocation.
LocationPicker(
onSelected: (location) => print(location.village),
)

Pass includeRegionHierarchy: true to prepend Region and Sub-region dropdowns above District, narrowing the District list to the chosen sub-region:
LocationPicker(
includeRegionHierarchy: true,
onSelected: (location) => print(location.village),
)

Pass initialLocation to pre-select every dropdown, e.g. when editing a record that already has a saved location. Each level's options are loaded so the seeded value is valid; onSelected is not called for it, since the caller already has the value:
LocationPicker(
onSelected: (location) => print(location.village),
initialLocation: existingLocation, // a UgandaLocation
)
LocationSearchField #
A text field that searches villages, parishes, subcounties, and districts as the user types (via UgandaLocations.search), showing a ranked suggestions list to pick from.
LocationSearchField(
onSelected: (location) => print(location.village),
limit: 5, // suggestions fetched per keystroke, default 3
)

Additional params for common cases:
LocationSearchField(
onSelected: (location) => print(location.village),
// Seed the field with a saved value, e.g. when editing an existing record.
initialValue: const TextEditingValue(text: 'KASAMBYA I'),
// Manual fallback for free text that doesn't match any suggestion
// (real gap for rural/offline data not yet in the dataset).
onTextChanged: (text) => print('typed: $text'),
// Delay searches until typing pauses, instead of querying on every
// keystroke. Defaults to null (no debounce).
debounceDuration: const Duration(milliseconds: 300),
)
Migrating from earlier versions: on both widgets, the
locations: Future<UgandaLocations>?param is deprecated in favor ofug: UgandaLocations?, which takes an already-resolved instance instead of a future — a more conventional shape for dependency injection.locationsstill works but will be removed in a future release.
API Reference #
| Method | Returns | Description |
|---|---|---|
UgandaLocations.getInstance() |
Future<UgandaLocations> |
Opens (and caches) the shared database instance |
getDistricts() |
Future<List<String>> |
Returns all 146 districts |
getRegions() |
Future<List<String>> |
Returns all 4 regions |
getSubRegionsInRegion(region) |
Future<List<String>> |
Sub-regions in a region |
getDistrictsInSubRegion(subRegion) |
Future<List<String>> |
Districts in a sub-region |
getLocationByVillage(village) |
Future<UgandaLocation?> |
Full hierarchy for a village |
getPath(village) |
Future<String?> |
Formatted path: "District → Subcounty → Parish → Village" |
search(query, {limit = 50}) |
Future<List<UgandaLocation>> |
Search across all levels, ranked by relevance |
getSubcountiesInDistrict(district) |
Future<List<String>> |
Subcounties in a district |
getParishesInSubcounty(district, subcounty) |
Future<List<String>> |
Parishes in a subcounty |
getVillagesInParish(district, subcounty, parish) |
Future<List<String>> |
Villages in a parish |
getParent(village) |
Future<UgandaLocationParent?> |
Parent parish/subcounty/district of a village |
Types #
class UgandaLocation {
final String village;
final String parish;
final String subcounty;
final String? county;
final String district;
final String? region;
final String? subRegion;
}
class UgandaLocationParent {
final String parish;
final String subcounty;
final String district;
}
Data #
146 districts, 52,000+ unique villages across 71,000+ administrative records, covering the full village → parish → subcounty → county → district → sub-region → region hierarchy. Sourced from the uganda npm package's dataset (also browsable at uganda-omega.vercel.app).
Data freshness: this replaces the package's original static snapshot of the July 2022 Electoral Commission list (145 districts, no region/sub-region data). Regenerating the bundled database from a newer source is a two-step, manual process — see skills/ug_locations/SKILL.md — there is still no automatic update mechanism. If you need data fresher than what's bundled here, check whether uganda/uganda-omega.vercel.app has since published an update.
Acknowledgments #
- Dart/Flutter port of the
ug-locationsnpm package by Natumanya Guy, reimplemented with a SQLite-backed storage layer. - Administrative-unit data from the
ugandanpm package by kakandemanwell (uganda-omega.vercel.app) — also a good source to check for more current data than what's bundled here.
Contributing #
Contributions welcome — please open a Pull Request.
License #
MIT