xue_hua_gaode_map 1.0.1 copy "xue_hua_gaode_map: ^1.0.1" to clipboard
xue_hua_gaode_map: ^1.0.1 copied to clipboard

Flutter plugin for the Amap (Gaode) SDK: location, geofencing, map and search, with a consistent Dart API on Android and iOS.

example/lib/main.dart

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:xue_hua_gaode_map/xue_hua_gaode_map.dart';

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

class GaodeMapExampleApp extends StatelessWidget {
  const GaodeMapExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Gaode Location Demo',
      theme: ThemeData(colorSchemeSeed: Colors.blue, useMaterial3: true),
      home: const DemoHomePage(),
    );
  }
}

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

  @override
  State<DemoHomePage> createState() => _DemoHomePageState();
}

class _DemoHomePageState extends State<DemoHomePage> {
  bool _privacyConfigured = false;
  String _permissionStatus = 'unknown';
  String _log = '';

  @override
  void initState() {
    super.initState();
    _refreshPermissionStatus();
  }

  Future<void> _refreshPermissionStatus() async {
    final status = await Permission.locationWhenInUse.status;
    setState(() {
      _permissionStatus = status.toString();
    });
  }

  void _appendLog(String message) {
    setState(() {
      _log = '$message\n$_log';
    });
  }

  Future<void> _configurePrivacy() async {
    await GaodeSdk.updatePrivacyShow(hasContains: true, hasShow: true);
    await GaodeSdk.updatePrivacyAgree(hasAgree: true);
    setState(() => _privacyConfigured = true);
    _appendLog('Privacy compliance configured');
  }

  Future<void> _requestPermission() async {
    final status = await Permission.locationWhenInUse.request();
    setState(() => _permissionStatus = status.toString());
    _appendLog('Permission status: $status');
  }

  Future<void> _requestAlwaysPermission() async {
    final status = await Permission.locationAlways.request();
    setState(() => _permissionStatus = status.toString());
    _appendLog('Always permission status: $status');
  }

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 5,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Gaode Location Demo'),
          bottom: const TabBar(
            isScrollable: true,
            tabs: [
              Tab(text: 'Init'),
              Tab(text: 'Location'),
              Tab(text: 'Geofence'),
              Tab(text: 'Map'),
              Tab(text: 'Search'),
            ],
          ),
        ),
        body: TabBarView(
          children: [
            _InitTab(
              privacyConfigured: _privacyConfigured,
              permissionStatus: _permissionStatus,
              onConfigurePrivacy: _configurePrivacy,
              onRequestPermission: _requestPermission,
              onRequestAlwaysPermission: _requestAlwaysPermission,
              onRefreshPermission: _refreshPermissionStatus,
              onLog: _appendLog,
            ),
            LocationTab(enabled: _privacyConfigured, onLog: _appendLog),
            GeofenceTab(enabled: _privacyConfigured, onLog: _appendLog),
            MapTab(enabled: _privacyConfigured, onLog: _appendLog),
            SearchTab(enabled: _privacyConfigured, onLog: _appendLog),
          ],
        ),
        bottomNavigationBar: Container(
          color: Colors.grey.shade100,
          height: 160,
          child: SingleChildScrollView(
            padding: const EdgeInsets.all(12),
            child: Text(_log.isEmpty ? 'Logs appear here' : _log),
          ),
        ),
      ),
    );
  }
}

class _InitTab extends StatefulWidget {
  const _InitTab({
    required this.privacyConfigured,
    required this.permissionStatus,
    required this.onConfigurePrivacy,
    required this.onRequestPermission,
    required this.onRequestAlwaysPermission,
    required this.onRefreshPermission,
    required this.onLog,
  });

  final bool privacyConfigured;
  final String permissionStatus;
  final Future<void> Function() onConfigurePrivacy;
  final Future<void> Function() onRequestPermission;
  final Future<void> Function() onRequestAlwaysPermission;
  final Future<void> Function() onRefreshPermission;
  final void Function(String message) onLog;

  @override
  State<_InitTab> createState() => _InitTabState();
}

class _InitTabState extends State<_InitTab> {
  final _apiKeyController = TextEditingController();

  @override
  void dispose() {
    _apiKeyController.dispose();
    super.dispose();
  }

