google_maps_places_autocomplete_widgets 1.2.3 copy "google_maps_places_autocomplete_widgets: ^1.2.3" to clipboard
google_maps_places_autocomplete_widgets: ^1.2.3 copied to clipboard

TextField and TextFormField widgets using Google Maps Places Api for Address Autocompletion.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_maps_places_autocomplete_widgets/address_autocomplete_widgets.dart';
import '/privatekeys.dart';

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

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Google Address AutoComplete Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: DefaultTabController(
        length: 2, 
        child: Scaffold(
        appBar: AppBar(
          bottom: const TabBar(
            tabs: [
              Tab(text: 'TextField'),
              Tab(text: 'TextFormField'),
            ],
          ),
          title: const Center( child:Text('Google_Map_Places_AutoComplete_Widgets Demo', style:TextStyle(fontSize:16,fontWeight:FontWeight.bold))),
        ),
        body: const TabBarView(
            children: [
              AddressAutocompleteTextFieldExample(title: 'TextField Example'),
              AddressAutocompleteTextFormFieldExample(),
            ],
          ),
        ),
      ),        
    );
  }
}

class AddressAutocompleteTextFieldExample extends StatefulWidget {
  const AddressAutocompleteTextFieldExample({super.key, required this.title});

  final String title;

  @override
  State<AddressAutocompleteTextFieldExample> createState() => _AddressAutocompleteTextFieldExampleState();
}

class _AddressAutocompleteTextFieldExampleState extends State<AddressAutocompleteTextFieldExample> {
  String? _suggestionPlaceId;
  String? _suggestionDescription;
  String? _name;
  String? _formattedAddress;
  String? _formattedAddressZipPlus4;
  String? _streetAddress;
  String? _streetNumber;
  String? _street;
  String? _streetShort;
  String? _city;
  String? _county;
  String? _state;
  String? _stateShort;
  String? _zipCode;
  String? _zipCodeSuffix;
  String? _zipCodePlus4;
  String? _vicinity;
  String? _country;
  double? _lat;
  double? _lng;

  // write optional function to determine what is placed in the textfield
  // control when the user chooses an address.  The default is the `formattedAddress`
  String? onSuggestionClickGetTextToUseForControl(Place placeDetails) {
    //  we just want the street address here, for example if we were using
    //  the address line of the form to trigger the address autocomplete
    return placeDetails.streetAddress;
  }


  String? onSuggestionClickFillZipCodeControl(Place placeDetails) {
    //  we just want the zipCodePlus4, for example if we were using
    //  the zipcode field controll in the form to trigger the zipcode
    //  autocomplete
    return placeDetails.zipCodePlus4;
  }

  // This is called Immediatelly when user clicks suggestion and BEFORE
  // the async request to get place
  void onInitialSuggestionClick(Suggestion suggestion) {
    debugPrint('onInitialSuggestionClick( suggestion:$suggestion )');
    setState(() {
      _suggestionDescription = suggestion.description;
      _suggestionPlaceId = suggestion.placeId;
      // clear these until they are loaded...
      _name = 
        _formattedAddress =
        _formattedAddressZipPlus4 =
        _streetAddress = 
        _streetNumber =
        _street =
        _streetShort = 
        _city = 
        _county = 
        _state = 
        _stateShort = 
        _zipCode = 
        _zipCodeSuffix = 
        _zipCodePlus4 = 
        _country = 
        _vicinity =  '....';
      _lat = 
        _lng = 0.0;
    });

  }

