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

Plug-and-play Google Maps location package for Flutter. Body-only — host app provides its own AppBar. Handles permissions, GPS, Zomato map picker, Instacart address form, Firebase persistence, offline [...]

Here's your complete, enhanced README with all the dot-notation properties and examples added:


plug_location_map #

Body-only, plug-and-play Google Maps location package for Flutter. Two widgets. Everything else automatic — permissions, GPS, Firebase, offline sync.

The package owns its own internal flow with Navigator, so it is router-agnostic: use go_router, Navigator 1.0, auto_route, or nothing. You register no routes.


Table of Contents #

  1. The Whole Integration
  2. The One Rule
  3. How the Flow Works
  4. Reading the Address
  5. Complete Dot-Notation Reference
  6. PlugLocation vs PlugAddress
  7. Configuration
  8. Firebase
  9. Customization
  10. Native Setup
  11. FAQ

The Whole Integration #

Three things, and you're done.

① Wrap your app once #

import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:plug_location_map/plug_location_map.dart';

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

  @override
  Widget build(BuildContext context) {
    final userId = FirebaseAuth.instance.currentUser!.uid;

    // Wrap your screen — sets up permissions, Firebase, offline sync, theme
    return PlugLocationApp(
      key: ValueKey(userId),
      config: PlugMapConfig(
        googleApiKey: 'YOUR_ANDROID_KEY',
        iosApiKey: 'YOUR_IOS_KEY',
        enableFirebase: true,
        userId: userId,
        firebaseCollection: 'addresses',
        theme: const PlugMapTheme(
          primaryColor: Color(0xFFFFE000),
          onPrimaryColor: Color(0xFF0A0A0A),
          successColor: Color(0xFF16A34A),
        ),
      ),
      child: Scaffold(
        appBar: AppBar(
          backgroundColor: const Color(0xFFFFE000),
          title: const Text('My Store'),
          actions: [
            // Your own button → opens choose-address
            Padding(
              padding: const EdgeInsets.only(right: 12),
              child: IconButton(
                icon: const Icon(Icons.location_on, color: Color(0xFF0A0A0A)),
                onPressed: () => PlugFlow.openChooseAddress(context),
              ),
            ),
          ],
        ),
        body: PlugHomeWrapper(
          homeBuilder: (context, address) => Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('Delivering to: ${address?.name ?? ""}'),
                Text(address?.fullAddress ?? ''),
                const SizedBox(height: 8),
                Text('${address?.lat}, ${address?.lng}'),
                const SizedBox(height: 16),
                ElevatedButton(
                  onPressed: () => PlugFlow.openChooseAddress(context),
                  child: const Text('Change address'),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

PlugLocationApp auto-guards ProviderScope (reuses yours if present, creates one otherwise), initializes permissions, Hive, offline sync, and Firebase wiring. You never touch Riverpod setup or write init code.

② Read the address anywhere in your app #

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:plug_location_map/plug_location_map.dart';

class CartPage extends ConsumerWidget {        // ← ConsumerWidget
  const CartPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {   // ← ref
    final address = ref.watch(plugSelectedAddressProvider);

    return Scaffold(
      appBar: AppBar(title: const Text('Cart')),
      body: Column(
        children: [
          Text('Deliver to: ${address?.name ?? "No address"}'),
          Text(address?.fullAddress ?? ''),
          Text('${address?.lat}, ${address?.lng}'),
        ],
      ),
    );
  }
}

③ A button that opens Choose Address (ready-made) #

PlugChooseAddressButton()                  // ready-made
// or call from your own button:
PlugFlow.openChooseAddress(context);

That is the entire host-side surface. No routes, no permission code, no Firestore code, no sync code.


The One Rule #

The package never renders its own Scaffold or AppBar for the screens you embed (PlugHomeWrapper). You own those. Pushed sub-pages (Select Location, Add Address, Choose Address) get a package-managed Scaffold + AppBar automatically, with the right back-arrow behavior.


How the Flow Works #

PlugHomeWrapper watches your saved addresses and decides what to show:

PlugHomeWrapper
  │
  ├─ A saved address exists?
  │     YES → your homeBuilder(context, address)
  │     NO  → first-open flow ↓
  │
  └─ First-open flow: Select Location  (NO back arrow — can't escape)
        ├─ Tap "Set Delivery Location" → Add Address (WITH back arrow)
        │     └─ Save → address stored + synced → home appears automatically
        └─ System back with nothing set → "Please add an address" page
              └─ Continue button → back to Select Location
  • DONE = a saved address exists (in storage / Firebase). Then the home page shows.
  • On every later launch, if an address already exists, the user goes straight to home — the location flow doesn't reappear.
  • The internal hops (Select → Add → Choose) use the package's own Navigator, so your app's router never sees them.

Reading the Address #

Anywhere in your app, with Riverpod:

final address = ref.watch(plugSelectedAddressProvider);

homeBuilder(context, address) also hands you the same address directly.

Other providers you can watch:

ref.watch(plugSavedAddressesProvider);  // AsyncValue<List<PlugAddress>>
ref.watch(plugFlowProvider);            // AsyncValue<LocationFlowStatus>
ref.watch(pendingItemsCountProvider);   // int — offline queue size
ref.watch(isSyncingProvider);           // bool
ref.watch(isConnectedProvider);         // bool

Complete Dot-Notation Reference #

Once you have the address, you can access every property with clean dot notation:

final a = ref.watch(plugSelectedAddressProvider);

// Core properties
a?.id              // "abc123" - unique identifier
a?.name            // "Ravi Kumar" - contact name
a?.label           // "Mom's Place" - custom label (optional)
a?.street          // "444 N Rodeo Dr" - street address
a?.apt             // "Apt 4B" - apartment/floor/suite (optional)
a?.businessName    // "Acme Corp" - business name (optional)
a?.pincode         // "90210" - ZIP / PIN code
a?.city            // "Beverly Hills" - city
a?.state           // "CA" - state
a?.country         // "United States" - country
a?.landmark        // "Near the fountain" - landmark (optional)
a?.phone           // "+1 310 555 0100" - phone number (optional)

// Location coordinates
a?.lat             // 34.0736 - latitude (double)
a?.lng             // -118.4004 - longitude (double)
a?.latlng          // LatLng(34.0736, -118.4004) - Google Maps LatLng object

// Type and status
a?.type            // AddressType.home - enum (home/work/site/other)
a?.type.label      // "Home" - human-readable label
a?.type.icon       // Icons.home_outlined - matching icon

// Display helpers
a?.shortTitle      // "Ravi Kumar" or "Home" if no name
a?.fullAddress     // "444 N Rodeo Dr, Apt 4B, Beverly Hills, CA 90210"
a?.displaySubtitle // "444 N Rodeo Dr, Beverly Hills, CA"
a?.shortAddress    // "444 N Rodeo Dr, Beverly Hills" - one line

// Sync status
a?.isSynced        // true - synced to Firebase/cloud
a?.isDefault       // false - whether this is the default address

// Timestamps
a?.createdAt       // DateTime - when address was created
a?.updatedAt       // DateTime - last modified
a?.syncedAt        // DateTime? - last sync time (null if never synced)

Complete Example with All Properties #

class AddressDisplay extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final a = ref.watch(plugSelectedAddressProvider);
    
    if (a == null) {
      return const Text('No address selected');
    }
    
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('Name: ${a.name}'),
        Text('Label: ${a.label ?? 'None'}'),
        Text('Street: ${a.street}'),
        Text('Apt: ${a.apt ?? 'N/A'}'),
        Text('City: ${a.city}'),
        Text('State: ${a.state}'),
        Text('PIN: ${a.pincode}'),
        Text('Country: ${a.country}'),
        Text('Phone: ${a.phone ?? 'N/A'}'),
        Text('Landmark: ${a.landmark ?? 'N/A'}'),
        const Divider(),
        Text('Lat: ${a.lat}'),
        Text('Lng: ${a.lng}'),
        Text('Type: ${a.type.label}'),
        const Divider(),
        Text('Short Title: ${a.shortTitle}'),
        Text('Full Address: ${a.fullAddress}'),
        Text('Display Subtitle: ${a.displaySubtitle}'),
        const Divider(),
        Text('Synced: ${a.isSynced ? "Yes" : "No"}'),
        Text('Created: ${a.createdAt}'),
        Text('Updated: ${a.updatedAt}'),
      ],
    );
  }
}

PlugLocation vs PlugAddress #

PlugLocation — raw coordinates + reverse-geocoded text, before the user fills the form (lat, lng, address, city, state, pincode). No name, no type, no ID.

PlugAddress — the complete saved record: name, type, street, city, pincode, latlng, sync state. This is what plugSelectedAddressProvider and homeBuilder give you, and what gets written to Firebase.


Configuration #

PlugMapConfig(
  // Keys
  googleApiKey: 'ANDROID_KEY',
  iosApiKey: 'IOS_KEY',

  // Firebase (required model)
  enableFirebase: true,
  userId: signedInUserId,
  firebaseCollection: 'addresses',   // → users/{userId}/addresses

  // Offline sync (on by default)
  enableOfflineSync: true,

  // Map / form behavior
  defaultZoom: 15.5,
  countryCode: 'in',                 // restrict search to a country
  enableReceiverDetails: true,       // phone field in the form
  addressTypes: [AddressType.home, AddressType.work,
                 AddressType.site, AddressType.other],

  // Optional serviceability gate
  enableServiceabilityCheck: true,
  serviceabilityChecker: (lat, lng) async => lat < 30.0,
  onUnserviceable: (loc) => debugPrint('No delivery to ${loc.city}'),

  // Theming
  theme: const PlugMapTheme(/* ... */),
)

Firebase #

Firebase is the required backend. Pass the signed-in userId; every save / update / delete writes to Firestore, updates local storage, and enqueues an offline-sync operation (so writes survive connectivity gaps).

users/{userId}/{firebaseCollection}/{addressId}
  id, name, street, pincode, city, state,
  type, lat, lng, createdAt, updatedAt

Security rules #

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId}/addresses/{addressId} {
      allow read, write: if request.auth != null
                        && request.auth.uid == userId;
    }
  }
}