  Future<void> _setApiKey() async {
    final apiKey = _apiKeyController.text.trim();
    if (apiKey.isEmpty) {
      widget.onLog('API key is empty');
      return;
    }
    try {
      await GaodeSdk.setApiKey(apiKey);
      widget.onLog('API key set at runtime');
    } catch (e) {
      widget.onLog('setApiKey failed: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Text('Privacy configured: ${widget.privacyConfigured}'),
          Text('Permission: ${widget.permissionStatus}'),
          const SizedBox(height: 12),
          TextField(
            controller: _apiKeyController,
            decoration: const InputDecoration(
              labelText: 'Amap API Key (optional runtime override)',
              border: OutlineInputBorder(),
            ),
          ),
          const SizedBox(height: 8),
          FilledButton(
            onPressed: _setApiKey,
            child: const Text('Set API key at runtime'),
          ),
          const SizedBox(height: 12),
          FilledButton(
            onPressed: widget.onConfigurePrivacy,
            child: const Text('Configure privacy compliance'),
          ),
          FilledButton(
            onPressed: widget.onRequestPermission,
            child: const Text('Request when-in-use permission'),
          ),
          FilledButton(
            onPressed: widget.onRequestAlwaysPermission,
            child: const Text('Request always permission (geofence)'),
          ),
          OutlinedButton(
            onPressed: widget.onRefreshPermission,
            child: const Text('Refresh permission status'),
          ),
          const SizedBox(height: 16),
          const Text(
            'You can also configure your Amap API Key in AndroidManifest.xml and Info.plist.',
          ),
        ],
      ),
    );
  }
}

class LocationTab extends StatefulWidget {
  const LocationTab({required this.enabled, required this.onLog, super.key});

  final bool enabled;
  final void Function(String message) onLog;

  @override
  State<LocationTab> createState() => _LocationTabState();
}

class _LocationTabState extends State<LocationTab> {
  late final LocationClient _client;
  StreamSubscription<LocationResult>? _subscription;

  @override
  void initState() {
    super.initState();
    _client = LocationClient();
  }

  @override
  void dispose() {
    _subscription?.cancel();
    unawaited(_client.dispose());
    super.dispose();
  }

  Future<void> _getOnce() async {
    if (!widget.enabled) return;
    try {
      await _client.setOptions(
        const LocationOptions(needAddress: true, onceLocation: true),
      );
      final result = await _client.getLocation();
      widget.onLog(
        'Once: ${result.latitude}, ${result.longitude} ${result.address ?? ''}',
      );
    } catch (e) {
      widget.onLog('Once failed: $e');
    }
  }

  Future<void> _startContinuous() async {
    if (!widget.enabled) return;
    await _subscription?.cancel();
    await _client.setOptions(
      const LocationOptions(needAddress: true, interval: 3000),
    );
    _subscription = _client.locationStream.listen((result) {
      widget.onLog(
        'Stream: ${result.latitude}, ${result.longitude} acc=${result.accuracy}',
      );
    }, onError: (Object e) => widget.onLog('Stream error: $e'));
    await _client.start();
    widget.onLog('Continuous location started');
  }

  Future<void> _stop() async {
    await _client.stop();
    widget.onLog('Location stopped');
  }

  Future<void> _reverseGeocode() async {
    if (!widget.enabled) return;
    try {
      const coordinate = GaodeCoordinate(
        latitude: 39.909187,
        longitude: 116.397451,
      );
      final result = await _client.reverseGeocode(coordinate);
      widget.onLog('ReGeo: ${result.address ?? result.city ?? 'no address'}');
    } catch (e) {
      widget.onLog('ReGeo failed: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          FilledButton(
            onPressed: _getOnce,
            child: const Text('Get location once'),
          ),
          FilledButton(
            onPressed: _startContinuous,
            child: const Text('Start continuous location'),
          ),
          OutlinedButton(onPressed: _stop, child: const Text('Stop location')),
          OutlinedButton(
            onPressed: _reverseGeocode,
            child: const Text('Reverse geocode (Tiananmen)'),
          ),
        ],
      ),
    );
  }
}

class GeofenceTab extends StatefulWidget {
  const GeofenceTab({required this.enabled, required this.onLog, super.key});

  final bool enabled;
  final void Function(String message) onLog;

  @override
  State<GeofenceTab> createState() => _GeofenceTabState();
}

class _GeofenceTabState extends State<GeofenceTab> {
  late final GeofenceClient _client;
  StreamSubscription<GeofenceEvent>? _subscription;

  @override
  void initState() {
    super.initState();
    _client = GeofenceClient();
  }

  @override
  void dispose() {
    _subscription?.cancel();
    unawaited(_client.dispose());
    super.dispose();
  }

