kryonex_address_picker 0.0.4 copy "kryonex_address_picker: ^0.0.4" to clipboard
kryonex_address_picker: ^0.0.4 copied to clipboard

An opinionated, search-first address picker for Flutter with Nominatim geocoding, map confirmation, and structured address output. Built with ForUI components and flutter_map.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:kryonex_address_picker/kryonex_address_picker.dart';

void main() {
  runApp(const ExampleApp());
}

class ExampleApp extends StatefulWidget {
  const ExampleApp({super.key});

  @override
  State<ExampleApp> createState() => _ExampleAppState();
}

class _ExampleAppState extends State<ExampleApp> {
  ThemeMode _themeMode = ThemeMode.system;

  void _setThemeMode(ThemeMode mode) => setState(() => _themeMode = mode);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Address Picker Example',
      theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
      darkTheme: ThemeData(
        colorSchemeSeed: Colors.indigo,
        brightness: Brightness.dark,
        useMaterial3: true,
      ),
      themeMode: _themeMode,
      home: HomePage(
        themeMode: _themeMode,
        onThemeModeChanged: _setThemeMode,
      ),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({
    super.key,
    required this.themeMode,
    required this.onThemeModeChanged,
  });

  final ThemeMode themeMode;
  final ValueChanged<ThemeMode> onThemeModeChanged;

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  SelectedAddress? _selectedAddress;

  // ─── Demo knobs ───────────────────────────────────────────────────────────

  /// Which dark-mode strategy to use for map tiles.
  MapDarkMode _mapDarkMode = MapDarkMode.auto;

  /// Whether to replace the built-in pin with a custom widget.
  bool _useCustomPin = false;

  /// Whether to apply a custom confirm-button style.
  bool _useCustomButtonStyle = false;

  /// Whether to show OSM attribution (null = suppress, non-null = show).
  bool _showAttribution = true;

  /// Whether to use a custom attribution label.
  bool _useCustomAttribution = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Address Picker Example'),
        actions: [
          // Quick toggle between light / dark / system so testers can
          // exercise mapDarkMode: auto without editing code.
          PopupMenuButton<ThemeMode>(
            icon: const Icon(Icons.brightness_6),
            tooltip: 'Theme mode',
            onSelected: widget.onThemeModeChanged,
            itemBuilder: (_) => const [
              PopupMenuItem(value: ThemeMode.system, child: Text('System')),
              PopupMenuItem(value: ThemeMode.light, child: Text('Light')),
              PopupMenuItem(value: ThemeMode.dark, child: Text('Dark')),
            ],
          ),
        ],
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(24),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // ── Config knobs ──────────────────────────────────────────────
            _SectionHeader('Map dark mode'),
            SegmentedButton<MapDarkMode>(
              segments: const [
                ButtonSegment(
                  value: MapDarkMode.auto,
                  label: Text('Auto'),
                  icon: Icon(Icons.brightness_auto),
                ),
                ButtonSegment(
                  value: MapDarkMode.light,
                  label: Text('Light'),
                  icon: Icon(Icons.light_mode),
                ),
                ButtonSegment(
                  value: MapDarkMode.dark,
                  label: Text('Dark'),
                  icon: Icon(Icons.dark_mode),
                ),
              ],
              selected: {_mapDarkMode},
              onSelectionChanged: (s) =>
                  setState(() => _mapDarkMode = s.first),
            ),
            const SizedBox(height: 16),
            _SectionHeader('Pin & button'),
            SwitchListTile(
              title: const Text('Custom pin builder'),
              subtitle: const Text(
                'Replaces the default MapPin with a coloured star icon',
              ),
              value: _useCustomPin,
              onChanged: (v) => setState(() => _useCustomPin = v),
            ),
            SwitchListTile(
              title: const Text('Custom confirmButtonStyle'),
              subtitle: const Text(
                'Overrides the Confirm Address button with a teal style',
              ),
              value: _useCustomButtonStyle,
              onChanged: (v) => setState(() => _useCustomButtonStyle = v),
            ),
            const SizedBox(height: 16),
            _SectionHeader('Attribution'),
            SwitchListTile(
              title: const Text('Show attribution'),
              subtitle: const Text(
                'Disable to suppress the OSM attribution widget '
                '(not recommended — violates OSM tile policy)',
              ),
              value: _showAttribution,
              onChanged: (v) => setState(() => _showAttribution = v),
            ),
            if (_showAttribution)
              SwitchListTile(
                title: const Text('Custom attribution label'),
                subtitle: const Text(
                  'Uses a custom text and left-side alignment instead of '
                  'the default OSM text',
                ),
                value: _useCustomAttribution,
                onChanged: (v) => setState(() => _useCustomAttribution = v),
              ),

