Flutter Google Map Search Location | Auto Complete Address

We will learn how to search for location on Google map. Searching address or location is an intereactive way to engage with your users from your app. Most modern apps require access to the user or finding a location.

Make sure you know to get the Goolge Map Api Key. If not watch the video tutorial below

Install Dependecies

For this one to work, we will use the below packages.

First go ahead and create a project and get the packages below

  get: ^4.1.4
  http: any

  geolocator: ^7.1.0
  geocoding: ^2.0.0
  google_maps_flutter: ^2.0.6
  flutter_typeahead: ^3.1.3
  flutter_google_places: ^0.3.0

Set up the Api key for iOS

import UIKit
import Flutter
import GoogleMaps

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
  GMSServices.provideAPIKey("Your Key")

    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

Import Google Map at the top and set up the key.

Set up the Api key for Android

Set up Api Key for Android in AndroidManifest.xml file

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="Your package name">
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
   <application
        android:label="shopping_app"
       android:networkSecurityConfig="@xml/network_security_config"
       android:requestLegacyExternalStorage="true"
       android:usesCleartextTraffic="true"

       android:icon="@mipmap/ic_launcher">

        <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
 
            <meta-data
              android:name="io.flutter.embedding.android.NormalTheme"
              android:resource="@style/NormalTheme"
              />
           
            <meta-data
              android:name="io.flutter.embedding.android.SplashScreenDrawable"
              android:resource="@drawable/launch_background"
              />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
       <meta-data android:name="com.google.android.geo.API_KEY"
           android:value="Your Key"/>
    </application>
</manifest>

Create service

We will use real world api to build this project. First we need to create http client. For this reason go ahead and create class, name it location_service.dart and put the code below

import 'dart:convert';
import 'package:http/http.dart' as http;

  Future<http.Response> getLocationData(String text) async {
    http.Response response;

      response = await http.get(
        Uri.parse("http://mvs.bslmeiyu.com/api/v1/config/place-api-autocomplete?search_text=$text"),
          headers: {"Content-Type": "application/json"},);

    print(jsonDecode(response.body));
    return response;
  }

We are using http.get() request and we are just simply returning response. The method name is getLocationData() and it takes a parameter String type. The parameter would be sent to the google api for searching location.

The parameter would be passed from the caller function of this function. 

Location Controller

We need to create a controller for dealing with from view to the database. Here we are using MVC pattern to do it. We will use Getx package to manage the state of the project.

Let's create a class file name location_controller.dart and put the code there

import 'package:g_map/services/location_service.dart';
import 'package:get/get.dart';
import 'dart:convert';
import 'dart:io';

import 'package:flutter/cupertino.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:http/http.dart' as http;
import 'package:google_maps_webservice/src/places.dart';

class LocationController extends GetxController{

}

Since we are using Getx for state management, we are extending GetxController.

Let's declare some variables

  Placemark _pickPlaceMark = Placemark();
  Placemark get pickPlaceMark=>_pickPlaceMark;

  List<Prediction> _predictionList = [];

Put the above variables inside the location_controller.dart file. _pickPlaceMark would help us to get specific information based on the given string address to Placemark().

_predictionList would help us to save the predicted address(as you type in the address text box) or similar address.

Let's create a method inside the controller

Future<List<Prediction>> searchLocation(BuildContext context, String text) async {
    if(text != null && text.isNotEmpty) {
      http.Response response = await getLocationData(text);
      var data = jsonDecode(response.body.toString());
      print("my status is "+data["status"]);
      if ( data['status']== 'OK') {
        _predictionList = [];
        data['predictions'].forEach((prediction)
        => _predictionList.add(Prediction.fromJson(prediction)));
      } else {
        // ApiChecker.checkApi(response);
      }
    }
    return _predictionList;
  }

See here we are calling getLocationData() by passing an arguments to, we are passing a text to it. 

If we can get the data, then we put it a response object and then check the status. If the status is ok, then we go through a forEach loop using prediction( here prediction means the probable match of the address). And then add it in list that we declared early. 

It's also very important to call fromJson method of the Prediction class.

Map screen

Now we need to create a class or dart file for holding our ui. Create a dart file name map_screen.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:g_map/controllers/location_controller.dart';
import 'package:g_map/widgets/location_dalogue.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:get/get.dart';
class MapScreen extends StatefulWidget {
  const MapScreen({Key? key}) : super(key: key);

  @override
  State<MapScreen> createState() => _MapScreenState();
}

class _MapScreenState extends State<MapScreen> {
  late CameraPosition _cameraPosition;
  @override
  void initState(){
    super.initState();
    _cameraPosition=CameraPosition(target: LatLng(
      45.521563,-122.677433
    ), zoom: 17);
  }