  Future<void> _setupGeofence() async {
    if (!widget.enabled) return;
    await _subscription?.cancel();
    await _client.setActiveActions({
      GeofenceAction.enter,
      GeofenceAction.exit,
    }, allowsBackgroundLocationUpdates: true);
    _subscription = _client.geofenceStream.listen((event) {
      if (event.isTrigger) {
        widget.onLog('Geofence trigger: ${event.status} ${event.customId}');
      } else if (event.isCreateFinished) {
        widget.onLog(
          'Geofence created: success=${event.success} count=${event.count}',
        );
      }
    });
    await _client.addCircle(
      center: const GaodeCoordinate(latitude: 39.909187, longitude: 116.397451),
      radius: 500,
      customId: 'demo-circle',
    );
    widget.onLog('Circle geofence added around Tiananmen');
  }

  Future<void> _setupPolygonGeofence() async {
    if (!widget.enabled) return;
    await _subscription?.cancel();
    await _client.setActiveActions({
      GeofenceAction.enter,
      GeofenceAction.exit,
    }, allowsBackgroundLocationUpdates: true);
    _subscription = _client.geofenceStream.listen((event) {
      if (event.isTrigger) {
        widget.onLog('Geofence trigger: ${event.status} ${event.customId}');
      } else if (event.isCreateFinished) {
        widget.onLog(
          'Geofence created: success=${event.success} count=${event.count}',
        );
      }
    });
    const center = GaodeCoordinate(latitude: 39.909187, longitude: 116.397451);
    await _client.addPolygon(
      points: [
        const GaodeCoordinate(latitude: 39.9105, longitude: 116.3960),
        const GaodeCoordinate(latitude: 39.9080, longitude: 116.3960),
        const GaodeCoordinate(latitude: 39.9090, longitude: 116.3990),
        center,
      ],
      customId: 'demo-polygon',
    );
    widget.onLog('Polygon geofence added near Tiananmen');
  }

  Future<void> _clearGeofences() async {
    await _client.removeAll();
    widget.onLog('All geofences removed');
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          FilledButton(
            onPressed: _setupGeofence,
            child: const Text('Add circle geofence & listen'),
          ),
          FilledButton(
            onPressed: _setupPolygonGeofence,
            child: const Text('Add polygon geofence & listen'),
          ),
          OutlinedButton(
            onPressed: _clearGeofences,
            child: const Text('Remove all geofences'),
          ),
        ],
      ),
    );
  }
}

class MapTab extends StatefulWidget {
  const MapTab({required this.enabled, required this.onLog, super.key});

  final bool enabled;
  final void Function(String message) onLog;

  @override
  State<MapTab> createState() => _MapTabState();
}

class _MapTabState extends State<MapTab> {
  static const _tiananmen = GaodeCoordinate(
    latitude: 39.909187,
    longitude: 116.397451,
  );

  GaodeMapController? _controller;
  GaodeMapType _mapType = GaodeMapType.normal;
  int _markerSeq = 0;

  void _onMapCreated(GaodeMapController controller) {
    _controller = controller;
    widget.onLog('Map created');
  }

  Future<void> _cycleMapType() async {
    const order = GaodeMapType.values;
    final next = order[(_mapType.index + 1) % order.length];
    setState(() => _mapType = next);
    await _controller?.setMapType(next);
    widget.onLog('Map type: ${next.name}');
  }

  Future<void> _moveToTiananmen() async {
    await _controller?.moveCamera(
      const CameraPosition(target: _tiananmen, zoom: 17),
    );
    widget.onLog('Camera moved to Tiananmen');
  }

  Future<void> _addMarker() async {
    final id = 'marker_${_markerSeq++}';
    await _controller?.addMarker(
      GaodeMapMarker(
        id: id,
        position: _tiananmen,
        title: 'Tiananmen',
        snippet: id,
      ),
    );
    widget.onLog('Marker added: $id');
  }

  Future<void> _clearMarkers() async {
    await _controller?.clearMarkers();
    widget.onLog('Markers cleared');
  }