            const SizedBox(height: 24),

            // ── Launch button ────────────────────────────────────────────
            FilledButton.icon(
              onPressed: _openPicker,
              icon: const Icon(Icons.location_on),
              label: const Text('Pick an Address'),
            ),

            const SizedBox(height: 24),

            // ── Result card ──────────────────────────────────────────────
            if (_selectedAddress != null) ...[
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        'Selected Address',
                        style: Theme.of(context).textTheme.titleMedium,
                      ),
                      const SizedBox(height: 12),
                      _row('Display', _selectedAddress!.address.displayName),
                      _row('Street', _selectedAddress!.address.street),
                      _row('House #', _selectedAddress!.address.houseNumber),
                      _row('City', _selectedAddress!.address.city),
                      _row('State', _selectedAddress!.address.state),
                      _row('Postal', _selectedAddress!.address.postalCode),
                      _row('Country', _selectedAddress!.address.country),
                      _row('Code', _selectedAddress!.address.countryCode),
                      _row(
                        'Lat',
                        '${_selectedAddress!.address.latLng.latitude}',
                      ),
                      _row(
                        'Lng',
                        '${_selectedAddress!.address.latLng.longitude}',
                      ),
                      if (_selectedAddress!.details != null) ...[
                        const Divider(),
                        _row('Apt', _selectedAddress!.details!.apt),
                        _row('Floor', _selectedAddress!.details!.floor),
                        _row('Gate', _selectedAddress!.details!['gate']),
                        _row('Notes', _selectedAddress!.details!.deliveryNotes),
                      ],
                    ],
                  ),
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }

  Future<void> _openPicker() async {
    // Resolve the effective attribution based on the demo knobs.
    final AddressPickerAttribution? attribution = !_showAttribution
        ? null
        : _useCustomAttribution
        ? const AddressPickerAttribution(
            text: 'Tiles by Example Corp',
            alignment: MapAttributionAlignment.bottomLeft,
          )
        : AddressPickerAttribution.osm;

    final result = await showAddressPicker(
      context,
      config: AddressPickerConfig(
        searchHint: 'Where to?',
        maxRecentAddresses: 5,
        showDetailScreen: true,
        countryCodes: const ['IN', 'US'],
        detailSheetSubtitle: 'Help your courier find the door',
        // ── New theming fields ────────────────────────────────────────────
        mapDarkMode: _mapDarkMode,
        pinBuilder: _useCustomPin
            ? (context) => Icon(
                  Icons.star,
                  size: 40,
                  color: Theme.of(context).colorScheme.tertiary,
                )
            : null,
        confirmButtonStyle: _useCustomButtonStyle
            ? FilledButton.styleFrom(
                backgroundColor: Colors.teal,
                foregroundColor: Colors.white,
                minimumSize: const Size.fromHeight(52),
                shape: const StadiumBorder(),
              )
            : null,
        attributionStyle: attribution,
        // ── Other existing fields ─────────────────────────────────────────
        detailFields: const [
          AddressFieldSpec.apt,
          AddressFieldSpec.floor,
          AddressFieldSpec(
            key: 'gate',
            label: 'Gate code',
            hint: 'e.g. 1234',
            icon: Icons.pin_outlined,
            keyboardType: TextInputType.number,
            quickFills: ['1234', '0000', 'Call me'],
          ),
          AddressFieldSpec.postalCode,
          AddressFieldSpec.deliveryNotes,
        ],
      ),
    );

    if (result != null) {
      setState(() => _selectedAddress = result);
    }
  }

  Widget _row(String label, String? value) {
    if (value == null) return const SizedBox.shrink();
    return Padding(
      padding: const EdgeInsets.only(bottom: 4),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          SizedBox(
            width: 80,
            child: Text(
              label,
              style: const TextStyle(fontWeight: FontWeight.w600),
            ),
          ),
          Expanded(child: Text(value)),
        ],
      ),
    );
  }
}

class _SectionHeader extends StatelessWidget {
  const _SectionHeader(this.text);
  final String text;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 8),
      child: Text(
        text,
        style: Theme.of(context).textTheme.labelLarge,
      ),
    );
  }
}
1
likes
0
points
34
downloads

Publisher

verified publisherkryonex.io

Weekly Downloads

An opinionated, search-first address picker for Flutter with Nominatim geocoding, map confirmation, and structured address output. Built with ForUI components and flutter_map.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, flutter_hooks, flutter_map, forui, geolocator, http, latlong2, shared_preferences

More

Packages that depend on kryonex_address_picker