Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 094f9e9

Browse files
committed
Initialize exo2
1 parent 63983e2 commit 094f9e9

File tree

99 files changed

+2056
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

99 files changed

+2056
-2
lines changed

.vscode/launch.json

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,22 @@
77
{
88
"name": "exo1",
99
"request": "launch",
10-
"type": "dart"
11-
}
10+
"type": "dart",
11+
"program": "exo1/lib/main.dart",
12+
},
13+
{
14+
"name": "exo2 - debug",
15+
"request": "launch",
16+
"type": "dart",
17+
"program": "exo2/lib/main.dart",
18+
"flutterMode": "debug",
19+
},
20+
{
21+
"name": "exo2 - release",
22+
"request": "launch",
23+
"type": "dart",
24+
"program": "exo2/lib/main.dart",
25+
"flutterMode": "release"
26+
},
1227
]
1328
}

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Exercises
44

55
- [Exercise 1](../master/exo1/README.md) - Learn the basics about state management
6+
- [Exercise 2](../master/exo2/README.md) - Learn about layout composition, navigation, WS calls and Google Maps integration
67

78
## Some essentials Widgets
89

exo2/.gitignore

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Miscellaneous
2+
*.class
3+
*.log
4+
*.pyc
5+
*.swp
6+
.DS_Store
7+
.atom/
8+
.buildlog/
9+
.history
10+
.svn/
11+
12+
# IntelliJ related
13+
*.iml
14+
*.ipr
15+
*.iws
16+
.idea/
17+
18+
# The .vscode folder contains launch configuration and tasks you configure in
19+
# VS Code which you may wish to be included in version control, so this line
20+
# is commented out by default.
21+
#.vscode/
22+
23+
# Flutter/Dart/Pub related
24+
**/doc/api/
25+
**/ios/Flutter/.last_build_id
26+
.dart_tool/
27+
.flutter-plugins
28+
.flutter-plugins-dependencies
29+
.packages
30+
.pub-cache/
31+
.pub/
32+
/build/
33+
34+
# Web related
35+
lib/generated_plugin_registrant.dart
36+
37+
# Symbolication related
38+
app.*.symbols
39+
40+
# Obfuscation related
41+
app.*.map.json
42+
43+
# Android Studio will place build artifacts here
44+
/android/app/debug
45+
/android/app/profile
46+
/android/app/release

exo2/.metadata

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# This file tracks properties of this Flutter project.
2+
# Used by Flutter tool to assess capabilities and perform upgrades etc.
3+
#
4+
# This file should be version controlled and should not be manually edited.
5+
6+
version:
7+
revision: 60bd88df915880d23877bfc1602e8ddcf4c4dd2a
8+
channel: stable
9+
10+
project_type: app

