Using Packages in Flutter

Packages are certainly one of the greatest features that Flutter offers. Both the Flutter team and third-party developers add and maintain packages to the Flutter ecosystem daily. This makes building apps much faster and more reliable, as you can focus on the specific features of your app while leveraging classes and functions that have been created and tested by other developers.

Packages are published on the following website: https://pub.dev. This is the hub where you can go to search for packages, verify their platform compatibility (iOS, Android, web, and desktop), their popularity, versions, and use cases. Chances are that you’ve already used pub.dev several times before reading this tutorial.

Here is the current home of the pub.dev repository, with the search box at the center of the screen: 

In this tutorial, we will cover the following topics:

  • Importing packages and dependencies
  • Creating your own package (part 1)
  • Creating your own package (part 2)
  • Creating your own package (part 3)
  • Adding Google Maps to your app
  • Using location services
  • Adding markers to a map

By the end of this tutorial, you will be able to use packages, create and publish your own package, and integrate the Google Maps plugin into your app.

Technical requirements

To follow along with the recipes in this tutorial, you should have the following software installed on your Windows, Mac, Linux, or Chrome OS device:

  • The Flutter SDK.
  • The Android SDK when developing for Android
  • macOS and Xcode when developing for iOS. 
  • An emulator or simulator, or a connected mobile device enabled for debugging.
  • Your favorite code editor: Android Studio, Visual Studio Code, and IntelliJ IDEA are recommended. All should have the Flutter/Dart extensions installed.

Importing packages and dependencies

This recipe shows how to get packages and plugins from https://pub.dev and integrate them into your app’s pubspec.yaml file.

Specifically, you will retrieve the version and package name from pub.dev, import a package into the pubspec.yaml file in your project, download the package, and use it in your classes. 

By the end of this recipe, you will know how to import any package available in the pub.dev hub into your apps.

Getting ready

In this recipe, you will create a new project. There are no prerequisites to follow along.

How to do it…

When you install a package from pub.dev, the process is very simple. For this example, we will install the http package, and connect to a Git repository:

  1. Create a new Flutter project, called plugins.
  2. Go to https://pub.dev.
  3. In the search box, type http.
  4. Click on the http package on the results page.
  5. From the http package’s home page, click on the Installing button.
  6. Copy the dependencies version in the Depend on it section at the top of the page.
  7. Open the pubspec.yaml file in your project.
  8. Paste the http dependency into the dependencies section of your pubspec.yaml file. Your dependencies should look like the following code (the http version number may be different by the time you read this). Make sure that the alignment is precisely as shown here, where http is exactly under flutter:
dependencies:
flutter:
sdk: flutter
http: ^0.13.1
  1. To download the package, the actions depend on your system. In the Terminal, type flutter pub get. This will initiate downloading of the package.
  2. In Visual Studio Code, press the Get Packages button, in the top-right corner of the screen, or, from the command palette, execute the Pub: Get Packages command, as shown here (you can also just save the pubspec.yaml file and wait a few seconds. The download should start automatically):
  1. In Android Studio/IntelliJ Idea, click the Packages get button at the top right of the window, as shown in the following screenshot:

How it works…

pub.dev is the main resource where packages and plugins are published. You can look for packages or publish them for other users. A few very important points to note are the following:

  • All packages are open source.
  • Once published, packages cannot ever be removed.
  • All packages can only depend on other published packages.

All these rules benefit the end user. If you use a package from pub.dev, you always know that your packages will remain available. They can be used without any limitations, and do not have hidden dependencies that follow different rules.

The pubspec.yaml file is written in YAML, which is a language mainly used for configurations. It is a superset of JSON, with key-value pairs. The most important feature to understand in yaml is that it uses indentation for nesting, so you need to pay attention to how you use indentation and spacing, as it may raise unexpected errors when used inappropriately.

The following command imports the http package into the project. It uses the caret syntax for the version number:

http: ^0.13.1

Once you import the package, you can use its properties and methods anywhere in your project. The http package, for instance, allows you to connect to web services from your app, using the http or https protocols. Whenever you want to use the package that you have added to the pubspec.yaml file, you also need to import it into the file where you are using the package with an import statement, such as the following:

import 'package:http/http.dart';

The three numbers of the version are denoted as MAJOR.MINOR.PATCH, where breaking changes happen only in major releases, after version 1.0.0. For versions before 1.0.0, breaking changes can happen at every minor release.

So, when you write ^0.13.1, this means that any version is equal to or bigger than 0.13.1 and lower than 0.14.0.

When you write ^1.0.0, this means that any version is equal to or bigger than 1.0.0 and lower than 2.0.0.Tip: When you import packages, you usually need to find the latest version of the package in the pub.dev repository. You should also update and solve dependency issues from time to time. There are tools that may help you save a lot of time in adding and updating dependencies. If you are using Visual Studio Code, you can install the Pubspec Assist Plugin, available at the following link: https://marketplace.visualstudio.com/items?itemName=jeroen-meijer.pubspec-assist.
If you are using Android Studio or Intellij Idea, you can add the Flutter Enhancement tools, available at https://plugins.jetbrains.com/plugin/12693-flutter-enhancement-suite. Both tools allow your dependencies to be added and updated easily, without leaving your editor.

After adding a package to the dependencies section of the pubspec.yaml file, you can download them manually from the terminal, with the flutter pub get command, or by pressing your editor’s Get button. This step might not be necessary for VS Code and Android Studio, as both can be configured to automatically get the packages when you update the pubspec.yaml file.

See also

Choosing the best packages and plugins for your apps may not always be simple. That’s why the Flutter team created the Flutter Favorite program, to help developers identify the packages and plugins that should be considered first when creating an app. For details about the program, refer to the following link: https://flutter.dev/docs/development/packages-and-plugins/favorites

Creating your own package (part 1)

While using packages made by other developers can really boost your app creation speed, sometimes you need to create your own packages. Some of the main reasons for creating a new package are as follows:

  • Modularity
  • Code reuse
  • Low-level interaction with a specific environment

Packages help you write modular code, as you can include several files and dependencies in a single package, and just depend on it in your app. At the same time, code reuse is made extremely simple, as packages can be shared among different apps. Also, when you make changes to a package, you only need to make them in one place, and they will automatically cascade to all the apps that point to that package.

There is a special type of package, called a plugin, that contains platform-specific implementations, for iOS, Android, and other systems. You generally create a plugin when you need to interact with specific low-level features of a system. Examples include hardware, such as the camera, or software, such as the contacts in a smartphone.

This is the first recipe in a series of three that will show you how to create and publish a package on GitHub and pub.dev. 

Getting ready

Create a new Flutter project with your favorite editor or use the project created in the previous recipe, Importing packages and dependencies.

How to do it…

In this recipe, you will create a simple Dart package that finds the area of a rectangle or a triangle:

  1. In the root folder of your project, create a new folder, called packages.
  2. Open a terminal window.
  3. In the terminal, type cd .\packages\.
  4. Type flutter create --template=package area.
  5. In the pubspec.yaml file in the package folder of your app, add the dependency to the latest version of the intl package:
dependencies:
flutter:
sdk: flutter
intl: ^0.17.0
  1. In the area.dart file, in the package/lib folder of your app, delete the existing code, import the intl package, and write a method to calculate the area of a rectangle, as shown here:
library area;

import 'package:intl/intl.dart';

String calculateAreaRect(double width, double height) {
double result = width * height;
final formatter = NumberFormat('#.####');
return formatter.format(result);
}
  1. Under the calculateAreaRect method, write another method to calculate the area of a triangle, as shown here:
String calculateAreaTriangle(double width, double height) {
double result = width * height / 2;
final formatter = NumberFormat('#.####');
return formatter.format(result);
}
  1. In the pubspec.yaml file of the main project, add the dependency to the package you have just created:
area:
path: packages/area
  1. At the top of the main.dart file of the main project, import the area package:
import 'package:area/area.dart';
  1. Remove the MyHomePage class created in the sample app.
  2. Refactor the MyApp class, as shown here:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Packages Demo',
home: PackageScreen(),
);
}
}
  1. Create a new stateful widget, called PackageScreen. You can use the stful shortcut to save some time. The final result is shown here:
class PackageScreen extends StatefulWidget {
@override
_PackageScreenState createState() => _PackageScreenState();
}

class _PackageScreenState extends State<PackageScreen> {
@override
Widget build(BuildContext context) {
return Container();
}
}
  1. In the _PackageScreenState class, create two TextEditingController widgets, for the width and height of the shape, and a String for the result:
final TextEditingController txtHeight = TextEditingController();
final TextEditingController txtWidth = TextEditingController();
String result = '';
  1. At the bottom of the main.dart file, create a stateless widget that will take a TextEditingController, and a String, which will return a TextField, with some padding around it, that we will use for the user input:
class AppTextField extends StatelessWidget {
final TextEditingController controller;
final String label;
AppTextField(this.controller, this.label);
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(24),
child: TextField(
controller: controller,
decoration: InputDecoration(hintText: label),
),
);
}
}
  1. In the build method of the _PackageScreenState class, remove the existing code and return a Scaffold, containing an appBar and a body, as shown here:
return Scaffold(
appBar: AppBar(
title: Text('Package App'),
),
body: Column(
children: [
AppTextField(txtWidth, 'Width'),
AppTextField(txtHeight, 'Height'),
Padding(
padding: EdgeInsets.all(24),
),
ElevatedButton(
child: Text('Calculate Area'),
onPressed: () {}),
Padding(
padding: EdgeInsets.all(24),
),
Text(result),
],
),
);
  1. In the onPressed function in ElevatedButton, add the code to call the calculateAreaRect method in the area package:
double width = double.tryParse(txtWidth.text);
double height = double.tryParse(txtHeight.text);
String res = calculateAreaRect(width, height);
setState(() {
result = res;
});
  1. Run the app.
  2. Insert two valid numbers into the text fields on the screen and press the Calculate Area button. You should see the result in the text widget, as shown in the following screenshot:

How it works…

Packages enable the creation of modular code that can be shared easily. The simplest package should at least contain the following:

  • pubspec.yaml file, with the package name, version, and other metadata
  • lib folder, containing the code of the package

A package can contain more than a single file in the lib folder, but must contain at least a single dart file with the name of the package; in our example, area.dart.

The area.dart file contains two methods, one to calculate the area of a rectangle, and the second to calculate the area of a triangle. It would make sense to have a single method that calculates both, but we need two methods for part 2 of this recipe, which follows.

In the pubspec.yaml file, we import the intl package:

intl: ^0.17.0

One of the great features of packages is that when you add a dependency there, and you depend on a package, you don’t need to depend on that package from the main app. That means you do not have to import intl into the main package.

Please note the following command:

final formatter = NumberFormat('#.####');

This creates a NumberFormat instance. This will create a String from a number, with a limit of four decimals for the result. In the pubspec.yaml file of the main project, we add the dependency to the area package with the following lines:

area:
path: packages/area

This is allowed when your package is stored locally, in the packages directory. Other options include a Git or the pub.dev repositories.

In the main.dart file of the project, before using the package, you need to import it at the top of the file, as you would do for any other third-party package:

import 'package:area/area.dart';

The user interface is rather simple. It contains a Column with two text fields. In order to manage the content of the fields, we use two TextEditingController widgets, which are declared with the following commands:

final TextEditingController txtHeight = TextEditingController();
final TextEditingController txtWidth = TextEditingController();

Once the TextEditingController widgets are declared, we associate them with their TextField in the AppTextField StatelessWidget, passing the controller parameter with the commands highlighted here:

class AppTextField extends StatelessWidget {
final TextEditingController controller;
final String label;
AppTextField(this.controller, this.label);
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(24),
child: TextField(
controller: controller,
decoration: InputDecoration(hintText: label),
),
);
}
}

Finally, we use the package by calling the calculateAreaRect method, which is immediately available in the main project after importing the area.dart package:

String res = calculateAreaRect(width, height);

See also

For more information regarding the difference between packages and plugins, and a full guide to creating both, refer to https://flutter.dev/docs/development/packages-and-plugins/developing-packages.

Creating your own package (part 2)

The previous recipe works when your package is contained within your project. In this second part of the Creating your own package recipe, you will see how to create a package made of multiple files, and depend on a Git repository from the main project.

