ethiopia_regions_data 0.2.0 copy "ethiopia_regions_data: ^0.2.0" to clipboard
ethiopia_regions_data: ^0.2.0 copied to clipboard

Lightweight Ethiopian administrative location data for regions, zones, woredas, and cities. Pure Dart, dependency-free, with JSON-backed datasets suitable for forms and location pickers.

example/lib/main.dart

import 'package:ethiopia_regions_data/ethiopia_regions_data.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final data = await _loadEthiopiaRegionsData();
  runApp(EthiopiaRegionsExampleApp(data: data));
}

/// Loads JSON via [rootBundle] on every platform.
///
/// [EthiopiaRegionsData.load] uses `Isolate.resolvePackageUri`, which is not
/// supported inside Flutter on Android/iOS (and is unreliable in Flutter apps
/// in general). Declaring these paths under `flutter: assets:` and using
/// `rootBundle` is the supported approach for Flutter.
Future<EthiopiaRegionsData> _loadEthiopiaRegionsData() async {
  final regionsJson = await rootBundle.loadString(
    'packages/ethiopia_regions_data/src/data/regions.json',
  );
  final zonesJson = await rootBundle.loadString(
    'packages/ethiopia_regions_data/src/data/zones.json',
  );
  final woredasJson = await rootBundle.loadString(
    'packages/ethiopia_regions_data/src/data/woredas.json',
  );
  final citiesJson = await rootBundle.loadString(
    'packages/ethiopia_regions_data/src/data/cities.json',
  );
  return EthiopiaRegionsData.fromJsonStrings(
    regionsJson: regionsJson,
    zonesJson: zonesJson,
    woredasJson: woredasJson,
    citiesJson: citiesJson,
  );
}

/// Chartered administrations that use **sub cities** instead of zones in the UI.
bool _regionUsesSubcityLabel(Region region) {
  return region.id == 'addis_ababa';
}

/// Demo Flutter app for cascading administrative pickers.
class EthiopiaRegionsExampleApp extends StatelessWidget {
  /// Creates the example app with preloaded [data].
  const EthiopiaRegionsExampleApp({required this.data, super.key});

  /// Administrative catalog used by the demo screens.
  final EthiopiaRegionsData data;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Ethiopia Regions Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
        useMaterial3: true,
      ),
      home: LocationPickerPage(data: data),
    );
  }
}

/// Demonstrates region, zone, and woreda dropdowns backed by the package API.
class LocationPickerPage extends StatefulWidget {
  /// Creates a page bound to [data].
  const LocationPickerPage({required this.data, super.key});

  /// Administrative catalog for lookups.
  final EthiopiaRegionsData data;

  @override
  State<LocationPickerPage> createState() => _LocationPickerPageState();
}

class _LocationPickerPageState extends State<LocationPickerPage> {
  Region? _region;
  Zone? _zone;
  String? _subCity;
  Woreda? _woreda;