  // write a function to receive the place details callback
  void onSuggestionClick(Place placeDetails) {
    debugPrint('onSuggestionClick( placeDetails:$placeDetails )');
    setState(() {
      _name = placeDetails.name;
      _formattedAddress = placeDetails.formattedAddress;
      _formattedAddressZipPlus4 = placeDetails.formattedAddressZipPlus4;
      _streetAddress = placeDetails.streetAddress;
      _streetNumber = placeDetails.streetNumber;
      _street = placeDetails.street;
      _streetShort = placeDetails.streetShort;
      _city = placeDetails.city;
      _county = placeDetails.county;
      _state = placeDetails.state;
      _stateShort = placeDetails.stateShort;
      _zipCode = placeDetails.zipCode;
      _zipCodeSuffix = placeDetails.zipCodeSuffix;
      _zipCodePlus4 = placeDetails.zipCodePlus4;
      _country = placeDetails.country;
      _vicinity = placeDetails.vicinity;
      _lat = placeDetails.lat;
      _lng = placeDetails.lng;
    });
  }

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
          child: Center(
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.start,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[

                // Postalcode lookup TextField example
                const Text('Example of ZipCode/Postal Code lookup:'),
                SizedBox(
                    height: 60,
                    child: AddressAutocompleteTextField(
                      postalCodeLookup: true,
                      keyboardType: TextInputType.number,
                      maxLength: 5,
                      maxLengthEnforcement: MaxLengthEnforcement.enforced,
                      style: const TextStyle(color: Colors.purple, fontSize:16, fontWeight: FontWeight.bold),
                      // create a `privatekeys.dart` file and add your API key there 
                      //   `const GOOGLE_MAPS_ACCOUNT_API_KEY = 'YourGoogleMapsApiKey_XXXXyyyzzzz';`
                      // the .gitignore file is set so this does not go into source repository.
                      mapsApiKey: GOOGLE_MAPS_ACCOUNT_API_KEY, 

                      // optional callback arg for use when you do not want `formattedAddress`
                      // to be used to fill the autocomplete textfield when an address is chosen.
                      onSuggestionClickGetTextToUseForControl: onSuggestionClickFillZipCodeControl,
                      onInitialSuggestionClick: onInitialSuggestionClick,
                      onSuggestionClick: onSuggestionClick,
                      hoverColor: Colors.blue,  // for desktop platforms with mouse
                      selectionColor: Colors.green, // for desktop platforms with mouse
                      onFinishedEditingWithNoSuggestion: (text) {
                        // you should invalidate the last entry of onSuggestionClick if you really need a valid location,
                        // otherwise decide what to do based on what the user typed, can be an empty string
                        debugPrint('onFinishedEditingWithNoSuggestion()  text typed: $text');
                      },
                      buildItem: (Suggestion suggestion, int index) {
                        return Container(
                          margin: const EdgeInsets.fromLTRB(2, 2, 2, 2),  //<<This area will get hoverColor/selectionColor on desktop
                          padding: const EdgeInsets.all(8),
                          alignment: Alignment.centerLeft,
                          color: const Color.fromARGB(255, 220, 220, 220),
                          child: Text(suggestion.description,
                                  style:const TextStyle(color:Colors.blueGrey,
                                              fontSize: 16,
                                              fontWeight: FontWeight.bold)
                                  )
                        );
                      },
                      clearButton: const Icon(Icons.close),
                      componentCountry: 'us',
                      language: 'en-Us',
                      decoration: const InputDecoration(
                        contentPadding: EdgeInsets.all(8),
                        border: OutlineInputBorder(
                            borderSide: BorderSide(color: Colors.white)),
                        hintText:
                            'Enter Zipcode to lookup',
                        //errorText: 'Zip code must be 5 digits max',
                        //errorStyle: TextStyle(color:Colors.red),
                        ),

                    ),
                  ),

                  // Address lookup TextField example
                  const Text('Example of address lookup:'),
                  SizedBox(
                    height: 40,
                    child: AddressAutocompleteTextField(

                      // create a `privatekeys.dart` file and add your API key there 
                      //   `const GOOGLE_MAPS_ACCOUNT_API_KEY = 'YourGoogleMapsApiKey_XXXXyyyzzzz';`
                      // the .gitignore file is set so this does not go into source repository.
                      mapsApiKey: GOOGLE_MAPS_ACCOUNT_API_KEY, 

                      // optional callback arg for use when you do not want `formattedAddress`
                      // to be used to fill the autocomplete textfield when an address is chosen.
                      onSuggestionClickGetTextToUseForControl: onSuggestionClickGetTextToUseForControl,
                      onInitialSuggestionClick: onInitialSuggestionClick,
                      onSuggestionClick: onSuggestionClick,
                      onFinishedEditingWithNoSuggestion: (text) {
                        // you should invalidate the last entry of onSuggestionClick if you really need a valid location,
                        // otherwise decide what to do based on what the user typed, can be an empty string
                        debugPrint('onFinishedEditingWithNoSuggestion()  text typed: $text');
                      },
                      hoverColor: Colors.purple,  // for desktop platforms with mouse
                      selectionColor: Colors.red, // for desktop platforms with mouse
                      buildItem: (Suggestion suggestion, int index) {
                        return Container(
                          margin: const EdgeInsets.fromLTRB(1, 1, 1, 1), //<<This area will get hoverColor/selectionColor on desktop
                          padding: const EdgeInsets.all(8),
                          alignment: Alignment.centerLeft,
                          color: Colors.white,
                          child: Text(suggestion.description)
                        );
                      },
                      clearButton: const Icon(Icons.close),
                      componentCountry: 'us',
                      language: 'en-Us',
                      decoration: const InputDecoration(
                        contentPadding: EdgeInsets.all(8),
                        border: OutlineInputBorder(
                            borderSide: BorderSide(color: Colors.white)),
                        hintText:
                            "Address",
                        errorText: null),
                    ),
                  ),
                  /*********/
        
        
                  /******** */
                  // Use the details from callback
                  Container(
                    padding: const EdgeInsets.all(8),
                    child: 
                      DefaultTextStyle.merge(
                        style: const TextStyle(
                          fontSize: 12,
                          fontWeight: FontWeight.normal,
                        ),
                    child:Column(
                      mainAxisAlignment: MainAxisAlignment.start,
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text('Suggestion PlaceId: ${_suggestionPlaceId?? '---'}'),
                        Text('Suggestion Description: ${_suggestionDescription?? '---'}'),
                        Text('Name: ${_name?? '---'}'),
                        Text('FormattedAddress: ${_formattedAddress?? '---'}'),
                        Text('FormattedAddressZipPlus4: ${_formattedAddressZipPlus4?? '---'}'),
                        Text('StreetAddress: ${_streetAddress?? '---'}'),
                        Text('StreetNumber: ${_streetNumber ?? '---'}'),
                        Text('Street: ${_street ?? '---'}'),
                        Text('StreetShort: ${_streetShort ?? '---'}'),
                        Text('Vicinity: ${_vicinity ?? '---'}'),
                        Text('City: ${_city ?? '---'}'),
                        Text('County: ${_county ?? '---'}'),
                        Text('State: ${_state ?? '---'}'),
                        Text('StateShort: ${_stateShort ?? '---'}'),
                        Text('Country: ${_country ?? '---'}'),
                        Text('ZipCode: ${_zipCode ?? '---'}'),
                        Text('ZipCodeSuffix: ${_zipCodeSuffix ?? '---'}'),
                        Text('ZipCodePlus4: ${_zipCodePlus4 ?? '---'}'),
                        Text('Latitude: ${_lat ?? '---'}'),
                        Text('Longitude: ${_lng ?? '---'}'),
                      ]),
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
  }
}


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

  @override
  State<AddressAutocompleteTextFormFieldExample> createState() => _AddressAutocompleteTextFormFieldExampleState();
}

class _AddressAutocompleteTextFormFieldExampleState extends State<AddressAutocompleteTextFormFieldExample> {
  final _formKey = GlobalKey<FormState>();
  late FocusNode addressFocusNode;
  late FocusNode cityFocusNode;
  late FocusNode stateFocusNode;
  late FocusNode zipFocusNode;

