Tutorials

Snippets

Search

Tags

Subscribe on Youtube

Easy Google Places in Flutter Tutorial

In this tutorial we're implementing the Mandatory Address selection for our food delivery service BoxtOut. To kick things off we'll start with the config setup for everything we require first and that's access to the Google Places Api.

We'll start off by creating our Google Cloud Project and adding the Places Api. Go to https://cloud.google.com if you have an account then click on the "Go to console" button. Then we'll click on the Project drop down in the top left that looks like this.

Google Cloud Dropdown

You can then either select the project you want to select or you click on new project. I'll add a new project called BoxtOut.

Google Cloud Create Project UI

To avoid any info leaking I won't show much, but after you click create and select the project you'll land on the dashboard for that project. Then you can click on the hamburger menu on the side and select APIs & Services

Google Cloud Hamburger UI

Then click on the "Enable API's and Services" at the top of the page, go to the "Places Api" and click "Enable". When completed click on the hamburger and go back to "API's and Services". Click on Credentials and then the Create Credentials button.

Google Cloud Api Credentials

I entered "BoxOut Mobile" as my name and saved that as well as the key. I won't restrict that now, we'll do API restrictions later on. And that's the setup done. Now we can go ahead and work in the code. We'll start with the functionality.

We have to do a few steps to build this functioality

First we add the package into our pubspec file

places_service: ^0.1.0

Once we have it in the project we add it into the app.dart file.

dependencies: [
		...
    LazySingleton(classType: PlacesService),
  ],

Then we can run the build_runner build function to get that registered with the locator.

Once we have that we can initialise the service with the api key from above. Open up the StartupViewModel . We'll get the PlacesService from the locator and then initialise it at the beginning of the runStartupLogic function call.

class StartUpViewModel extends BaseViewModel {
	...
  final _placesService = locator<PlacesService>();

	Future<void> runStartupLogic() async {
    _placesService.initialize(apiKey: 'API_KEY_FROM_ABOVE');
	}
}

Open up the AddressSelectionView and we'll do a basic Stacked forms setup. We'll define a FormView with one field for address.. Add the Generated mixin, then we'll use the listenToFormUpdated function to sync values across to the ViewModel.

// 1. Define the form field
(fields: [
  FormTextField(
    name: 'address',
  )
])
class AddressSelectionView extends StatelessWidget 
			with $AddressSelectionView { // 2. Include the Generated Mixin for the form
  AddressSelectionView({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return ViewModelBuilder<AddressSelectionViewModel>.reactive(
		
      onModelReady: (model) => listenToFormUpdated(model),
      builder: (context, model, child) => Scaffold(
        body: ListView(
          children: [
          
          ],
        ),
      ),
      viewModelBuilder: () => AddressSelectionViewModel(),
    );
  }
}

For the children of the list view we'll simply add a normal TextFormField and give it the controller generated. and then we'll have some conditional UI t either show a message or show the auto complete results

  ListView(
    children: [
			TextFormField(
			      decoration: InputDecoration(hintText: 'Enter your address'),
			      controller: addressController,
			    ),
			    if (!model.hasAutoCompleteResults)
			      Text('We have no suggestions for you'),
			    if (model.hasAutoCompleteResults)
			      ...model.autoCompleteResults.map((autoCompleteResult) => ListTile(
			            title: Text(autoCompleteResult.mainText ?? ''),
			            subtitle: Text(autoCompleteResult.secondaryText ?? ''),
			          ))
]),

Open up the AddressSelectionViewModel where we will build all the functionality required. We'll start off by getting the PlacesService from the locator and creating the properties to expose the information we'll get from it.

class AddressSelectionViewModel extends FormViewModel {
  final _placesService = locator<PlacesService>();

  List<PlacesAutoCompleteResult> _autoCompleteResults = [];

  List<PlacesAutoCompleteResult> get autoCompleteResults =>
      _autoCompleteResults;

  bool get hasAutoCompleteResults => _autoCompleteResults.isNotEmpty;

  
  void setFormStatus() {

  }
}

We have the autoCompleteResults which will store what the name indicates. And we have a value to check if it's empty. Then the final piece is to create a function that gets the results from the places api and set it to the _autoCompleteResults.


void setFormStatus() {
	_getAutoCompleteResults();
}

Future<void> _getAutoCompleteResults() async {
  if (addressValue != null) {
    final placesResults = await _placesService.getAutoComplete(addressValue!);

    if (placesResults != null) {
      _autoCompleteResults = placesResults;
      notifyListeners();
    }
  }
}

In this function we check if the addressValue is not null. If it's not, we get the auto complete results, if that returns anything we assign that value to the _autoCompleteResults. And then we notifyListeners so that our UI can rebuild. The last thing to do is to call the _getAutoCompleteResults function in the setFormStatus override.

If you run your code now you should see the auto complete working just fine.

One last thing to consider is to make sure you don't publish any sensitive information to your repo. To achieve this we'll use a .env file. Open up your pubspec file and add the flutter_dotenv package.

flutter_dotenv: ^4.0.0-nullsafety.1

Then in the root of your project create a new file called .env and add that into the assets section of your pubspec as well.

assets:
    - ./.env

This file will contain the key for the google maps api

GOOGLE_MAPS_API_KEY='YOUR_API_KEY'

Now you can go back to the StartupViewModel and remove your key and replace it with the value from the env package.

_placesService.initialize(apiKey: env['GOOGLE_MAPS_API_KEY']!);

The last thing to do is to initialise that package before we start our application. Open the main file where we'll import the package as DotEnv and then load our env file.

import 'package:flutter_dotenv/flutter_dotenv.dart' as DotEnv;

Future main() async {
  await DotEnv.load(fileName: ".env");
	...
  runApp(MyApp());
}

And that's it. You're ready to roll with places. thanks for reading and I'll see you in the next episode where we're building our design system.

Dane

Also check out

Cover image

Practical Guide to Unit Testing in Flutter

This tutorial guides you through a practical process for unit testing your Flutter app

Link
Cover image

Handle Users profile in Flutter

This tutorial shows you how to handle the users profile during Authentication flows in Fultter.

Link
Cover image

Sign in with Google or Apple Sign In using Flutter

This tutorial goes over the implementation to implement Google And Apple Sign in

Link