exo2/README.md

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# Exercise 2
2+
3+
Learn to compose layouts, call a web-service and integrate Google Maps
4+
5+
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.
6+
7+
Initial | List | Map
8+
--------|------|------
9+
![initial](images/initial.png)|![list](images/list.png)|![map](images/map.png)
10+
11+
## Goals
12+
13+
### Agency list
14+
15+
List items follows the [Material specs](https://material.io/components/lists#specs) with these elements:
16+
17+
![list item](images/list-item.png)
18+
19+
Take a look at the [Cookbook](https://flutter.dev/docs/cookbook/lists/long-lists) for some pointers on how to create list in Flutter.
20+
21+
*Sideline stuff*
22+
23+
`agency.company` is represented by an enum `Company`. Both `label` and `logo` properties have been added to this enum with extension properties.
24+
25+
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.
26+
27+
Take a look at [agency.dart](lib/domain/agency.dart) to see how it is done.
28+
29+
### Agency map
30+
31+
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`).
32+
33+
![map item](images/map-item.png)
34+
35+
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
36+
37+
Platform| Google Maps API key
38+
--------|---------------------
39+
Android | AIzaSyB0_z7TX7gwWptMi7CaPc5WMKyNtGzJO08
40+
iOS | AIzaSyDork6umsMWcEecc4AbN27oKsYeztc2Hu4
41+
Web | AIzaSyBQcpEMvCU4K9cD9OIxdDfDPwgr48fyiK4
42+
43+
*LatLng and other Maps related classes*
44+
45+
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](lib/domain/map_domain.dart) and replace it by
46+
47+
```
48+
export 'package:google_maps_flutter/google_maps_flutter.dart';
49+
```
50+
51+
This will import the classes provided by Google Maps in all files that import `map_domain.dart`
52+
53+
*Resources*
54+
55+
Helpful methods have been added to [map_utils.dart](lib/screen/dashboard/map/map_utils.dart):
56+
57+
- `boundsFromLatLngList()` to create a `LatLngBounds` from a list of `LatLng`. This can be used to ensure all agencies are visible
58+
- `getBitmapDescriptorFromAssetBytes()` to prepare a marker icon from an asset image
59+
60+
*Sideline stuff*
61+
62+
Google Maps on Flutter is implemented as a [Platform View](https://flutter.dev/docs/development/platform-integration/platform-views) 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`](https://api.flutter.dev/flutter/widgets/AndroidView-class.html) and [`UiKitView`](https://api.flutter.dev/flutter/widgets/UiKitView-class.html).
63+
64+
65+
:bangbang: Google Maps integration can be a bit messy, don't forget to check the [Troubleshooting](#Troubleshooting) sections if you get into an issue
66+
67+
### Agency details
68+
69+
Clicking on a list item opens the details screen of the agency. At first, this screen will only display the name of the agency.
70+
71+
![initial](images/details1.png)
72+
73+
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`.
74+
75+
#### Navigation
76+
77+
There is two [navigation and routing](https://flutter.dev/docs/development/ui/navigation) mechanisms in Flutter, and both can be used concurrently in different places of the same application.
78+
79+
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:
80+
81+
- [Navigate to a new screen and back](https://flutter.dev/docs/cookbook/navigation/navigation-basics)
82+
- [Navigate with named routes](https://flutter.dev/docs/cookbook/navigation/named-routes.html)
83+
- [Pass arguments to a named route](https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments)
84+
85+
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.
86+
87+
[Learning Flutter’s new navigation and routing system](https://medium.com/flutter/learning-flutters-new-navigation-and-routing-system-7c9068155ade)
88+
89+
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).
90+
91+
:medal_sports: Bonus point if you are able to manage urls like http://localhost:1234/agency/2 to open the agency details screen with Flutter Web :)
92+
93+
#### Details layout
94+
95+
Here are the design elements for the agency details screen
96+
97+
Content |Blueprint
98+
----------------------------------------------|--------------------------------------------------
99+
![content](images/details2-design-content.png)|![blueprint](images/details2-design-blueprint.png)
100+
101+
`fetchLorem()` is provided by [lorem_fetcher.dart](lib/utils/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.
102+
103+
Text styles follows the [Material typography](https://material.io/design/typography/the-type-system.html#type-scale)
104+
105+
Don't hesitate to experiment with the widgets provided by Flutter. Try different layouts, add animations, etc.
106+
107+
*Resources*
108+
109+
- [Layouts in Flutter](https://flutter.dev/docs/development/ui/layout)
110+
- [Basic Flutter layout concepts (Codelab)](https://flutter.dev/docs/codelabs/layout-basics)
111+
- [Widget catalog](https://flutter.dev/docs/development/ui/widgets)
112+
- [How to debug layout issues with the Flutter Inspector](https://medium.com/flutter/how-to-debug-layout-issues-with-the-flutter-inspector-87460a7b9db)
113+
114+
#### Hero animation
115+
116+
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:
117+
118+
![Hero animation](images/hero-animation.gif)
119+
120+
*Resources*
121+
122+
- [Animate a widget across screens](https://flutter.dev/docs/cookbook/navigation/hero-animations)
123+
- [Hero animations](https://flutter.dev/docs/development/ui/animations/hero-animations)
124+
125+
#### HTTP Call (Lorem Ipsum)
126+
127+
Currently, [lorem_fetcher.dart](lib/utils/lorem_fetcher.dart) provides static content.
128+
129+
The goal is to replace it with a call to the [Bacon Ipsum API](https://baconipsum.com/), like https://baconipsum.com/api/?type=meat-and-filler.
130+
(Or another similar API if you don't like bacon :wink: )
131+
132+
*Resources*
133+
134+
- [Fetch data from the internet](https://flutter.dev/docs/cookbook/networking/fetch-data)
135+
- [Networking & HTTP](https://flutter.dev/docs/development/data-and-backend/networking)
136+
- [JSON and serialization](https://flutter.dev/docs/development/data-and-backend/json)
137+
138+
## Troubleshooting
139+
140+
I encountered some problems while preparing this exercise, and it is likely you will encounter them too. Here is how I resolved them:
141+
142+
### Unable to run the project on the Web after adding Google Maps for the Web platform
143+
144+
![Error message](images/gmaps-web-error.png)
145+
146+
This happens because the exercise project use sound null-safety, while Google Maps for Flutter Web is not null-safe yet.
147+
As a result, the Dart compiler is unable to guarantee null-safety and fails with the above error.
148+
149+
To indicate to the compiler that we are ok with *unsound* null-safety, the flag `--no-sound-null-safety` must be used. For example:
150+
`$ flutter run --no-sound-null-safety -d chrome`
151+
152+
If you are using Visual Studio code, you can also run the project with "exo2 - disable sound null-safety".
153+
154+
155+
### Application crashes when opening the map in release mode
156+
157+
![Error message](images/gmaps-android-release-error.png)
158+
159+
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.
160+
161+
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.
162+
163+
To fix this, follow these steps:
164+
165+
1. Go to the `android/app/` folder
166+
2. Create a `proguard-rules.pro` file. This will be used to configure ProGuard
167+
3. In the `proguard-rules.pro` file, add the following line:
168+
169+
`-keep class androidx.lifecycle.DefaultLifecycleObserver`
170+
171+
This will instruct ProGuard to not rename nor remove the `DefaultLifecycleObserver` class that Google Maps relies on.
172+
173+
4. Open `android/app/build.gradle` file (it should already exists)
174+
5. Locate the `release` configuration block inside the `buildTypes` block. It should be around line 45
175+
6. Edit the `release` configuration block to provide ProGuard with the configuration file we just made:
176+
177+
```
178+
release {
179+
// TODO: Add your own signing config for the release build.
180+
// Signing with the debug keys for now, so `flutter run --release` works.
181+
signingConfig signingConfigs.debug
182+
183+
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
184+
}
185+
```
186+
187+
To spot a ProGuard configuration issue in the future:
188+
189+
- It only happens in Android release mode
190+
- You get a strange error message about a method not being accessible or not existing, while you are positive it should exist
191+
- The names of the class or method in the stacktrace are single letters like `io.flutter.plugins.googlemaps.h.a` (obfuscation)

exo2/android/.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
gradle-wrapper.jar
2+
/.gradle
3+
/captures/
4+
/gradlew
5+
/gradlew.bat
6+
/local.properties
7+
GeneratedPluginRegistrant.java
8+
9+
# Remember to never publicly share your keystore.
10+
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11+
key.properties

exo2/android/app/build.gradle

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
def localProperties = new Properties()
2+
def localPropertiesFile = rootProject.file('local.properties')
3+
if (localPropertiesFile.exists()) {
4+
localPropertiesFile.withReader('UTF-8') { reader ->
5+
localProperties.load(reader)
6+
}
7+
}
8+
9+
def flutterRoot = localProperties.getProperty('flutter.sdk')
10+
if (flutterRoot == null) {
11+
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12+
}
13+
14+
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15+
if (flutterVersionCode == null) {
16+
flutterVersionCode = '1'
17+
}
18+
19+
def flutterVersionName = localProperties.getProperty('flutter.versionName')
20+
if (flutterVersionName == null) {
21+
flutterVersionName = '1.0'
22+
}
23+
24+
apply plugin: 'com.android.application'
25+
apply plugin: 'kotlin-android'
26+
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27+
28+
android {
29+
compileSdkVersion 30
30+
31+
sourceSets {
32+
main.java.srcDirs += 'src/main/kotlin'
33+
}
34+
35+
defaultConfig {
36+
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
37+
applicationId "com.example.exo2"
38+
minSdkVersion 16
39+
targetSdkVersion 30
40+
versionCode flutterVersionCode.toInteger()
41+
versionName flutterVersionName
42+
}
43+
44+
buildTypes {
45+
release {
46+
// TODO: Add your own signing config for the release build.
47+
// Signing with the debug keys for now, so `flutter run --release` works.
48+
signingConfig signingConfigs.debug
49+
}
50+
}
51+
}
52+
53+
flutter {
54+
source '../..'
55+
}
56+
57+
dependencies {
58+
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
59+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2+
package="com.example.exo2">
3+
<!-- Flutter needs it to communicate with the running application
4+
to allow setting breakpoints, to provide hot reload, etc.
5+
-->
6+
<uses-permission android:name="android.permission.INTERNET"/>
7+
</manifest>

0 commit comments

Comments
 (0)