Learn to compose layouts, call a web-service and integrate Google Maps
The app goal is to display agencies of in-Tact and Atecna as a list and as markers on a map. Currently however, both views are replaced by placeholders.
| Initial | List | Map |
|---|---|---|
![]() |
![]() |
![]() |
List items follows the Material specs with these elements:
Take a look at the Cookbook for some pointers on how to create list in Flutter.
Sideline stuff
agency.company is represented by an enum Company. Both label and logo properties have been added to this enum with extension properties.
These could obviously have been represented by a proper class (it might even make more sense), but it is nonetheless interesting to see how extensions are done with Dart.
Take a look at agency.dart to see how it is done.
The map displays all agencies at the coordinates provided by agency.position and use the logo of their company as an icon. Clicking an agency opens a popup with the name of the city (agency.city) and the name of the company (agency.company.label).
API keys have been prepared for Google Maps on the different platforms, but you are free to use Mapbox or any other provider of your preference
| Platform | Google Maps API key |
|---|---|
| Android | AIzaSyB0_z7TX7gwWptMi7CaPc5WMKyNtGzJO08 |
| iOS | AIzaSyDork6umsMWcEecc4AbN27oKsYeztc2Hu4 |
| Web | AIzaSyBQcpEMvCU4K9cD9OIxdDfDPwgr48fyiK4 |
LatLng and other Maps related classes
To prevent having to convert our own LatLng (used by agency.position) to Google LatLng, you can simply comment all code from map_domain.dart and replace it by
export 'package:google_maps_flutter/google_maps_flutter.dart';
This will import the classes provided by Google Maps in all files that import map_domain.dart
Resources
Helpful methods have been added to map_utils.dart:
boundsFromLatLngList()to create aLatLngBoundsfrom a list ofLatLng. This can be used to ensure all agencies are visiblegetBitmapDescriptorFromAssetBytes()to prepare a marker icon from an asset image
Sideline stuff
Google Maps on Flutter is implemented as a Platform View for Android and iOS. This means the view is rendered natively by the underlying platform, which comes with some caveats that you should learn about in the linked documentation. You can also take a look at the documentation of AndroidView and UiKitView.
Clicking on a list item opens the details screen of the agency. At first, this screen will only display the name of the agency.
You will notice that the screen looks a bit broken. This is because no theme are provided to the screen, and the default style of a TextField is intentionaly ugly to make sure the developers fix that. This is easily fixed by wrapping the screen inside a Material widget like Scaffold.
There is two navigation and routing mechanisms in Flutter, and both can be used concurrently in different places of the same application.
Navigator 1.0 is original mechanism, and uses an imperative approach that is usually easier to grasp. This is the mechanism explained in the Flutter Cookbook recipes:
Navigator 2.0 was introduced in 2020 and uses a declarative approach that is closer to the rest of Flutter philosophy, but can be a bit harder to grasp. With Navigator 2.0, navigation is part of the application state and we can manipulate the whole screen/page stack. This is not possible with Navigator 1.0 that can only push and pop one screen/page at a time.
Learning Flutter’s new navigation and routing system
You are free to choose the approach, knowing that the corrections are based on Navigator 1.0 (I'll try to implement a Navigator 2.0 approach later).
🏅 Bonus point if you are able to manage urls like http://localhost:1234/agency/2 to open the agency details screen with Flutter Web :)
Here are the design elements for the agency details screen
| Content | Blueprint |
|---|---|
![]() |
![]() |
fetchLorem() is provided by lorem_fetcher.dart. This method returns a Future<List<String>>, each string representing a paragraph. The future has a small delay to simulate a remote call. The screen should display a loader until the text content is available.
Text styles follows the Material typography
Don't hesitate to experiment with the widgets provided by Flutter. Try different layouts, add animations, etc.
Resources
- Layouts in Flutter
- Basic Flutter layout concepts (Codelab)
- Widget catalog
- How to debug layout issues with the Flutter Inspector
Add a Hero animation on the logo and agency label when switching between the list and the details screens. See the video below for an example:
Resources
Currently, lorem_fetcher.dart provides static content.
The goal is to replace it with a call to the Bacon Ipsum API, like https://baconipsum.com/api/?type=meat-and-filler. (Or another similar API if you don't like bacon 😉 )
Resources
I encountered some problems while preparing this exercise, and it is likely you will encounter them too. Here is how I resolved them:
This happens because the exercise project use sound null-safety, while Google Maps for Flutter Web is not null-safe yet. As a result, the Dart compiler is unable to guarantee null-safety and fails with the above error.
To indicate to the compiler that we are ok with unsound null-safety, the flag --no-sound-null-safety must be used. For example:
$ flutter run --no-sound-null-safety -d chrome
If you are using Visual Studio code, you can also run the project with "exo2 - disable sound null-safety".
In Android, a special tool named ProGuard is usually run on release mode. The purpose of this tool is to obfuscate and minify the code, respectively to make it harder to reverse-engineer the application and remove any unused methods/classes/etc.
This tools is notoriously difficult to configure and in some cases a method will be removed or renamed that is actually used. This often happens when using reflection to call a method by its name, and I suppose the Google Maps plugin for Android has to rely on that.
To fix this, follow these steps:
- Go to the
android/app/folder - Create a
proguard-rules.profile. This will be used to configure ProGuard - In the
proguard-rules.profile, add the following line:
-keep class androidx.lifecycle.DefaultLifecycleObserver
This will instruct ProGuard to not rename nor remove the DefaultLifecycleObserver class that Google Maps relies on.
- Open
android/app/build.gradlefile (it should already exists) - Locate the
releaseconfiguration block inside thebuildTypesblock. It should be around line 45 - Edit the
releaseconfiguration block to provide ProGuard with the configuration file we just made:
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
To spot a ProGuard configuration issue in the future:
- It only happens in Android release mode
- You get a strange error message about a method not being accessible or not existing, while you are positive it should exist
- The names of the class or method in the stacktrace are single letters like
io.flutter.plugins.googlemaps.h.a(obfuscation)










