manara_map 1.0.0 copy "manara_map: ^1.0.0" to clipboard
manara_map: ^1.0.0 copied to clipboard

A Flutter package for OpenStreetMap-based maps with address search, location services, turn-by-turn directions with live rider tracking, and customizable UI.

example/lib/main.dart

import 'dart:async';
import 'dart:developer';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:manara_map/manara_map.dart';


// Supplied at build time so no credential is committed:
//   flutter run --dart-define=API_KEY=... --dart-define=BASE_URL=https://... \n//     --dart-define=TILE_URL=https://.../{z}/{x}/{y}.png
// The defaults are placeholders; the API rejects them, which is the intended
// signal that the defines are missing.
const String _apiKey =
    String.fromEnvironment("API_KEY", defaultValue: "API-KEY");
const String _baseUrl =
    String.fromEnvironment("BASE_URL", defaultValue: "BASE-URL");
const String _tileUrl =
    String.fromEnvironment("TILE_URL", defaultValue: "TILE-URL");

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Manara Map Example',
      home: MapExamplePage(),
    );
  }
}

/// Demonstrates [ManaraMapWidget]: address search, the directions picker,
/// and the current-location button that repurposes itself into "Start
/// Routing" once a route is picked. The show/hide constructor params below
/// mirror [ManaraMapWidget]'s own — see [DemoHomePage], which drives them
/// live from its drawer.
class MapExamplePage extends StatefulWidget {
  const MapExamplePage({
    super.key,
    this.showSearchBar = true,
    this.showCurrentLocationButton = true,
    this.showCustomLocationButton = true,
    this.showAddressText = true,
    this.showSearchBarAtBottom = false,
    this.searchIconIsLeading = true,
    this.showDirectionsButton = true,
  });

  final bool showSearchBar;
  final bool showCurrentLocationButton;
  final bool showCustomLocationButton;
  final bool showAddressText;
  final bool showSearchBarAtBottom;
  final bool searchIconIsLeading;
  final bool showDirectionsButton;

  @override
  State<MapExamplePage> createState() => _MapExamplePageState();
}

class _MapExamplePageState extends State<MapExamplePage>
    with TickerProviderStateMixin {
  late final ManaraMapController _mapController;
  StreamSubscription<Position>? _riderPositionSubscription;

  /// Whether "Start Routing" is active — while true, real GPS ticks are also
  /// fed into [trackingLocation] so the rider marker follows actual device
  /// movement instead of sitting still.
  bool _isTracking = false;

  LocationEntity? _pickLocation;
  String get className => runtimeType.toString();

  @override
  void initState() {
    super.initState();
    _mapController = ManaraMapController(this);
    _publishRealRiderLocation();
  }

  @override
  void dispose() {
    _mapController.dispose();
    _riderPositionSubscription?.cancel();
    super.dispose();
  }

  /// Publishes the device's real, continuous GPS position through
  /// [RiderLocationService] — what would run on a rider's phone.
  ///
  /// This opens its own location stream, which is how a production app should
  /// feed the package. [ManaraMapWidget.onLocationChanged] is not a substitute:
  /// it only fires when the map center moves. While [_isTracking] is true the
  /// same positions also drive the on-map rider marker through
  /// `trackingLocation`.
  Future<void> _publishRealRiderLocation() async {
    if (!await Geolocator.isLocationServiceEnabled()) {
      await Geolocator.openLocationSettings();
      if (!await Geolocator.isLocationServiceEnabled()) {
        return;
      }
    }

    var permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      permission = await Geolocator.requestPermission();
    }
    if (permission == LocationPermission.denied ||
        permission == LocationPermission.deniedForever) {
      await Geolocator.openAppSettings();
      permission = await Geolocator.checkPermission();
      if (permission == LocationPermission.denied) return;
    }

    _riderPositionSubscription = Geolocator.getPositionStream(
      locationSettings: const LocationSettings(
          accuracy: LocationAccuracy.high, distanceFilter: 10),
    ).listen((position) {
      final location = LocationEntity(
          latitude: position.latitude, longitude: position.longitude);
      // Update on your Service
      //_riderLocationService.updateRiderLocation(location);
      if (_isTracking) {
        setState(() => _pickLocation = location);
        _mapController.animateTo(location.latitude, location.longitude, 18.0);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Expanded(
          child: ManaraMapWidget(
            apiKey: _apiKey,
            baseUrl: _baseUrl,
            tileUrl: _tileUrl,
            mapController: _mapController,
            showSearchBar: widget.showSearchBar,
            showCurrentLocationButton: widget.showCurrentLocationButton,
            showCustomLocationButton: widget.showCustomLocationButton,
            showAddressText: widget.showAddressText,
            showSearchBarAtBottom: widget.showSearchBarAtBottom,
            searchIconIsLeading: widget.searchIconIsLeading,
            showDirectionsButton: widget.showDirectionsButton,
            searchActiveIconColor: Colors.blue,
            locationButtonPadding: const EdgeInsetsDirectional.only(bottom: 20),
            onLocationChanged: (lat, lng) {
              // Fires for map-center changes (initial load, re-center,
              // dragging) — not the rider's live GPS position, so it's
              // just logged here. See _publishRealRiderLocation for the
              // actual RiderLocationService publishing.
              if (kDebugMode) {
                debugPrint('Location: $lat, $lng');
                // _riderLocationService.updateRiderLocation(
                //     LocationEntity(latitude: lat, longitude: lng));
              }
            },
            onAddressChanged: (lat, lng, address) {
              if (kDebugMode) {
                debugPrint('Address: $lat, $lng, $address');
              }
            },
            onSearchActiveChanged: (isActive) {
              if (kDebugMode) debugPrint('Search active: $isActive');
            },
            onDirectionsRouteFound: (RouteEntity routeEntity) {
              // Seed trackingLocation with the route's own start, so the rider
              // marker has a position before the first GPS tick arrives.
              // See _publishRealRiderLocation.
              if (routeEntity.points.isNotEmpty) {
                // _pickLocation = routeEntity.points.first;
                // final endLocation = routeEntity.points.last;
                // _riderLocationService.updateRiderLocation(
                //     _pickLocation!, endLocation);
              }
            },
            onRerouting: () {
              if (kDebugMode) {
                log('🔀 Off-route — rerouting...', name: className);
              }
            },
            onRerouted: (RouteEntity routeEntity) {
              if (kDebugMode) {
                log('✅ Rerouted with ${routeEntity.points.length} points',
                    name: className);
              }
              if (routeEntity.points.isNotEmpty) {
                // _pickLocation = routeEntity.points.first;
                // final endLocation = routeEntity.points.last;
                // _riderLocationService.updateRiderLocation(
                //     _pickLocation!, endLocation);
              }
            },
            trackingLocation: _pickLocation,
            onStartRouting: () {
              if (kDebugMode) log('Route started', name: className);
              _isTracking = true;
              if (_pickLocation != null) {
                _mapController.animateTo(
                    _pickLocation!.latitude, _pickLocation!.longitude, 18.0);
              }
            },
            onTrackingCompleted: () {
              if (kDebugMode) log('Rider arrived', name: className);
              _isTracking = false;
            },
          ),
        ),
      ],
    );
  }
}

