Skip to main content

afrihex 0.1.0 copy "afrihex: ^0.1.0" to clipboard
afrihex: ^0.1.0 copied to clipboard

The AfriHex API for Dart and Flutter: GhanaPostGPS codes, landmark directions, routing, delivery tracking, cash on delivery and pricing for Ghana.

afrihex #

The AfriHex API for Dart and Flutter. Use it to:

  • find Ghana addresses the way people give them: GhanaPostGPS codes, place names, and directions such as "adjacent Goil filling station, Madina Zongo Junction";
  • get directions a rider can follow, for any point: "Opposite Nick Petroleum filling station, Gbatsuna";
  • route and plan deliveries;
  • track deliveries, record cash on delivery, quote delivery fees, issue proof of delivery and check service areas.

It works in Flutter apps (Android, iOS, web, desktop) and on Dart servers. Every response is typed.

flutter pub add afrihex   # or: dart pub add afrihex

In a Flutter app: use a publishable key #

Never put your secret key (afx_live_…) in an app, because anyone can pull it out of the APK. Use a publishable key (afx_pub_…) instead. It is locked to your app's package name or bundle ID, and it can only look up addresses. It cannot create trips, read your data or change anything.

  1. In your AfriHex dashboard, go to Publishable keys and click Create publishable key. Under Mobile apps, add your Android applicationId and your iOS bundle ID (for example com.kofishop.app).
  2. Pass the key and the app ID to the client:
import 'package:afrihex/afrihex.dart';

final afx = AfriHex(apiKey: 'afx_pub_…', appId: 'com.kofishop.app');

// What the customer typed: a code, a place, or directions.
final suggestions = await afx.addresses.suggest('adjacent goil filling station, madina zongo junction');
// suggestions.first.title    → Goil Fuel Station Madina Zongo Junction
// suggestions.first.subtitle → 276 m from Madina zongo junction bus stop · J.J. Rawlings Avenue

// Once they pick one, add the GhanaPostGPS code and rider directions.
final address = await afx.addresses.complete(suggestions.first.address);
// address.gpsCode    → GM-017-3727
// address.directions → At Goil filling station, Madina

// Or use their current position (from the geolocator package, for example).
final here = await afx.addresses.atPosition(position.latitude, position.longitude, accuracyM: position.accuracy);

A publishable key can call addresses.lookup, reverse, parse, autocomplete, suggest, complete, atPosition and landmarks.geocode. Each visitor is limited to 120 calls a minute, and the calls count against your account's daily quota.

A checkout address field #

class AddressField extends StatefulWidget {
  const AddressField({super.key, required this.onSelected});
  final ValueChanged<AfriHexAddress> onSelected;

  @override
  State<AddressField> createState() => _AddressFieldState();
}

class _AddressFieldState extends State<AddressField> {
  Timer? _debounce;
  Completer<List<AddressSuggestion>>? _pending;

  // Wait until the customer pauses typing, then ask AfriHex.
  Future<List<AddressSuggestion>> _search(String text) {
    _debounce?.cancel();
    _pending?.complete(const []);
    final done = _pending = Completer();
    _debounce = Timer(const Duration(milliseconds: 250), () async {
      try {
        final s = await afx.addresses.suggest(text);
        if (!done.isCompleted) done.complete(s);
      } on AfriHexException {
        if (!done.isCompleted) done.complete(const []);
      }
    });
    return done.future;
  }

  @override
  Widget build(BuildContext context) => Autocomplete<AddressSuggestion>(
        optionsBuilder: (v) => v.text.trim().length < 2 ? const <AddressSuggestion>[] : _search(v.text),
        displayStringForOption: (s) => s.title,
        optionsViewBuilder: (context, onSelected, options) => Material(
          elevation: 4,
          child: ListView(shrinkWrap: true, children: [
            for (final s in options)
              ListTile(title: Text(s.title), subtitle: s.subtitle == null ? null : Text(s.subtitle!), onTap: () => onSelected(s)),
          ]),
        ),
        onSelected: (s) async => widget.onSelected(await afx.addresses.complete(s.address)),
        fieldViewBuilder: (context, controller, focus, _) => TextField(
          controller: controller,
          focusNode: focus,
          decoration: const InputDecoration(labelText: 'Delivery address', hintText: 'GhanaPostGPS code, place or directions'),
        ),
      );
}