  @override
  Widget build(BuildContext context) {
    final regions = widget.data.getRegions();
    final zones =
        _region == null ? const <Zone>[] : widget.data.getZones(_region!.id);
    final subCityNames =
        _zone == null ? const <String>[] : widget.data.getSubCities(_zone!.id);
    final useSubCityTier = _zone != null &&
        EthiopiaRegionsData.zoneUsesSubCityPicker(_zone!) &&
        subCityNames.isNotEmpty;
    final woredas = _zone == null
        ? const <Woreda>[]
        : useSubCityTier
            ? (_subCity == null
                ? const <Woreda>[]
                : widget.data.getWoredas(_zone!.id, subCity: _subCity))
            : widget.data.getWoredas(_zone!.id);

    final cities =
        _region == null ? const <City>[] : widget.data.getCities(_region!.id);

    final secondLevelLabel =
        _region != null && _regionUsesSubcityLabel(_region!)
            ? 'Sub city'
            : 'Zone';

    return Scaffold(
      appBar: AppBar(title: const Text('Ethiopia location pickers')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: ListView(
          children: [
            Text(
              'Sample data is provided for development and testing. '
              '',
              style: Theme.of(context).textTheme.bodyMedium,
            ),
            const SizedBox(height: 16),
            _LabeledDropdown<Region>(
              label: 'Region',
              value: _region,
              items: regions,
              itemLabel: (r) => r.name,
              onChanged: (value) {
                setState(() {
                  _region = value;
                  _zone = null;
                  _subCity = null;
                  _woreda = null;
                });
              },
            ),
            const SizedBox(height: 12),
            _LabeledDropdown<Zone>(
              label: secondLevelLabel,
              value: _zone,
              items: zones,
              enabled: _region != null,
              itemLabel: (z) => z.name,
              onChanged: (value) {
                setState(() {
                  _zone = value;
                  _subCity = null;
                  _woreda = null;
                });
              },
            ),
            if (useSubCityTier) ...[
              const SizedBox(height: 12),
              _LabeledDropdown<String>(
                label: 'Sub city',
                value: _subCity,
                items: subCityNames,
                enabled: _zone != null,
                itemLabel: (s) => s,
                onChanged: (value) {
                  setState(() {
                    _subCity = value;
                    _woreda = null;
                  });
                },
              ),
            ],
            const SizedBox(height: 12),
            _LabeledDropdown<Woreda>(
              label: 'Woreda',
              value: _woreda,
              items: woredas,
              enabled: _zone != null && (!useSubCityTier || _subCity != null),
              itemLabel: (w) => w.name,
              onChanged: (value) {
                setState(() {
                  _woreda = value;
                });
              },
            ),
            const SizedBox(height: 24),
            Text('Cities in selected region',
                style: Theme.of(context).textTheme.titleMedium),
            const SizedBox(height: 8),
            if (cities.isEmpty)
              const Text('Select a region to list sample cities.')
            else
              ...cities.map(
                (c) => ListTile(
                  dense: true,
                  title: Text(c.name),
                  subtitle: Text('id: ${c.id}'),
                ),
              ),
            const SizedBox(height: 24),
            Text('Selection', style: Theme.of(context).textTheme.titleMedium),
            const SizedBox(height: 8),
            Text(
              'Region: ${_region?.name ?? "—"}\n'
              '$secondLevelLabel: ${_zone?.name ?? "—"}\n'
              '${useSubCityTier ? 'Sub city: ${_subCity ?? "—"}\n' : ''}'
              'Woreda: ${_woreda?.name ?? "—"}',
            ),
          ],
        ),
      ),
    );
  }
}

class _LabeledDropdown<T> extends StatelessWidget {
  const _LabeledDropdown({
    required this.label,
    required this.value,
    required this.items,
    required this.itemLabel,
    required this.onChanged,
    this.enabled = true,
  });

  final String label;
  final T? value;
  final List<T> items;
  final String Function(T) itemLabel;
  final ValueChanged<T?> onChanged;
  final bool enabled;

  @override
  Widget build(BuildContext context) {
    return InputDecorator(
      decoration: InputDecoration(
        labelText: label,
        border: const OutlineInputBorder(),
      ),
      child: DropdownButtonHideUnderline(
        child: DropdownButton<T>(
          isExpanded: true,
          value: value,
          hint: Text(enabled ? 'Choose $label' : 'Select a region first'),
          onChanged: enabled ? onChanged : null,
          items: items
              .map(
                (item) => DropdownMenuItem<T>(
                  value: item,
                  child: Text(itemLabel(item)),
                ),
              )
              .toList(growable: false),
        ),
      ),
    );
  }
}
7
likes
150
points
153
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Lightweight Ethiopian administrative location data for regions, zones, woredas, and cities. Pure Dart, dependency-free, with JSON-backed datasets suitable for forms and location pickers.

Homepage
Repository (GitHub)
View/report issues

Topics

#ethiopia #geography #administrative-divisions #location #i18n

License

MIT (license)

More

Packages that depend on ethiopia_regions_data