  late GoogleMapController _mapController;
  @override
  Widget build(BuildContext context) {

    return GetBuilder<LocationController>(builder: (locationController){
      return Scaffold(
          appBar: AppBar(
            title: const Text('Maps Sample App'),
            backgroundColor: Colors.green[700],
          ),
          body: Stack(
            children: <Widget>[

              GoogleMap(
                  onMapCreated: (GoogleMapController mapController) {
                    _mapController = mapController;
                    locationController.setMapController(mapController);
                  },
                  initialCameraPosition: _cameraPosition
              ),
              Positioned(
                top: 100,
                left: 10, right: 20,
                child: GestureDetector(
                  onTap:() {
                 },
                  child: Container(
                    height: 50,
                    padding: EdgeInsets.symmetric(horizontal: 5),
                    decoration: BoxDecoration(color: Theme.of(context).cardColor,
                        borderRadius: BorderRadius.circular(10)),
                    child: Row(children: [
                      Icon(Icons.location_on, size: 25, color: Theme.of(context).primaryColor),
                      SizedBox(width: 5),
                      //here we show the address on the top
                      Expanded(
                        child: Text(
                          '${locationController.pickPlaceMark.name ?? ''} ${locationController.pickPlaceMark.locality ?? ''} '
                              '${locationController.pickPlaceMark.postalCode ?? ''} ${locationController.pickPlaceMark.country ?? ''}',
                          style: TextStyle(fontSize: 20),
                          maxLines: 1, overflow: TextOverflow.ellipsis,
                        ),
                      ),
                      SizedBox(width: 10),
                      Icon(Icons.search, size: 25, color: Theme.of(context).textTheme.bodyText1!.color),
                    ]),
                  ),
                ),
              ),
            ],
          )
      );
    },);
  }
}

Here we have initialized Google Map using CameraPosition. We also gave it LatLng. For this project we are keeping it simple. 

We just used onMapCreated and initialCameraPosition for the Google map properties.

We kept the onTap() method empty. Here we will use Get.dialogue to search address in the search bar.

Search dialogue box

A dialoge box should appear when we search for address or location from Google Map. We want to type in a address keyword and wanna get the response and show it in a drop down search box for auto complete. For this one we will use the plugin flutter_typeahead.

Create a file name called location_search_dialogue.dart and put the code below

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/location_controller.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:google_maps_webservice/places.dart';

class LocationSearchDialog extends StatelessWidget {
  final GoogleMapController? mapController;
  const LocationSearchDialog({required this.mapController});

  @override
  Widget build(BuildContext context) {
    final TextEditingController _controller = TextEditingController();

    return Container(
      margin: EdgeInsets.only(top : 150),
      padding: EdgeInsets.all(5),
      alignment: Alignment.topCenter,
      child: Material(
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
        child: SizedBox(width: 350, child: TypeAheadField(
          textFieldConfiguration: TextFieldConfiguration(
            controller: _controller,
            textInputAction: TextInputAction.search,
            autofocus: true,
            textCapitalization: TextCapitalization.words,
            keyboardType: TextInputType.streetAddress,
            decoration: InputDecoration(
              hintText: 'search_location',
              border: OutlineInputBorder(
                borderRadius: BorderRadius.circular(10),
                borderSide: BorderSide(style: BorderStyle.none, width: 0),
              ),
              hintStyle: Theme.of(context).textTheme.headline2?.copyWith(
                fontSize: 16, color: Theme.of(context).disabledColor,
              ),
              filled: true, fillColor: Theme.of(context).cardColor,
            ),
            style: Theme.of(context).textTheme.headline2?.copyWith(
              color: Theme.of(context).textTheme.bodyText1?.color, fontSize: 20,
            ),
          ),
          suggestionsCallback: (pattern) async {
            return await Get.find<LocationController>().searchLocation(context, pattern);
          },
          itemBuilder: (context, Prediction suggestion) {
            return Padding(
              padding: EdgeInsets.all(10),
              child: Row(children: [
                Icon(Icons.location_on),
                Expanded(
                  child: Text(suggestion.description!, maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.headline2?.copyWith(
                    color: Theme.of(context).textTheme.bodyText1?.color, fontSize: 20,
                  )),
                ),
              ]),
            );
          },
          onSuggestionSelected: (Prediction suggestion) {
            print("My location is "+suggestion.description!);
            //Get.find<LocationController>().setLocation(suggestion.placeId!, suggestion.description!, mapController);
            Get.back();
          },
        )),
      ),
    );
  }
}

Notice TypeAheadField. It works like a text controller. But it also does auto completion for you.

suggestionsCallback and itemBuilder are the most important. suggestionsCallback receives what you type in and sends to Google server and gets the data.

Later on the data is shown using itemBuilder property. It shows data in drop down list.

See how we connect the method searchLocation() from here. It takes context and string.

Now call MapScreen() class from main.dart

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  @override
  Widget build(BuildContext context) {
    Get.put(LocationController());
    return  const GetMaterialApp(
      debugShowCheckedModeBanner: false,
        home: MapScreen()
    );
  }
}

One more thing

Before it to work we need to put a new line of code in MapScreen(). Inside GestureDetector, use the onTap() like below

 onTap: () => Get.dialog(LocationSearchDialog(mapController: _mapController)),

Server side code

I am using Laravel as backend and here is the basic end point that flutter app points to. The main idea is that, you have to catch or retrieve the text information, that's send from Flutter front end.

      public function place_api_autocomplete(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'search_text' => 'required',
        ]);

        if ($validator->errors()->count()>0) {
            return response()->json(['errors' => Helpers::error_processor($validator)], 403);
        }
        $response = Http::get('https://maps.googleapis.com/maps/api/place/autocomplete/json?input='.$request['search_text'].'&key='.'Your key');
        return $response->json();
    }

Recent posts