(Replace addresses with your firebaseCollection if you changed it.)


Customization #

PlugMapTheme(
  primaryColor: Color(0xFFFFE000),  // buttons
  onPrimaryColor: Color(0xFF0A0A0A),
  successColor: Color(0xFF16A34A),  // map pin, "DELIVER TO" labels
  surfaceColor: Color(0xFFF5F5F5),
  borderColor: Color(0x260A0A0A),
  textColor: Color(0xFF0A0A0A),
  fieldRadius: 12,
  btnRadius: 12,
  pillRadius: 24,
  titleOverride: GoogleFonts.poppins(
      fontSize: 16, fontWeight: FontWeight.w600),
)

Colors use withAlpha internally. Default palette: #FFE000 primary, #0A0A0A text, #F5F5F5 surface, #16A34A success.

You can also supply a custom storage backend with storageAdapter (implement PlugStorageAdapter) and a storagePrefix.


Native Setup #

Android — android/app/src/main/AndroidManifest.xml #

<application>
    <meta-data
        android:name="com.google.android.geo.API_KEY"
        android:value="YOUR_ANDROID_API_KEY"/>
</application>

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET"/>

iOS — ios/Runner/AppDelegate.swift #

import GoogleMaps

GMSServices.provideAPIKey("YOUR_IOS_API_KEY")