Getting ready

You should have completed the previous recipe, Creating your own package (part 1), before following this one.

How to do it…

For this recipe, first, we will separate the functions we have created in the area.dart file into two separate files using the part and part of keywords. Then, for the dependency, we will use a Git repository instead of a package inside the project’s folder:

  1. In your package’s lib folder, create a new file, called rectangle.dart.
  2. Create another file, called triangle.dart.
  3. In the rectangle.dart file, at the top of the file, specify that this is part of the area package:
part of area;
  1. Under the part of statement, paste the method to calculate the area of the rectangle and remove it from area.dart. You will see an error on the NumberFormat method; this is expected, and we will solve this shortly:
String calculateAreaRect(double width, double height) {
double result = width * height;
final formatter = NumberFormat('#.####');
return formatter.format(result);
}
  1. Repeat the process for the triangle.dart file. The code for the triangle.dart file is shown here:
part of area;

String calculateAreaTriangle(double width, double height) {
double result = width * height / 2;
final formatter = NumberFormat('#.####');
return formatter.format(result);
}
  1. In the area.dart file, remove the existing methods, and add two part statements. This will solve the errors in triangle.dart and rectangle.dart. The full code for the area.dart file is shown here:
library area;

import 'package:intl/intl.dart';

part 'rectangle.dart';
part 'triangle.dart';
  1. The package can now be uploaded to any git repository. Once the package is published, you can update the pubspec.yaml file of the main project with the Git address. Remove the following dependency from the pubspec.yaml file of the main project:
area:
path: packages/area
  1. Add your own Git URL, or use the following git address in the dependencies:
area:
git: https://github.com/simoales/area.git

How it works…

In this recipe, you have seen how to create a package comprising multiple files and depend on it using a git repository.

The part and part of keywords allow a library to be split into several files. In the main file, you specify all the other files that make the library, using the part statement. Note the commands:

part 'rectangle.dart';
part 'triangle.dart';

The preceding commands mean that the triangle.dart and rectangle.dart files are parts of the area library. This is also where you put all the import statements, which are visible in each linked file. In the linked files, you also add the part of statement:

part of area;

This means that each file is a part of the area package.
It’s now generally recommended that you prefer small libraries (also called mini-packages) that avoid using the part/part of commands, when possible. Still, knowing that you can separate complex code into several files can be useful in certain circumstances.

The other key part of this recipe was the dependency in the pubspec.yaml file:

area:
git: https://github.com/simoales/area.git

This syntax allows packages to be added from a git repository. This is useful for packages that you want to keep private within your team, or to depend on a package before it gets published to pub.dev. Being able to depend on a package available in a git repository allows you to simply share packages between projects and teams.

See also

There is a list of guidelines when creating libraries comprising multiple files in Dart. It is available at the following link: https://dart.dev/guides/language/effective-dart/usage.

Creating your own package (part 3)

If you want to contribute to the Flutter community, you can share your packages in the pub.dev repository. In this recipe, you will see the steps required in order to achieve this.

Getting ready

You should have completed the previous recipes, Creating your own package (part 1) and Creating your own package (part 2).

How to do it…

Let’s look at the steps to publish a package to pub.dev:

  1. In a terminal window, move to the area directory:
cd packages/area
  1. Run the flutter pub publish --dry-run command. This will give you some information about the changes required before publishing.
  2. Copy the BSD license, available at the following link: https://opensource.org/licenses/BSD-3-Clause.

BSD licenses are open source licenses that allow almost any legitimate use of the software, cover the author from any liability, and only add minimal restrictions on the use and distribution of the software, both for private and commercial reasons.

  1. Open the LICENSE file in the area directory.
  1. Paste the BSD license into the LICENCE file.
  2. In the first line of the license, add the current year and your name, or the name of your entity:
Copyright 2021 Your Name Here
  1. Open the README.md file.
  2. In the README.md file, remove the existing code and add the following:
# area
A package to calculate the area of a rectangle or a triangle
## Getting Started
This project is a sample to show how to create and publish packages
from a local directory, then a git repo, and finally pub.dev
You can view some documentation at the link:
[online documentation](https://youraddress.com), that contains samples and a getting started guide.
Open the CHANGELOG.md file, and add the content below:
## [0.0.1]
* First published version
  1. In the pubspec.yaml file in your project, remove the author key (if available) and add the home page of the package. This may also be the address of the git repository. The final result of the first four lines in the pubspec.yaml file should look similar to the following example:
name: area
description: The area Flutter package.
version: 0.0.1
homepage: https://github.com/simoales/area
  1. Run the flutter pub publish --dry-run command again and check that there are no more warnings.

Before running the command that follows, make sure your package adds real value to the Flutter community. This specific package is probably not a good candidate.

  1. Run the flutter pub publish command to upload your package to the pub.dev public repository.

How it works…

The flutter pub publish --dry-run command does not publish the package. It just tells you which files will be published and whether there are warnings or errors. This is a good starting point when you decide to publish a package to pub.dev.

A package published in pub.dev must contain an open source license. The Flutter team recommends the BSD license, which basically grants all kinds of use without attribution, and releases the author from any liability. It’s so short that you can actually read it (which is a miracle in itself).

The LICENSE file in the package project is where the text of the license is placed.

Another extremely important file for your packages is the README.md file. This is the main content that users will see on your package home page. It uses the Markdown format, which is a markup language that you can use to format plain text documents.

In the example shown above, we used three formatting options:

  • # area: The single # is a level 1 heading (the H1 in HTML).
  • ## Getting Started: The double ## is a level 2 heading (the H2 in HTML).
  • [online documentation](https://youraddress.com): This creates a link to the URL in the parentheses (youraddress.com) and shows the text in the square brackets.

The CHANGELOG.md file is not required but highly recommended. It is also a Markdown file, which should contain all changes you make when updating your package. If you add it to the package, it will show as a tab on your package’s page on the pub.dev site.

Before finally publishing it, you should also upgrade the pubspec.yaml file, which should include the package name and description, a version number, and the home page of the package, which often is the GitHub repository:

name: area
description: The area Flutter package.
version: 0.0.1
homepage: https://github.com/simoales/area

The flutter pub publish terminal command publishes your package to pub.dev. Please note that once published, other developers can depend on it, so you will be unable to remove your package from the repository. Of course, you will be able to upload updates.

See also

Publishing a package is not just about running the flutter pub publish command. There are several rules, a scoring system, and recommendations to create a high quality, successful package. For more information, see https://pub.dev/help/publishing

Adding Google Maps to your app

This recipe shows how to add the Google Maps plugin to a Flutter app.

Specifically, you will see how to get a Google Maps API key, how to add Google Maps to your Android and iOS project, and how to show a map on the screen.

By the end of this recipe, you’ll know how to integrate Google Maps into your projects.

Getting ready

In this recipe, you will create a new project. There are no prerequisites to follow along.

How to do it…

In this recipe, you will add Google Maps to your app, and the requisite steps to integrate it in your iOS or Android project:

  1. Create a new Flutter app, and call it map_recipe.
  2. Add the Google Maps package dependency to the project’s pubspec.yaml file. The name of the package is google_maps_flutter:
dependencies:
google_maps_flutter: ^2.0.3
  1. In order to use Google Maps, you need to obtain an API key. You can get one from the Google Cloud Platform (GCP) console at the following link: https://cloud.google.com/maps-platform/.
  2. Once you enter the console with a Google account, you should see a screen similar to the one shown here:
  1. Every API key belongs to a project. Create a new project called maps-recipe, leaving No Organization for your location, and then click the Create button.
  2. On the credentials page, you can create new credentials. Click on the Create credentials button and choose Api Key. You will generally want to restrict the use of your keys, but this won’t be necessary for this test project.
  3. Once your key has been created, copy the key to the clipboard. You can retrieve your key from the Credentials page later on.
  4. On iOS and Android, you should also enable the Maps SDK for your target system, from the API page. The end result is shown in the following screenshot:

The following steps vary based on the platform you are using.

To add Google Maps on Android:

  1. Open the android/app/src/main/AndroidManifest.xml file in your project.
  2. Add the following line under the icon launcher icon, in the application node:
android:icon="@mipmap/ic_launcher">
<meta-data android:name="com.google.android.geo.API_KEY"
android:value="[PUT YOUR KEY HERE]"/>

To add Google Maps on iOS:

  1. Open the AppDelegate file, which you will find at ios/Runner/AppDelegate.swift.
  2. At the top of the AppDelegate.swift file, import GoogleMaps, as follows:
import UIKit
import Flutter
import GoogleMaps
  1. Add the API key to the AppDelegate class, as follows:
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GMSServices.provideAPIKey("YOUR API KEY HERE")
GeneratedPluginRegistrant.register(with: self)
return super.application(application,
didFinishLaunchingWithOptions: launchOptions)
}
  1. Opt into the preview of the embedded view. Open your project’s ios/Runner/Info.plist file and add the following to the <dict> node:
<key>io.flutter.embedded_views_preview</key>
<true/>

To show a map on the screen, perform the following steps:

  1. At the top of the main.dart file, import the Google Maps for Flutter package:
import 'package:google_maps_flutter/google_maps_flutter.dart';
  1. Remove the MyHomePage class from the file.
  2. Create a new stateful widget using the stful shortcut, and call the class MyMap:
class MyMap extends StatefulWidget {
@override
_MyMapState createState() => _MyMapState();
}
class _MyMapState extends State<MyMap> {
@override
Widget build(BuildContext context) {
return Container(
);}
}
  1. In the MyApp class, remove the comments and change the title and the home of MaterialApp as follows:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Map Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyMap(),
);
}
}
  1. In the body of a new Scaffold in the _MyMapState class, add a GoogleMap object, passing the initialCameraPosition parameter as follows:
class _MyMapState extends State<MyMap> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Google Maps'),),
body: GoogleMap(
initialCameraPosition: CameraPosition(
target: LatLng(51.5285582, -0.24167),
zoom: 12)
,),
);
}
}

Run the app. You should see a map covering the screen, showing the center of London:

How it works…

In this recipe, you have added Google Maps to your app. This requires three steps:

  • Getting the Google Maps credentials and enabling them
  • Configuring your app with the credentials
  • Using Google Maps in your project

The credentials are free, and you can use Google Maps for free as well, up to a certain threshold. This should be enough for all development purposes and more, but if you want to publish your app, you should take into account the price for using the API.
For more details about pricing and thresholds in Google Maps, visit the following link: https://cloud.google.com/maps-platform/pricing/.

Configuring the app depends on the system you are using. For Android, you need to add the Google Maps information to the Android app manifest. This file contains essential information about your app, including the package name, the permissions that the app needs, and the system requirements.

On iOS, you use the AppDelegate.swift file, the root object of any iOS app, which manages the app’s shared behaviors. For iOS projects, you also need to opt into the preview of the embedded view, which can be done in the app’s Info.plist file.

As you have seen, showing a map is rather simple. The object you use is GoogleMap. The only required parameter is initialCameraPosition, which takes a CameraPosition object. This is the center of the map and requires a target, which in turn takes a LatLng object to express the position in the map. This includes two coordinates expressed in decimal numbers, one for latitude and one for longitude. Optionally, you can also specify a zoom level: the bigger the number, the higher the scale of the map. Google uses latitude and longitude to position a map and to place markers on it.

When the map is shown on the screen, users can zoom in and out, and move the center of the map in the four cardinal directions.

In the next recipe, you will see how to position the map dynamically, based on the position of the user.

See also

While Google Maps is certainly an awesome product, you might want to use other map services, such as Bing or Apple. You can actually choose your favorite map provider with Flutter. One of the platform-independent plugins currently available is the maps plugin, available at https://pub.dev/packages/maps.

Using location services

In the previous recipe, you have seen how to show and position a map using Google Maps, with fixed coordinates. In this recipe, you will find the current position of the user so that the map will change based on the user’s position.

Specifically, you will add the location package to your project, retrieve the coordinates of the device’s position, and set the map’s position to the coordinates you have retrieved.

By the end of this recipe, you will understand how to leverage the user’s location into your apps.

Getting ready

You should have completed the previous recipe, Adding Google Maps to your app, before following this one.

How to do it…