  @override
  Widget build(BuildContext context) {
    if (!widget.enabled) {
      return const Center(
        child: Padding(
          padding: EdgeInsets.all(24),
          child: Text('Configure privacy compliance first (Init tab).'),
        ),
      );
    }
    return Column(
      children: [
        Expanded(
          child: GaodeMapView(
            options: const GaodeMapOptions(
              initialCamera: CameraPosition(target: _tiananmen, zoom: 16),
              myLocationEnabled: true,
            ),
            gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
              Factory<OneSequenceGestureRecognizer>(EagerGestureRecognizer.new),
            },
            onMapCreated: _onMapCreated,
          ),
        ),
        Padding(
          padding: const EdgeInsets.all(8),
          child: Wrap(
            spacing: 8,
            runSpacing: 4,
            children: [
              FilledButton(
                onPressed: _cycleMapType,
                child: const Text('Cycle map type'),
              ),
              FilledButton(
                onPressed: _moveToTiananmen,
                child: const Text('Move camera'),
              ),
              FilledButton(
                onPressed: _addMarker,
                child: const Text('Add marker'),
              ),
              OutlinedButton(
                onPressed: _clearMarkers,
                child: const Text('Clear markers'),
              ),
            ],
          ),
        ),
      ],
    );
  }
}

class SearchTab extends StatefulWidget {
  const SearchTab({required this.enabled, required this.onLog, super.key});

  final bool enabled;
  final void Function(String message) onLog;

  @override
  State<SearchTab> createState() => _SearchTabState();
}

class _SearchTabState extends State<SearchTab> {
  static const _tiananmen = GaodeCoordinate(
    latitude: 39.909187,
    longitude: 116.397451,
  );

  final _searchClient = const SearchClient();
  final _keywordController = TextEditingController(text: '咖啡');
  final _cityController = TextEditingController(text: '北京');

  @override
  void dispose() {
    _keywordController.dispose();
    _cityController.dispose();
    super.dispose();
  }

  Future<void> _poiKeyword() async {
    if (!widget.enabled) return;
    try {
      final result = await _searchClient.searchPoiKeyword(
        keyword: _keywordController.text.trim(),
        city: _cityController.text.trim(),
      );
      widget.onLog(
        'POI keyword: count=${result.count} first=${result.pois.isEmpty ? '-' : result.pois.first.name}',
      );
    } catch (e) {
      widget.onLog('POI keyword failed: $e');
    }
  }

  Future<void> _poiAround() async {
    if (!widget.enabled) return;
    try {
      final result = await _searchClient.searchPoiAround(
        center: _tiananmen,
        keyword: _keywordController.text.trim(),
        radius: 2000,
      );
      widget.onLog(
        'POI around: count=${result.count} first=${result.pois.isEmpty ? '-' : result.pois.first.name}',
      );
    } catch (e) {
      widget.onLog('POI around failed: $e');
    }
  }

  Future<void> _inputTips() async {
    if (!widget.enabled) return;
    try {
      final tips = await _searchClient.inputTips(
        keyword: _keywordController.text.trim(),
        city: _cityController.text.trim(),
      );
      widget.onLog('Tips: ${tips.take(3).map((t) => t.name).join(', ')}');
    } catch (e) {
      widget.onLog('Input tips failed: $e');
    }
  }

  Future<void> _geocode() async {
    if (!widget.enabled) return;
    try {
      final result = await _searchClient.geocode(
        address: _keywordController.text.trim(),
        city: _cityController.text.trim(),
      );
      final first = result.geocodes.isEmpty ? null : result.geocodes.first;
      widget.onLog(
        'Geocode: ${first == null ? 'no result' : '${first.formattedAddress} (${first.location.latitude}, ${first.location.longitude})'}',
      );
    } catch (e) {
      widget.onLog('Geocode failed: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          TextField(
            controller: _keywordController,
            decoration: const InputDecoration(
              labelText: 'Keyword / address',
              border: OutlineInputBorder(),
            ),
          ),
          const SizedBox(height: 8),
          TextField(
            controller: _cityController,
            decoration: const InputDecoration(
              labelText: 'City',
              border: OutlineInputBorder(),
            ),
          ),
          const SizedBox(height: 12),
          FilledButton(
            onPressed: _poiKeyword,
            child: const Text('POI keyword search'),
          ),
          FilledButton(
            onPressed: _poiAround,
            child: const Text('POI around (Tiananmen 2km)'),
          ),
          FilledButton(
            onPressed: _inputTips,
            child: const Text('Input tips (autocomplete)'),
          ),
          OutlinedButton(
            onPressed: _geocode,
            child: const Text('Geocode (address -> coordinate)'),
          ),
        ],
      ),
    );
  }
}
1
likes
0
points
28
downloads

Publisher

unverified uploader

Weekly Downloads

Flutter plugin for the Amap (Gaode) SDK: location, geofencing, map and search, with a consistent Dart API on Android and iOS.

Repository (GitHub)
View/report issues

Topics

#amap #gaode #location #geofence #map

License

unknown (license)

Dependencies

flutter

More

Packages that depend on xue_hua_gaode_map

Packages that implement xue_hua_gaode_map