  late TextEditingController addressTextController;
  late TextEditingController cityTextController;
  late TextEditingController stateTextController;
  late TextEditingController zipTextController;

  @override
  void initState() {
    debugPrint('_LocationInformationPageState() init()');

    super.initState();

    addressFocusNode = FocusNode();
    cityFocusNode = FocusNode();
    stateFocusNode = FocusNode();
    zipFocusNode = FocusNode();

    addressTextController = TextEditingController();
    cityTextController = TextEditingController();
    stateTextController = TextEditingController();
    zipTextController = TextEditingController();
  }

  @override
  void dispose() {
    addressFocusNode.dispose();
    cityFocusNode.dispose();
    stateFocusNode.dispose();
    zipFocusNode.dispose();

    addressTextController.dispose();
    cityTextController.dispose();
    stateTextController.dispose();
    zipTextController.dispose();
    
    super.dispose();
  }

  // This callback returns what we want to be put into the text control
  // when they choose an address
  String? onSuggestionClickGetTextToUseForControl(Place placeDetails) {
    String? forOurAddressBox = placeDetails.streetAddress;
    if(forOurAddressBox==null || forOurAddressBox.isEmpty) {
      forOurAddressBox = placeDetails.streetNumber ?? '';
      forOurAddressBox += (forOurAddressBox.isNotEmpty ? ' ' : '');
      forOurAddressBox += placeDetails.streetShort ?? '';
    }
    return forOurAddressBox;
  }