There’s a Flutter package called location that you can use to access the platform-specific location services:

  1. Add the latest version of the location package in the dependencies to the pubspec.yaml file:
location: ^4.1.1
  1. On Android, add permission to access the user’s location. In the Android manifest file, which you can find at android/app/src/main/AndroidManifest.xml, add the following node to the Manifest node:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  1. In the main.dart file, import the location package:
import 'package:location/location.dart';
  1. In the _MyMapState class, add a LatLng variable at the top of the class:
LatLng userPosition;
  1. Still in the _MyMapState class, add a new method at the bottom of the class, containing the code to retrieve the current user’s location:
Future<LatLng> findUserLocation() async {
Location location = Location();
LocationData userLocation;
PermissionStatus hasPermission = await
location.hasPermission();
bool active = await location.serviceEnabled();
if (hasPermission == PermissionStatus.granted && active) {
userLocation = await location.getLocation();
userPosition = LatLng(userLocation.latitude,
userLocation.longitude);
} else {
userPosition = LatLng(51.5285582, -0.24167);
}
return userPosition;
}
  1. In the build method of the _MyMapState class, enclose GoogleMap in a FutureBuilder object whose future property calls the findUserLocation method:
body: FutureBuilder(
future: findUserLocation(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return GoogleMap(
initialCameraPosition:
CameraPosition(target: snapshot.data, zoom: 12),
        );
},
),
  1. Run the app. Now you should see your position on the map, or on an emulator, and the position set on the emulator itself.

How it works…

In order to find the current location of our user, you have created an async method called findUserLocation. This method leverages the device’s GPS to find the latitude and longitude of the user’s current location (if available), and returns it to the caller. This method is then used to set the future property of the FutureBuilder object in the user interface.

Before trying to retrieve the user’s location, there are two important steps. You should always check whether location services are activated and that the user has granted permission to retrieve their location. In the example in this recipe, you have used the following commands:

PermissionStatus hasPermission = await location.hasPermission();

Later, you added the following:

bool active = await location.serviceEnabled();

The hasPermission method returns a PermissionStatus value, which includes the state of the location permission. The serviceEnabled method returns a Boolean, which is true when location services are enabled. Both are prerequisites before trying to ascertain the device’s location.

The getLocation method returns a LocationData object. This not only contains latitude and longitude, but also altitude and speed, which we are not using in this recipe but might be useful for other apps.

In the build method, we are using a FutureBuilder object to automatically set initialPosition when it becomes available.

In the next recipe, we will also add markers to our map.

See also

As you have seen in this recipe, you need specific permissions to use location services. In order to deal better with all the permission requirements of an app, there is a package called permission_handler. See https://pub.dev/packages/permission_handler to learn more. 

Adding markers to a map

In this recipe, you will see how to make a query to the Google Map Places service and add markers to the map in the app. Specifically, you will search for all restaurants near the user’s location within a radius of 1,000 meters.

By the end of this recipe, you will know how to query the huge Google Places archive and point to any place in your maps with a marker. 

Getting ready

You should have completed the previous two recipes, Adding Google Maps to your app, and Using location services, before following this one.

How to do it…

To add markers to the map in your project, perform the following steps:

  1. Get back to the Google Maps API console and enable the places API for your app. Make sure that your Flutter Maps project is selected, and then click the Enable button.
  2. At the top of the main.dart file, add two new imports, one for http and another for the dart:convert package, as shown here:
import 'package:http/http.dart' as http;
import 'dart:convert';
  1. At the top of the _MyMapState class, and a new List of Marker objects:
class _MyMapState extends State<MyMap> {
LatLng userPosition;
List<Marker> markers = [];
  1. In the AppBar contained in the build method of the _MyMapState class, add the actions property, containing an IconButton, which, when pressed, calls a findPlaces method that we will create in the next steps:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Google Maps'),
actions: [
IconButton(
icon: Icon(Icons.map),
onPressed: () => findPlaces(),
)
],
),
  1. In the GoogleMap object, add the markers argument, which will take a Set of Marker objects taken from the markers list:
return GoogleMap(
initialCameraPosition:
CameraPosition(target: snapshot.data, zoom: 12),
markers: Set<Marker>.of(markers),
);
  1. At the bottom of the _MyMapState class, create a new asynchronous method, called findPlaces:
Future findPlaces() async {}
  1. Inside the findPlaces method, add your Google Maps key and the base URL of the query:
final String key = '[Your Key Here]';
final String placesUrl =
'https://maps.googleapis.com/maps/api/place/nearbysearch/json?';
  1. Under the declarations, add the dynamic part of the URL:
String url = placesUrl +
'key=$key&type=restaurant&location=${userPosition.latitude},${userPosition.longitude}' + '&radius=1000';
  1. Next, make an http get call to the generated URL. If the response is valid, call a showMarkers method, which we will create next, passing the retrieved data, otherwise, throw an Exception. The code is shown here:
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
final data = json.decode(response.body);
showMarkers(data);
} else {
throw Exception('Unable to retrieve places');
}
}
  1. Create a new method, called showMarkers, that takes a data parameter:
showMarkers(data) {}
  1. Inside the method, create a List, called places, that reads the results node of the data object that was passed, and clears the markers List:
List places = data['results'];
markers.clear();
  1. Create a forEach loop over the result list, which adds a new marker for each item of the list. For each marker, set markerIdposition, and infoWindow, as shown here:
places.forEach((place) {
markers.add(Marker(
markerId: MarkerId(place['reference']),
position: LatLng(place['geometry']['location']['lat'],
place['geometry']['location']['lng']),
infoWindow:
InfoWindow(title: place['name'], snippet:
place['vicinity'])));
});
  1. Finally, update the State setting the markers:
setState(() {
markers = markers;
});
  1. Run the app. You should see a list of markers on the map, with all the restaurants near your position. If you tap on one of the markers, an info window should appear, containing the name and address of the restaurant.

How it works…

The Google Places API contains over 150 million points of interest that you can add to your maps. Once you activate the service, you can then make search queries using get calls with the http class. The base address to make queries based on your position is the following:

https://maps.googleapis.com/maps/api/place/nearbysearch/json

In the last part of the address, you can specify json or xml, based on the format you wish to receive. Before that, note that nearbysearch makes queries for places near a specified location. When using the nearby search, there are three required parameters and several optional ones. You must separate each parameter with the ampersand (&) character.

The required parameters are as follows:

  • key: Your API key.
  • location: The position around which you want to get the places. This requires latitude and longitude.
  • radius: The radius, expressed in meters, within which you want to retrieve the results.

In our example, we have also used an optional parameter, called type.

Type filters the results so that only places matching the specified type are returned. In this recipe, we used “restaurant.” Other types include café, church, mosque, museum, and school. For a full list of supported types have a look at https://developers.google.com/places/web-service/supported_types.

An example of the final address of the URL should look like this:

https://maps.googleapis.com/maps/api/place/nearbysearch/json?key=%5BYOUR KEY HERE]&type=restaurant&location=41.8999983,12.49639830000001&radius=10000

Once you’ve built the query, you need to call the web service with the http.get method. If the call is successful, it returns a response containing the JSON information of the places that were found. A partial selection of the contents is shown here:

"results": [
{
"geometry" : {
"location" : {
"lat" : 41.8998425,
"lng" : 12.499711
},
},
"name" : "UNAHOTELS Decò",
"place_id" : "ChIJk6d0a6RhLxMRVH_wYTNrTDQ",
"reference" : "ChIJk6d0a6RhLxMRVH_wYTNrTDQ",
"types" : [ "lodging", "restaurant", "food", "point_of_interest",
"establishment" ],
"vicinity" : "Via Giovanni Amendola, 57, Roma"
},

You can use markers to pin places on a map. Markers have a markerId, which uniquely identifies the place, a position, which takes a LatLng object, and an optional infoWindow, which shows some information about the place when users tap or click on Marker. In our example, we’ve shown the name of the place and its address (called vicinity in the API).

In this recipe, the showMarkers method adds a new Marker for each of the retrieved places, using the forEach method over the places List.

In the GoogleMaps object, the markers parameter is used to add the markers to the map.

Written by

XR Developer responsible for end-to-end development of XR solutions spanning multiple domains, by using various XR and WebXR libraries.

Leave a Reply