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
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
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 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>
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.
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.
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.
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)),
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();
}