ubhub_maps 0.2.1
ubhub_maps: ^0.2.1 copied to clipboard
Official Dart SDK for the UBHUB Geospatial API, with typed geocoding, reverse geocoding, routing, and secure API-key authentication.
UBHUB Dart SDK #
ubhub_maps is the official Dart client for the UBHUB public Geospatial API.
Phase 1 provides geocoding, reverse geocoding, and routing. Despite the package
name, it does not render maps or provide navigation UI or offline maps.
Documentation: https://developers.ubhub.mn Publisher: developers.ubhub.mn API version: v1 · Package version: 0.2.1 · License: MIT
The default API origin is https://developers.ubhub.mn. Its live OpenAPI
contract was verified against the SDK on 2026-10-08. Authenticated live service
verification is pending; unit tests use mocked responses. TLS verification is
always enabled.
Installation #
dart pub add ubhub_maps
For Flutter:
flutter pub add ubhub_maps
Import the single supported entry point:
import 'package:ubhub_maps/ubhub_maps.dart';
Authentication #
final ubhub = UbHubGeo(apiKey: 'YOUR_API_KEY');
The client adds Authorization: Bearer <API_KEY> automatically. Empty keys and
keys containing whitespace are rejected before a request is made. Create keys
through the UBHUB developer console. Never commit keys to source control.
Keys embedded in Flutter, mobile, desktop, or browser applications are not secrets: applications can be inspected or reverse engineered. Use restricted keys, endpoint restrictions, quotas, and rate limits. Add application/package restrictions where the backend supports them; this SDK does not implement such restrictions itself. Use your own trusted backend for operations requiring a server-side secret. Environment variables help keep CLI/backend credentials out of source files; they do not make client-distributed credentials secret.
Geocoding #
import 'package:ubhub_maps/ubhub_maps.dart';
Future<void> main() async {
final ubhub = UbHubGeo(apiKey: 'YOUR_API_KEY');
try {
final result = await ubhub.geocoding.search('Sukhbaatar Square', limit: 5);
for (final place in result.features) {
print('${place.name}: ${place.address}');
print(place.location);
}
} on UbHubException catch (e) {
print('${e.message} Request: ${e.requestId}');
} finally {
ubhub.close();
}
}
search accepts a 2–200 character query and a limit from 1–20 (default 5).
The server performs normalization. Only q and limit are transmitted.
results and features refer to the same immutable list. An empty list is a
successful no-match response. This is not a GeoJSON FeatureCollection.
Reverse geocoding #
import 'package:ubhub_maps/ubhub_maps.dart';
Future<void> main() async {
final ubhub = UbHubGeo(apiKey: 'YOUR_API_KEY');
try {
final result = await ubhub.geocoding.reverse(
location: const GeoPoint(latitude: 47.9184, longitude: 106.9177),
);
for (final place in result.results) {
print('${place.address ?? place.name}: ${place.distanceMeters} metres');
}
} on UbHubException catch (e) {
print('${e.message} Request: ${e.requestId}');
} finally {
ubhub.close();
}
}
Reverse lookups use GET /v1/geocode with lat and lon. The current API
has no /reverse-geocode endpoint. Nullable address/distance fields remain
nullable; the SDK does not fabricate a match.
Routing #
import 'package:ubhub_maps/ubhub_maps.dart';
Future<void> main() async {
final ubhub = UbHubGeo(apiKey: 'YOUR_API_KEY');
try {
final result = await ubhub.routing.route(
origin: const GeoPoint(latitude: 47.9184, longitude: 106.9177),
destination: const GeoPoint(latitude: 47.9220, longitude: 106.9057),
mode: TravelMode.driving,
);
print('${result.route.distanceMeters} metres');
print('${result.route.durationSeconds} seconds');
print(result.route.geometry.toJson());
print(result.waypoints.first.location);
} on UbHubException catch (e) {
print('${e.message} Request: ${e.requestId}');
} finally {
ubhub.close();
}
}
Modes are TravelMode.driving (default) and TravelMode.walking. Geometry is a
GeoJSON LineString; waypoints preserve submitted and snapped coordinates.
There are no invented alternatives, instructions, profile, avoid, or language
options. Flutter consumers can hide this package's Route type if they also
import Flutter's navigation Route: import 'package:ubhub_maps/ubhub_maps.dart' hide Route;.
Coordinate order and models #
GeoPoint uses explicit latitude and longitude names. GeoJSON arrays and
route wire parameters use longitude, latitude. Latitude must be finite and
within ±90; longitude within ±180. Constant points are checked at service
boundaries, or explicitly with point.validate(). Invalid values are never
clamped or silently swapped.
The route backend accepts at most 15 fractional decimal places and no exponent
notation. The SDK expands representable small values and rejects values that
would require rounding. Model fromJson methods throw a safe FormatException
for malformed data; requests convert these into ResponseException with HTTP
context. toJson retains the public wire field names and omits HTTP metadata.
New unknown response fields are ignored. No raw infrastructure data is exposed.
Error handling #
import 'package:ubhub_maps/ubhub_maps.dart';
Future<void> lookup(UbHubGeo ubhub) async {
try {
await ubhub.geocoding.search('Ulaanbaatar');
} on RateLimitException catch (e) {
print('Retry after ${e.retryAfter}; remaining ${e.remaining}');
print(e.requestId);
} on UbHubException catch (e) {
print('${e.message} HTTP ${e.statusCode}, code ${e.code}');
print(e.requestId);
}
}
| Failure | Exception |
|---|---|
| 400, 422 | ValidationException |
| 401 | AuthenticationException |
| 402 | PaymentRequiredException |
| 403 | AuthorizationException |
| 404 | NotFoundException |
| 408 or client deadline | TimeoutException |
| 429 | RateLimitException |
| 500–599, including 504 | ServerException |
| Other non-200 status, including redirects | HttpException |
| Transport failure | NetworkException |
| Malformed/oversized success response | ResponseException |
Invalid local configuration/arguments throw ArgumentError; using a closed
client throws StateError. SDK messages are safe local descriptions; raw server
messages, details, bodies, headers, and transport exceptions are deliberately
not included. Known public API error codes are preserved. Request IDs come
from X-Request-ID, falling back to the error envelope. Invalid or credential-
containing IDs are discarded. Nothing retries automatically, including 429.
Successful results expose .context.requestId, .context.rateLimit,
.context.rateLimitRemaining, .context.rateLimitReset (Unix seconds), and
.context.retryAfter. Rate-limit exceptions expose .retryAfter, .limit,
.remaining, and .reset. Retry-After accepts integer seconds and IMF-fixdate
HTTP dates. Missing or malformed headers remain null. No pagination or
attribution schema exists in the current geocoding/routing contract.
Configuration #
final ubhub = UbHubGeo(
apiKey: 'YOUR_API_KEY',
baseUrl: 'https://developers.ubhub.mn',
timeout: const Duration(seconds: 10),
coalesceRequests: true, // Share identical requests while they are in flight.
debug: true,
logger: (message) { /* Forward safe diagnostics to your application logger. */ },
);
- The default endpoint is centralized in
ApiClientConfig.defaultBaseUrl. - Base URL overrides are advanced configuration. Only trusted HTTPS origins are accepted, optionally with a port and trailing slashes. Paths, credentials, queries, and fragments are rejected. Your API key will be sent to that origin; an override is not a proxy target or a way to bypass backend access policies.
- Version
v1is centralized and appended automatically. Do not add/v1tobaseUrl. - The positive timeout (default 10 seconds) covers sending and response body reading. Timeouts abort supported transports without closing other requests.
- Responses are buffered up to 8 MiB, then rejected and aborted.
- Debug logging requires both
debug: trueand a callback. It includes only method, fixed endpoint path, status, and elapsed time—no queries, keys, response bodies, or authorization headers. Nothing prints by default. - Redirects are rejected, never followed. TLS validation cannot be disabled.
- Reuse the client and call synchronous, idempotent
close()when finished. - You may inject an
http.ClientusinghttpClient:for tests/custom transport. It remains caller-owned. A custom transport must honor redirect/TLS/abort semantics; keep it trusted. Closing the SDK cancels its own pending requests and prevents new requests, but does not close an injected client.
Performance and smooth search #
Keep one UbHubGeo client for a feature or application lifetime to reuse
connections. Identical concurrent operations on that client automatically share
one HTTP request and one immutable result. They share the original deadline,
request ID, and rate-limit snapshot. Debug logging runs once per HTTP request.
Different queries, limits, modes, or client instances stay independent.
Set coalesceRequests: false when each caller needs its own HTTP request.
There is no completed-response cache: once a request finishes or fails, a later call goes back to the API. There is no cross-client credential or result sharing. Closing a client aborts requests shared by its callers.
For a search-as-you-type UI, debounce input in the app and ignore responses for
outdated text. Different queries are not cancelled or debounced by the SDK.
Run independent operations concurrently with Future.wait when appropriate,
but keep concurrency within your plan's rate limits. Client optimizations cannot
remove server processing time or network latency.
Response buffering uses typed bytes and fused UTF-8/JSON decoding. JSON/model
parsing is still synchronous in the calling isolate; very large responses can
still affect a UI frame. See PERFORMANCE.md in the repository for the local
benchmark, measurement limits, and reproduction commands.
Supported platforms #
Dart 3.6 or newer with package:http, without Flutter dependencies or global state.
Supports Dart CLI/backend programs and Flutter on mobile, desktop, and web.
Create clients per isolate; do not share live transports across isolates.
Browser requests remain subject to the API's CORS policy. Mobile/desktop apps
must enable normal platform networking permissions. The example uses dart:io
for a CLI; the library itself is browser-compatible.
Example and tests #
Run dart run example/main.dart with UBHUB_API_KEY in the process
environment. Optionally set UBHUB_BASE_URL to a trusted staging origin.
The example demonstrates all three services, error handling, and cleanup.
dart pub get
dart format .
dart analyze
dart test
dart pub publish --dry-run
Unit tests use mocks and never require production access. Opt-in staging tests
live in integration_test/; see RELEASING.md in the repository. The package
follows semantic versioning; 0.x allows iteration before a stable 1.0 contract.
License #
MIT, copyright 2026 UBHUB. See LICENSE.