I’ve been asked through Twitter to build something that uses GoogleMap so here you go!

In order to follow along with the tutorial please grab the code from GitHub: https://github.com/cybdom/transportation_ui

Another important step is supporting me at https://www.buymeacoffee.com/bi3cp0Zk5 (I collected 5$ of the 130$ required for the hosting of this website)

Project Setup:

Since this project uses Google Maps, there is an important step to make this work.

Once you cloned the repo from Github, head to android/app/src/main/AndroidManifest.xml find this code:

<meta-data android:name="com.google.android.geo.API_KEY" android:value="CHANGE ME"/>

Change the value to your google maps API key that you can get for free here: https://cloud.google.com/maps-platform/

Details page:

We start with the simpler screen, in this case, the itinerary screen, first we build the AppBar as follows:

appBar: AppBar( iconTheme: IconThemeData(color: Colors.grey[300]), actions: [ IconButton(icon: Icon(Icons.notifications), onPressed: () {}), ], ),

Our notification container has the same green color as the one we’ve set in our global file. As well as a box shadow of the same color, a border-radius, and some padding.

Inside we have a Row, an Image asset inside a Flexible along with a column for the Text. To the top right, we have a cancel button that does nothing until we wrap it inside a GestureDetector and we give it an onTap property.

Container( padding: const EdgeInsets.all(15.0), decoration: BoxDecoration( color: lightGreen, borderRadius: BorderRadius.circular(15.0), boxShadow: [ BoxShadow( blurRadius: 15, offset: Offset(0, 9), color: lightGreen.withOpacity(.75), ) ], ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Flexible( flex: 1, child: Image.asset('assets/invoice.png'), ), SizedBox(width: 15.0), Flexible( flex: 3, child: Column( children: [ Text( "Buying tickets is now much more comfortable.", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold), ), SizedBox(height: 5.0), Text( "Buy a ticket now and get 50% discount.", style: TextStyle(color: Colors.white70), ) ], ), ), Container( padding: EdgeInsets.all(5.0), alignment: Alignment.center, decoration: BoxDecoration( color: darkGreen, borderRadius: BorderRadius.circular(5.0), ), child: Icon( Icons.close, color: Colors.white, ), ) ], ), ),

MyMap:

In flutter, integrating google maps is really easy.

First, we import the package through our pubspec.yaml.

google_maps_flutter: 0.5.21+8

Then inside my widgets folder, we create a file named mymap.dart. Inside of which we create a stateful widget called MyMap.

import 'dart:async'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; class MyMap extends StatefulWidget { @override _MyMapState createState() => _MyMapState(); } class _MyMapState extends State { Completer _controller = Completer(); @override Widget build(BuildContext context) { return Container( margin: EdgeInsets.symmetric(vertical: 15.0), height: MediaQuery.of(context).size.height / 4, width: double.infinity, child: ClipRRect( borderRadius: BorderRadius.circular(15), child: GoogleMap( initialCameraPosition: CameraPosition( target: LatLng(40, 0), ), onMapCreated: (GoogleMapController controller) { _controller.complete(controller); }, ), ), ); } }

As you can see the GoogleMap widget is pretty lean, we just put it inside a ClipRRect to give it that nice border-radius then we put everything inside a container to set a fixed height and we are done.

A more complete tutorial on how to use GoogleMap for:

Showing routes

Drawing Polygons

Showing User location

will be released soon, so please follow me on Twitter.

Bottom App Bar: (A custom one 😉 )

bottomNavigationBar: Padding( padding: const EdgeInsets.only(bottom: 11.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Flexible( child: GestureDetector( onTap: () {}, child: Container( padding: EdgeInsets.all(15.0), decoration: BoxDecoration( color: Colors.green, borderRadius: BorderRadius.circular(9.0), boxShadow: [ BoxShadow( color: Colors.green, blurRadius: 3.0, offset: Offset(0, 3)), ], ), child: Icon(Icons.map, color: Colors.white), ), ), ), Flexible( child: GestureDetector( onTap: () {}, child: Container( padding: EdgeInsets.all(15.0), decoration: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(9.0), boxShadow: [ BoxShadow( color: Colors.blue, blurRadius: 3.0, offset: Offset(0, 3)), ], ), child: Icon(Icons.star, color: Colors.white), ), ), ), Expanded( child: GestureDetector( onTap: () {}, child: Container( padding: EdgeInsets.all(15.0), decoration: BoxDecoration( gradient: LinearGradient( colors: [Colors.orange, Colors.orangeAccent]), borderRadius: BorderRadius.circular(9.0), boxShadow: [ BoxShadow( color: Colors.orange, blurRadius: 3.0, offset: Offset(0, 3)), ], ), child: Text( "Show Ticket", style: Theme.of(context) .textTheme .button .apply(color: Colors.white), textAlign: TextAlign.center, ), ), ), ) ], ), ),