  /// This method really does not seem to help...
  /// But i left the ability in because it might help more in 
  /// countries other than the united states.
  String prepareQuery(String baseAddressQuery) {
    debugPrint('prepareQuery() baseAddressQuery=$baseAddressQuery');
    String built = baseAddressQuery;
    String city = cityTextController.text;
    String state = stateTextController.text;
    String zip = zipTextController.text;
    if(city.isNotEmpty) {
      built += ', $city';
    }
    if(state.isNotEmpty) {
      built += ', $state';
    }
    if(zip.isNotEmpty) {
      built += ' $zip';
    }
    built += ', USA';
    debugPrint('prepareQuery made built="$built"');
    return built;
  }

  void onClearClick() {
    debugPrint('onClearClickInAddressAutocomplete() clearing form');
    addressTextController.clear();
    cityTextController.clear();
    stateTextController.clear();
    zipTextController.clear();

    if(!addressFocusNode.hasFocus) {
      // if address does not have focus unfocus everything to close keyboard
      // and show the cleared form.
      FocusScopeNode currentFocus = FocusScope.of(context);
      currentFocus.unfocus();
    }
 }

  // This gets us an IMMEDIATE form fill but address has no abbreviations
  // and we must wait for the zipcode.
  void onInitialSuggestionClick(Suggestion suggestion) {
    final description = suggestion.description;

    debugPrint('onInitialSuggestionClick()  description=$description');
    debugPrint('  suggestion = $suggestion');
    /* COULD BE USED TO SHOW IMMEDIATE values in form
       BEFORE PlaceDetails arrive from API

    var parts = description.split(',');

    if(parts.length<4) {
      parts = [ '','','','' ];
    }
    addressTextController.text = parts[0];
    cityTextController.text = parts[1];
    stateTextController.text = parts[2].trim();
    zipTextController.clear(); // we wont have zip until details come thru
    */
  }

  // write a function to receive the place details callback
  void onSuggestionClick(Place placeDetails) {
    debugPrint('onSuggestionClick() placeDetails:$placeDetails');

    cityTextController.text = placeDetails.city ?? '';
    stateTextController.text = placeDetails.state ?? '';
    zipTextController.text = placeDetails.zipCode ?? '';
  }