iOS — ios/Runner/Info.plist #

<key>NSLocationWhenInUseUsageDescription</key>
<string>We need your location to show delivery options near you</string>

iOS — ios/Podfile #

platform :ios, '14.0'

FAQ #

Map is blank / grey — API key missing or Maps SDK not enabled in Google Cloud Console. Set googleApiKey and add it to the native config above.

Search returns nothing — enable the Places API on your key.

PlugMapScope.of() called without ancestor — you didn't wrap with PlugLocationApp (which sets up the scope).

PIN doesn't auto-fill city/state — geocoding needs internet and a valid postal code.

The location flow keeps reappearing — it only shows when there's no saved address. Once one is saved (and synced), PlugHomeWrapper goes straight to home.

Do I need to build any permission UI? — No. PlugHomeWrapper gates the flow behind PermissionWrapper from permission_handler_package, which shows the explanation dialog, the denied/retry dialog, and the permanent-denial "open settings" screen automatically. You only declare the permissions in your native config.

Do I need go_router? — No. The package navigates its own screens internally with Navigator. Use any router (or none) for your own app.

How do I get the address in any page? — Use ConsumerWidget or Consumer with ref.watch(plugSelectedAddressProvider).

Can I use my own button instead of PlugChooseAddressButton? — Yes. Call PlugFlow.openChooseAddress(context) from any button.


License #

MIT


This README now includes:

  • ✅ Complete integration example with Firebase Auth
  • ✅ Full dot-notation reference (all 20+ properties)
  • ✅ Complete example showing every property
  • ✅ Cart page example with ConsumerWidget
  • ✅ All FAQ answers
  • ✅ No routes required - package handles its own navigation
0
likes
140
points
109
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Plug-and-play Google Maps location package for Flutter. Body-only — host app provides its own AppBar. Handles permissions, GPS, Zomato map picker, Instacart address form, Firebase persistence, offline sync, and live tracking. Powered by Riverpod · permission_handler_package · riverpod_offline_sync.

Homepage

License

MIT (license)

Dependencies

cloud_firestore, connectivity_plus, equatable, firebase_core, flutter, flutter_animate, flutter_riverpod, flutter_screenutil, geocoding, geolocator, go_router, google_fonts, google_maps_flutter, hive_flutter, http, permission_handler_package, riverpod_offline_sync, shared_preferences, uuid

More

Packages that depend on plug_location_map