Instead of using the original BottomNavigationBar I preferred to use a Row just to mix things up.

Home Screen:

The home screen has the same bottom navigation as the details page, so no need to explain that.

Starting from the top we have a right-aligned Notification IconButton, pretty simple.

Below we have our first Row, which has 3 children. The first one is a text widget the second is a DropdownButton (we will take a look at its code in a minute). and finally, a FlatButton.Icon.

Between the DropDownButton and the FlatButton, we have a spacer in order to separate the two and make a better user experience.

Talking about DropDownButton I created a widget that I called departureSelector:

import 'package:flutter/material.dart'; class DepartureSelector extends StatefulWidget { const DepartureSelector({ Key key, }) : super(key: key); @override _DepartureSelectorState createState() => _DepartureSelectorState(); } class _DepartureSelectorState extends State { int _selected = 0; @override Widget build(BuildContext context) { return DropdownButton( style: Theme.of(context).textTheme.button.apply(color: Colors.white), iconEnabledColor: Colors.white, underline: Container(), value: _selected, onChanged: (i) { _selected = i; }, items: [ DropdownMenuItem( child: Text("Now"), value: 0, ) ], ); } }

Location Selector Container:

The main part of this transportation app is selecting our departure location as well as our destination and that’s exactly why we made this widget.

import 'package:flutter/material.dart'; class LocationSelectorContainer extends StatelessWidget { @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(15.0), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15.0), boxShadow: [ BoxShadow( color: Colors.black26, blurRadius: 15, offset: Offset(0, 5), ), ], ), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 15, height: 15, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.blue.withOpacity(.3), border: Border.all(color: Colors.blue, width: 3.0), ), ), SizedBox(width: 15.0), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "From", style: Theme.of(context) .textTheme .subtitle .apply(color: Colors.black38), ), GestureDetector( child: Text( "Your Location", style: Theme.of(context) .textTheme .title .apply(color: Colors.black87), ), onTap: () {}), ], ) ], ), Divider( height: 25, color: Colors.black, thickness: .7, ), Row( children: [ Container( width: 15, height: 15, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.orange.withOpacity(.3), border: Border.all(color: Colors.orange, width: 3.0), ), ), SizedBox(width: 15.0), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "To", style: Theme.of(context) .textTheme .subtitle .apply(color: Colors.black38), ), GestureDetector( child: Text( "Choose A Destination", style: Theme.of(context) .textTheme .title .apply(color: Colors.black87), ), onTap: () {}), ], ) ], ), ], ), ), SizedBox( width: 15.0, ), Container( decoration: BoxDecoration( color: Color(0xfff0f3f7), borderRadius: BorderRadius.circular(5.0)), child: IconButton( icon: Icon( Icons.import_export, color: Colors.black54, ), onPressed: () {}, ), ) ], ), ); } }

Results Container:

The final part of this app is obviously the results.

On top of this container, we have our transportation means selector menu.