  InputDecoration getInputDecoration(String hintText) {
    return InputDecoration(
                filled: true,
                fillColor: Colors.white,
                hintText: hintText,
                hintStyle: const TextStyle(color:Colors.grey),
                focusedBorder: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(10.0),
                  borderSide: const BorderSide(
                    color: Colors.purple,
                  ),
                ),
                enabledBorder: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(10.0),
                  borderSide: const BorderSide(
                    color: Colors.black12,
                    width: 1.0,
                  ),
                ),
              );
  }

  @override
  Widget build(BuildContext context) {
    return
    SingleChildScrollView(
      child: Center(
      child: Padding(
      padding: const EdgeInsets.all(8.0),
      child: Form(
      key: _formKey,
      child: Column(
      children: <Widget>[
        const LeftAlignedLabelRow('Address'),
        AddressAutocompleteTextFormField(
              // following args specific to AddressAutocompleteTextFormField()
              mapsApiKey: GOOGLE_MAPS_ACCOUNT_API_KEY,
              debounceTime: 200,
              //In practice this does not seem to help United States address//prepareQuery: prepareQuery,
              onClearClick: onClearClick,
              onSuggestionClickGetTextToUseForControl: onSuggestionClickGetTextToUseForControl,
              onInitialSuggestionClick: onInitialSuggestionClick,
              onSuggestionClick: onSuggestionClick,
              onFinishedEditingWithNoSuggestion: (text) {
                // you should invalidate the last entry of onSuggestionClick if you really need a valid location,
                // otherwise decide what to do based on what the user typed, can be an empty string
                debugPrint('onFinishedEditingWithNoSuggestion()  text typed: $text');
              },
              hoverColor: Colors.purple,  // for desktop platforms with mouse
              selectionColor: Colors.purpleAccent, // for desktop platforms with mouse
              buildItem: (Suggestion suggestion, int index) {
                return Container(
                  margin: const EdgeInsets.fromLTRB(2, 2, 2, 2), //<<This area will get hoverColor/selectionColor on desktop
                  padding: const EdgeInsets.all(8),
                  alignment: Alignment.centerLeft,
                  color: Colors.white,
                  child: Text(suggestion.description, style:const TextStyle(fontSize:14, fontWeight:FontWeight.bold))
                );
              },
              clearButton: const Icon(Icons.close),
              componentCountry: 'us',
              language: 'en-Us',

              // 'normal' TextFormField arguments:
              autofocus: true,
              scrollPadding: EdgeInsets.only(
                  bottom: MediaQuery.of(context).viewInsets.bottom),
              autovalidateMode: AutovalidateMode.disabled,
              keyboardType: TextInputType.streetAddress,
              textCapitalization: TextCapitalization.words,
              textInputAction: TextInputAction.next,
              onEditingComplete: () {
                debugPrint('onEditingComplete() for TextFormField');
              },
              onChanged: (newText) {
                debugPrint('onChanged() for TextFormField got "$newText"');
              },
              style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
              decoration: getInputDecoration('Start typing address for Autocomplete..'),
            ),
      const LeftAlignedLabelRow('City'),
      TextFormField(
        controller: cityTextController,
        focusNode: cityFocusNode,
        style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
        decoration: getInputDecoration('New York'),
        validator: (value) {
          // validation logic
          return null;
        },
      ),
      const LeftAlignedLabelRow('State'),
      TextFormField(
        controller: stateTextController,
        focusNode: stateFocusNode,
        style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
        decoration: getInputDecoration('NY'),
        validator: (value) {
          // validation logic
          return null;
        },
      ),
      const LeftAlignedLabelRow('Zip'),
      TextFormField(
        controller: zipTextController,
        focusNode: zipFocusNode,
        style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
        decoration: getInputDecoration('10001'),
        validator: (value) {
          // validation logic
          return null;
        },
      ),
      ],
      ),
    ),),),);
  }
}
class LeftAlignedLabelRow extends StatelessWidget {
  final String label;
  final double fontSize;
  final FontWeight fontWeight;

  const LeftAlignedLabelRow(this.label,
      {this.fontSize = 16.0, this.fontWeight = FontWeight.normal, super.key});

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.start,
      children: <Widget>[
        Text(
          label,
          style: TextStyle(fontSize: fontSize, fontWeight: fontWeight),
        ),
      ],
    );
  }
}
12
likes
140
points
1.29k
downloads

Publisher

verified publisherhiveright.tech

Weekly Downloads

TextField and TextFormField widgets using Google Maps Places Api for Address Autocompletion.

Repository (GitHub)
View/report issues

Documentation

API reference

License

Apache-2.0 (license)

Dependencies

flutter, http, uuid

More

Packages that depend on google_maps_places_autocomplete_widgets