/// Demonstrates [ManaraRouteMapWidget]: a fixed pickup/dropoff pair, a
/// "Find Route" button that fetches the route between them, and a
/// "Start Route" button once one is found.
class RouteExamplePage extends StatefulWidget {
  const RouteExamplePage({super.key});

  @override
  State<RouteExamplePage> createState() => _RouteExamplePageState();
}

class _RouteExamplePageState extends State<RouteExamplePage>
    with TickerProviderStateMixin {
  late final ManaraMapController _mapController;
  StreamSubscription<(LocationEntity?, LocationEntity?)>?
      _riderLocationSubscription;

  late LocationEntity _pickup;

  /// The consumer's own current device location — where the rider is headed.
  /// Fetched once (not streamed like [_pickup]/the rider's position): unlike
  /// the rider, the consumer isn't expected to be moving while waiting for
  /// the delivery. Null until resolved.
  LocationEntity? _dropoff;

  String get className => runtimeType.toString();

  @override
  void initState() {
    super.initState();
    _pickup = LocationEntity(
      latitude: 31.467192334473534,
      longitude: 74.29420709609987,
    );
    _mapController = ManaraMapController(this);
    // The customer's side of the delivery: follow the rider's live position,
    // published by MapExamplePage through RiderLocationService.
    // _riderLocationSubscription =
    //     _riderLocationService.streamRiderLocation().listen((data) {
    //   if (kDebugMode) log("Location ---> $data", name: runtimeType.toString());
    //   final (location, endLocation) = data;
    //   if (location != null) setState(() => _pickup = location);
    //   if (_dropoff != endLocation) setState(() => _dropoff = endLocation);
    // });
  }

  @override
  void dispose() {
    _mapController.dispose();
    _riderLocationSubscription?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final dropoff = _dropoff;
    return dropoff == null
        ? const Center(child: CircularProgressIndicator())
        : ManaraRouteMapWidget(
            apiKey: _apiKey,
            baseUrl: _baseUrl,
            tileUrl: _tileUrl,
            mapController: _mapController,
            startLocation: _pickup,
            endLocation: dropoff,
            onRouteFound: (route) {
              if (kDebugMode) {
                log('Route found with ${route.points.length} points',
                    name: className);
              }
            },
            onRouteError: (message) {
              if (kDebugMode) {
                log('Route error: $message', name: className);
              }
            },
            onRerouting: () {
              if (kDebugMode) {
                log('🔀 Off-route — rerouting...', name: className);
              }
            },
            onRerouted: (route) {
              if (kDebugMode) {
                log('✅ Rerouted with ${route.points.length} points',
                    name: className);
              }
            },
          );
  }
}
0
likes
140
points
267
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter package for OpenStreetMap-based maps with address search, location services, turn-by-turn directions with live rider tracking, and customizable UI.

Repository (GitHub)

License

BSD-3-Clause (license)

Dependencies

dio, flutter, flutter_bloc, flutter_map, flutter_map_animations, flutter_map_cancellable_tile_provider, freezed_annotation, geolocator, json_annotation, latlong2, package_info_plus, shimmer

More

Packages that depend on manara_map