import 'package:flutter/material.dart'; class MeansTransportMenu extends StatefulWidget { @override _MeansTransportMenuState createState() => _MeansTransportMenuState(); } class _MeansTransportMenuState extends State { final List > menuItems = [ {'time': '9 min', 'icon': Icons.directions_car}, {'time': '15 min', 'icon': Icons.directions_bus}, {'time': '31 min', 'icon': Icons.directions_walk}, {'time': '21 min', 'icon': Icons.directions_bike}, ]; int _a = 0; @override Widget build(BuildContext context) { return Row( children: List.generate(menuItems.length, (f) { return Expanded( child: GestureDetector( onTap: () { setState(() { _a = f; }); }, child: Container( padding: EdgeInsets.symmetric(horizontal: 3.0, vertical: 9.0), margin: EdgeInsets.symmetric(horizontal: 5.0), decoration: BoxDecoration( color: _a == f ? Color(0xff02aefe) : Color(0xfffafbfc), border: _a == f ? Border() : Border.all(color: Colors.grey), borderRadius: BorderRadius.circular(9.0), boxShadow: _a == f ? [ BoxShadow( blurRadius: 9.0, color: Color(0xff02aefe), offset: Offset(0, 3)) ] : null, ), child: Row( children: [ Icon( menuItems[f]['icon'], color: _a == f ? Colors.white : Color(0xffa7b7c5), ), SizedBox( width: 3.0, ), Flexible( child: Text( "${menuItems[f]['time']}", style: TextStyle( color: _a == f ? Colors.white : Color(0xffa7b7c5), ), )) ], ), ), ), ); }), ); } }

Then, we have an expanded list of TicketContainers.

import 'package:flutter/material.dart'; import 'package:transportation_app/global.dart'; class TicketContainer extends StatelessWidget { @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(15.0), margin: EdgeInsets.symmetric(vertical: 15.0), decoration: BoxDecoration( border: Border.all( color: Colors.grey[300], ), borderRadius: BorderRadius.circular(25.0) ), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Flexible( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Departure In:"), SizedBox( height: 5.0, ), RichText( text: TextSpan( children: [ TextSpan( text: "05", style: Theme.of(context).textTheme.display1, ), TextSpan( text: "min", style: Theme.of(context).textTheme.subtitle), ], ), ) ], ), ), Flexible( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ RichText( text: TextSpan( children: [ TextSpan( text: "Travel Time: ", style: TextStyle(color: Colors.black87)), TextSpan( text: "15 min", style: TextStyle( fontWeight: FontWeight.bold, color: Colors.black87)), ], ), ), SizedBox( height: 5.0, ), Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "07:05", style: TextStyle(color: Colors.grey), ), SizedBox( height: 3.0, ), Row( children: [ Icon( Icons.directions_bus, color: Colors.black54, ), Container( padding: EdgeInsets.symmetric( vertical: 2.0, horizontal: 9.0), decoration: BoxDecoration( color: Colors.orange, borderRadius: BorderRadius.circular(9.0), ), child: Text( "20", style: TextStyle(color: Colors.white), ), ), ], ) ], ), SizedBox(width: 9), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "07:23", style: TextStyle(color: Colors.grey), ), SizedBox( height: 3.0, ), Row( children: [ Icon( Icons.directions_bus, color: Colors.black54, ), Container( padding: EdgeInsets.symmetric( vertical: 2.0, horizontal: 9.0), decoration: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(9.0), ), child: Text( "20", style: TextStyle(color: Colors.white), ), ), ], ) ], ) ], ) ], ), ), Text( "19:57", style: Theme.of(context).textTheme.body2.apply(color: Colors.blue), ), ], ), SizedBox( height: 15, ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 15, height: 15, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.blue.withOpacity(.3), border: Border.all(color: Colors.blue, width: 3.0), ), ), SizedBox( width: 9.0, ), Text("Boghni", style: Theme.of(context).textTheme.subtitle), ], ), SizedBox( height: 15.0, ), Row( children: [ Container( width: 15, height: 15, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.orange.withOpacity(.3), border: Border.all(color: Colors.orange, width: 3.0), ), ), SizedBox( width: 9.0, ), Text("Tizi Ouzou", style: Theme.of(context).textTheme.subtitle), ], ), ], ), RaisedButton.icon( color: lightGreen, icon: Icon(Icons.confirmation_number, color: Colors.white), onPressed: () => Navigator.of(context).pushNamed('details'), label: Text( "Ticket: 70 DA", style: Theme.of(context) .textTheme .button .apply(color: Colors.white), ), ) ], ), ], ), ); } }

Final words:

Today’s tutorial is more axed to showing off the code more than explaining how it works. However, the next article will be more of a tutorial going through all of the details of how to use GoogleMap widget in flutter so stay tuned.