Send the chosen AfriHexAddress (address.toJson()) to your server with the order. It has lat, lng, gpsCode, street, area, region, directions, and input, which is what the customer typed, worth keeping for the rider.

On your server: use your secret key #

On a Dart server (Dart Frog, Shelf, Serverpod) the client can call every endpoint:

final afx = AfriHex(apiKey: Platform.environment['AFRIHEX_API_KEY']!);

final quote = await afx.delivery.quote(DeliveryQuoteRequest(
  pickup: RouteEndpoint(gpsCode: 'GA-183-8164'),
  dropoff: RouteEndpoint(point: LatLng(order.lat, order.lng)),
));

final trip = await afx.trips.create(
  TripCreateRequest(
    destination: RouteEndpoint(point: LatLng(order.lat, order.lng)),
    destinationLabel: order.directions,
    amountToCollect: 120,
    riderRef: 'kwame',
    recipientPhone: order.phone,
  ),
  options: RequestOptions(idempotencyKey: order.id), // safe to retry: one order, one trip
);
print(trip.trackingUrl); // send this to your customer
Module What it does
addresses lookup, reverse (with landmark directions), search, autocomplete, parse, suggest, complete, atPosition
landmarks find a landmark by name, or near a point or place
routing route for directions by okada, car, foot or truck; plan for fleet planning
trips create, ping, update, events, failed attempts, tracking links, recipient replies, cash collections
cash rider remittances, balances and reconciliation
delivery fee rulebook and quotes
pod proof of delivery: issue, confirm, verify signed receipts
serviceAreas create areas and check whether a point is inside one
hex, bridge AfriHex hex codes, and converting between them and GhanaPostGPS codes
webhooks, account webhook endpoints, test keys, SMS usage

Webhooks #

// Pass the raw body, exactly as received.
final event = verifyWebhook(await request.readAsString(), request.headers, Platform.environment['AFRIHEX_WEBHOOK_SECRET']!);
if (event.event == 'trip.completed') await markDelivered(event.trip!.externalId);

verifyWebhook checks the X-Webhook-Signature-V2 header, which also covers the timestamp. If the signature doesn't match, or the event is more than 5 minutes old, it throws AfriHexException('INVALID_SIGNATURE'). Reply 400 when that happens.

Errors, retries and timeouts #

Every failure throws AfriHexException, which has:

  • code, such as NOT_FOUND, RATE_LIMITED, APP_NOT_ALLOWED, TIMEOUT or NETWORK_ERROR;
  • the HTTP status;
  • a requestId to quote to support.

Calls are retried twice by default after network errors, timeouts, 408, 429 and 5xx responses. The client waits as long as Retry-After says, up to 30 seconds; a longer wait, such as for a daily quota reset, is returned to you as an error instead. POST and PATCH calls carry an Idempotency-Key, so a retry never creates a second trip. You can change the retry count and timeout:

AfriHex(apiKey: key, maxRetries: 3, timeout: const Duration(seconds: 20));

Test mode #

A test key (afx_test_…) runs every call in a sandbox. It sends no real texts and doesn't use your quota. Create one in the dashboard, or with afx.account.createTestKey().

Docs: developer.afrihex.com/dart · MIT licence

0
likes
150
points
--
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

The AfriHex API for Dart and Flutter: GhanaPostGPS codes, landmark directions, routing, delivery tracking, cash on delivery and pricing for Ghana.

Homepage

Topics

#ghana #geocoding #delivery #routing #maps

License

MIT (license)

Dependencies

crypto, http

More

Packages that depend on afrihex