├── LICENSE
├── README.md
└── images
└── guide
└── android_studio_sdk_tools.png
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Jay
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AppBrewery Flutter : Null Safety Guide
2 |
3 | ## Read Before Starting
4 |
5 | - **DISCLAIMER : This repository is not an official outcome of [London AppBrewery Team](https://github.com/londonappbrewery)**
6 |
7 | - This repository is made to help new students get through [**AppBrewery Flutter Course**](https://www.udemy.com/course/flutter-bootcamp-with-dart/)
8 |
9 | - This repository contains notes for **only coding (project) sections** and explains what has changed and what's the difference.
10 |
11 | - If something is not covered here, **start a discussion**, not an issue! I will try to add it then.
12 |
13 | - Use latest versions of required **`packages`** and **`plugins`**, find them on [**pub.dev**](https://pub.dev/)
14 |
15 | ## Index
16 |
17 | #### 1. [Terminology](#terminology)
18 |
19 | #### 2. [Common issues and fixes](#common-issues-and-fixes)
20 |
21 | 1. #### [Using old Flutter SDK?](#1-using-old-flutter-sdk)
22 |
23 | 2. #### [Android license status unknown](#2-android-license-status-unknown)
24 |
25 | 3. #### [Option to create a new package is missing](#3-option-to-create-a-new-package-is-missing)
26 |
27 | 4. #### [Migrating V2](#4-migrating-v2)
28 |
29 | #### 3. [Resources](#resources)
30 |
31 | #### 4. [Code to Update](#code-to-update)
32 |
33 | 1. #### [Section 3 : I Am Rich App](#section-3--i-am-rich-app-lesson-28)
34 |
35 | 2. #### [Section 7 : Dicee App](#section-7--dicee-app-lesson-53)
36 |
37 | 3. #### [Section 9 : Xylophone App](#section-9--xylophone-app-lessons-76-77)
38 |
39 | 4. #### [Section 10 : Quizzler App](#section-10--quizzler-app-lesson-94)
40 |
41 | 5. #### [Section 12 : BMI Calculator App](#section-12--bmi-calculator-app-lessons-125-126-128-129)
42 |
43 | 6. #### [Section 13 : Clima App](#section-13--clima-app-lesson-140)
44 |
45 | 7. #### [Section 14 : Flash Chat App](#section-14--flash-chat-app-lessons-169-194)
46 |
47 | ## Terminology
48 |
49 | ##### [Go back to Index](#index)
50 |
51 | ### 1. Deprecated
52 |
53 | - You will often come across **`deprecated`** stuff, where it says **This is `deprecated`**. This means it's **not recommended** to use it anymore in your projects. You should avoid it and use alternatives.
54 |
55 | ### 2. Null Safety
56 |
57 | - **Null safety is not your enemy!** It's there you help you so you don't accidentally make something null and crash your app.
58 |
59 | - Dart has **`sound null safety`**. Basically, if you're writing any code that compiler thinks might end up being **`null`**, it will notify you right away! Isn't that cool?
60 |
61 | - Read more : [**Sound Null Safety**](https://dart.dev/null-safety), [**Understanding Null Safety**](https://dart.dev/null-safety/understanding-null-safety), [**Null Safety in Flutter**](https://flutter.dev/docs/null-safety)
62 |
63 | ## Common Issues and Fixes
64 |
65 | ##### [Go back to Index](#index)
66 |
67 | #### 1. Using old Flutter SDK?
68 |
69 | - Use latest **`Flutter SDK`**, currently I am using **`2.2`** in **`stable channel`**
70 |
71 | - To upgrade old one, run **`flutter upgrade`** in your **`Terminal / Command Prompt (cmd)`**
72 |
73 | #### 2. **`Android license status unknown`**
74 |
75 | - There are couple of things that can cause this, I'll keep adding them in future! For now I have these solutions,
76 |
77 | ##### Solution 1. Accept the new ones!
78 |
79 | - Just run **`flutter doctor --android-licenses`**
80 |
81 | - Normally, this does the job. If it doesn't, go ahead.
82 |
83 | ##### Solution 2. Install / Update SDK Command Line Tools
84 |
85 | - Open Settings panel by,
86 |
87 | - **`File > Settings`** (**Windows and Linux**)
88 |
89 | - **`Android Studio > Preferences`** (**Mac**)
90 |
91 | - Then navigate to,
92 |
93 | **`Appearance & Behavior > System Settings > Android SDK`**
94 |
95 | - Select the **`SDK Tools` Tab**
96 |
97 | - Select **`Android SDK Command Line Tools`** and click **`Apply`**
98 |
99 | - A dialog will pop up and ask you if you want to install these.
100 |
101 | Click Yes/OK and let it install, after that, close **`Android Studio`** and **`restart`** it.
102 |
103 |
104 |
105 | #### 3. Option to create a new **`package`** is missing
106 |
107 | - When I first encountered this issue, I thought there must be something wrong with just this particular update.
108 |
109 | - I searched it online, posted on Reddit, twitter, but found nothing.
110 |
111 | - Later on, I got to know that **`New > Package`** and **`New > Directory (Folder)`** options have now merged!
112 |
113 | - So, to create a new **`package`** or just a **`folder`**, simply use **`New > Directory`** option.
114 |
115 | #### 4. Migrating V2
116 |
117 | - As we know [**Sound Null Safety**](https://dart.dev/null-safety) was added to **Flutter 2**.
118 |
119 | - And before **Flutter 2** old apps don't have [**Sound Null Safety**](https://dart.dev/null-safety) feature.
120 |
121 | - So we need to fix this, and thank [**Flutter**](https://flutter.dev/) we have simple way to do.
122 |
123 | - To fix this issue easily ,
124 |
125 | * Delete all files and folders in App folder **`except`** course materials **`lib` - `assets` - `fonts` - `pubspec.yaml` etc.**
126 |
127 | * For Example;
128 |
129 | 
130 |
131 |
132 | - And then go to **`Terminal`** while its in project folder and
133 |
134 | - **Write this line to `Terminal`**
135 |
136 | - `flutter create .`
137 |
138 | - For Example;
139 |
140 | 
141 |
142 |
143 | - **Then Flutter starts rebuilding application with migrated version of it. And Done!**
144 |
145 |
146 | ## Resources
147 |
148 | ##### [Go back to Index](#index)
149 |
150 | #### 1. Try out Null Safety on [DartPad](https://dartpad.dev/?null_safety=true)
151 |
152 | #### 2. Read Updated [Flutter Docs](https://flutter.dev/docs)
153 |
154 | #### 3. Watch and Follow [Flutter's Official Youtube Channel](https://www.youtube.com/channel/UCwXdFgeE9KYzlDdR7TG9cMw)
155 | - To learn more about Null Safety and staying updated in general.
156 |
157 | # Code to Update
158 |
159 | ## Section 3 : I Am Rich App (Lesson 28)
160 |
161 | ##### [Go back to Index](#index)
162 |
163 | - You right clicked on **`res`** folder but didn't find **`Image Asset`**? Don't worry Follow these steps,
164 |
165 | - Right click on **`android`** folder and a pop-up menu will open up.
166 |
167 | From that, select **`Flutter > Open Android Module in Android Studio`**
168 |
169 | **If this doesn't work for you then follow these steps**,
170 |
171 | **1.** Close current project by pressing **`File > Close Project`**
172 |
173 | **2.** Now you will have the first screen of Android Studio.
174 |
175 | **3.** Press **`Open an Existing Project`**, then **`Open File or Project`** dialog will open.
176 |
177 | **4.** Here, navigate to your **Flutter project** in which, you want to add **`Image Asset`**
178 |
179 | **5.** Expand that and you will find **`android`** folder. Select that and press **`OK`**
180 |
181 | - **Both ways** should open **Android Part** of your **Flutter Project** in **`Android Studio`**.
182 |
183 | - Now, at bottom right, if it's running any **`gradle`** processes, let it run. Don't interrupt! However, if you close it, it'll rebuild everything when you reopen it. So, no need to worry!
184 |
185 | - After that long build process completes, you can find **`Image Asset`** option when you click on **`res`** folder, **Yay**!
186 |
187 | - Add assets and again, **`File > Close Project`**, **`Open an Existing Project`** and this time, select your **Flutter Project** and continue!
188 |
189 | ## Section 7 : Dicee App (Lesson 53)
190 |
191 | ##### [Go back to Index](#index)
192 |
193 | - **`FlatButton`** is **`deprecated`**, so use **`TextButton`** instead.
194 |
195 | ## Section 9 : Xylophone App (Lessons 76, 77)
196 |
197 | ##### [Go back to Index](#index)
198 |
199 | - Getting a lengthy error when trying to use **`audioplayers` plugin**?
200 |
201 | - All you need to do is open **`android > build.gradle` (Project Level `gradle` file)**
202 |
203 | - Inside **`buildscript {}`**, you'll find **`ext.kotlin_version` (Line 2 in file)**
204 |
205 | - Replace whatever version it is with [**Latest Stable Kotlin Version**](https://kotlinlang.org/docs/releases.html#release-details)
206 |
207 | - As of **July 23, 2021** it is, **`ext.kotlin_version = '1.5.21'`**
208 |
209 | - Now, **re-install** the app. If it's already running, press **Stop** then press **Run (Play)** again.
210 |
211 | - **`FlatButton`** is **`deprecated`**, so use **`TextButton`** instead.
212 |
213 | - By the end, the implementation of your **`TextButton`** should look like this:
214 |
215 | ```dart
216 | @override
217 | Widget build(BuildContext context) {
218 | return Expanded(
219 | child: TextButton(
220 | style: ButtonStyle(
221 | backgroundColor: MaterialStateProperty.all(color),
222 | ),
223 | onPressed: () {
224 | playSound(soundNumber);
225 | },
226 | ),
227 | );
228 | }
229 | ```
230 |
231 | - **`AudioCache`** is **`deprecated`**, so use **`AudioPlayer`** instead.
232 | - By the end, your solution to playing the audio should look like this:
233 | ```dart
234 | void playSound(int soundNumber) {
235 | final player = AudioPlayer();
236 | player.setSource(AssetSource('note$soundNumber.wav'));
237 | }
238 | ```
239 |
240 | - An example of a working project as of 16/07/2022 has been linked below:
241 | - [Link to repository](https://github.com/vpatel-dev/xylophone-flutter)
242 |
243 | ## Section 10 : Quizzler App (Lesson 94)
244 |
245 | ##### [Go back to Index](#index)
246 |
247 | - Due to **`null safety`**, all variables in a class must have a value assigned, when created. If not, they must be declared **`Nullable`** intentionally. This rule also applies to **`Stateless`** and **`Stateful`** widgets. On top of that, in classes extending **`StatelessWidget`**, all variables must be declared **`final`**
248 |
249 | - So, make your **`Question`** class like this,
250 | ```dart
251 | class Question {
252 | String questionText;
253 | bool questionAnswer;
254 |
255 | Question(this.questionText, this.questionAnswer);
256 |
257 | // If you want named parameters
258 | // Question({required this.questionText, required this.questionAnswer});
259 | }
260 | ```
261 |
262 | - **`@required`** is replaced by just **`required` (Without @ sign)**
263 |
264 | - Here, the **Keyword `this`** points to **current context**, which happens to be **`Question`** class.
265 |
266 | - **`FlatButton`** is **`deprecated`**, so use **`TextButton`** instead.
267 |
268 | ## Section 12 : BMI Calculator App (Lessons 125, 126, 128, 129)
269 |
270 | ##### [Go back to Index](#index)
271 |
272 | - **`@required`** is replaced by just **`required` (Without @ sign)**
273 |
274 | - So, while making **`ReusableCard`**, lesson shows you can skip using **`cardChild`** property, but that isn't possible, due to **`null safety`**
275 |
276 | - This part is tricky, because now you can't have null arguments anymore.
277 |
278 | - So, you must have to intentionally make it **`Nullable`**, by adding **`?`** to it, like this,
279 | ```dart
280 | class ReusableCard extends StatelessWidget {
281 | final Color colour;
282 | final Widget? cardChild;
283 |
284 | ReusableCard({required this.colour, this.cardChild});
285 |
286 | @override
287 | Widget build(BuildContext context) {
288 | return Container(
289 | decoration: BoxDecoration(
290 | color: colour,
291 | ),
292 | child: child,
293 | );
294 | }
295 | }
296 | ```
297 |
298 | - Use it like **`ReusableCard(color: Colors.amber)`** and your app won't crash.
299 |
300 | - But, it's not same for **`IconContent`**, **`Icon`** can have **`null`** value, but **`Text`** can't!
301 | ```dart
302 | class IconContent extends StatelessWidget {
303 | final IconData? icon;
304 | final String? label;
305 |
306 | IconContent({this.icon, this.label});
307 |
308 | @override
309 | Widget build(BuildContext context) {
310 | return Column(
311 | children: [
312 | Icon(icon),
313 | Text(label ?? ''),
314 | ],
315 | );
316 | }
317 | }
318 | ```
319 |
320 | - So using **`??`** operator, you need to check if label is **`null`** or not, if it is, then you must provide a **`String`** value to it. Here, I provided an empty String.
321 |
322 | - Even if you don't pass any arguments like **`IconContent()`**, your app won't crash.
323 |
324 | - According to **Lesson 129**, **`ReusableCard`** now has a parameter named **`onPress`**, to get it working, use this,
325 | ```dart
326 | class ReusableCard extends StatelessWidget {
327 | final Color colour;
328 | final Widget? cardChild;
329 | final void Function()? onPress;
330 |
331 | ReusableCard({required this.colour, this.cardChild, this.onPress});
332 |
333 | @override
334 | Widget build(BuildContext context) {
335 | return GestureDetector(
336 | onTap: onPress,
337 | child: Container(
338 | decoration: BoxDecoration(
339 | color: colour,
340 | ),
341 | child: child,
342 | ),
343 | );
344 | }
345 | }
346 | ```
347 | - Because **`GestureDetector`**'s **`onTap`** property wants **`void Function()?`** as argument.
348 |
349 | - In **Lesson 128**, **`_InputPageState`** has a new variable which haven't been initialized. As I already told you, you must initialize them or make them **`Nullable`**.
350 | ```dart
351 | class _InputPageState extends State {
352 | Gender? selectedGender;
353 | }
354 | ```
355 |
356 | - Here, making it **`Nullable`** will do the job. Rest of the code will work perfectly fine.
357 |
358 | ## Section 13 : Clima App (Lesson 140)
359 |
360 | ##### [Go back to Index](#index)
361 |
362 | - When running this app on a **Physical Device** running on:
363 |
364 | - **`Android` :** you will need **Internet Permission** because the app sends a **`request`** to the OpenWeatherMap **`API`**. For this, open **`AndroidManifest.xml`** by navigating to,
365 |
366 | **`android > app > src > main > AndroidManifest.xml`**
367 |
368 | and add the following line,
369 |
370 | ```xml
371 |
372 | ```
373 |
374 | **Keep the existing location permissions** and add this above/below them.
375 | Add it under **`manifest`** tag, like this,
376 |
377 | ```xml
378 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
390 | .
391 | .
392 | .
393 |
394 |
395 | ```
396 |
397 | - **`iOS` :**
398 |
399 | - There is **no required Internet Permission**.
400 | - When launching the app, you will probably have a message asking you for **Local Network Permission**: it is **not required** either.
401 | - To run your app on an iOS physical device, make sure that you selected a **Development Team** (refer to **Section 4 - Lesson 32** of the course):
402 | 1. Open the Flutter project's Xcode target with open ios/Runner.xcworkspace
403 | 2. Select the 'Runner' project in the navigator then the 'Runner' target in the project settings
404 | 3. Make sure a 'Development Team' is selected under Signing & Capabilities > Team. You may need to:
405 | 1. Log in with your Apple ID in Xcode first
406 | 2. Ensure you have a valid unique Bundle ID (for example *"com.put-your-name-here.clima"*)
407 | 3. Register your device with your Apple Developer Account
408 | 4. Let Xcode automatically provision a profile for your app
409 | 4. Select your iOS physical device as the target and click the Run button
410 | - You may have several pop-up asking you that "codesign" wants access to your Apple Development Team's key. Accept by entering your password (it's your Mac session's password, not your Apple ID's password)
411 | - :exclamation: **ATTENTION:** if you don't activate Internet on your physical device, it is likely that you will see a pop-up on your screen telling you that **an Internet connection is required** to verify if the developer (you) is reliable, so you would need to activate your Internet connection
412 |
413 | - If your app cannot retrieve your current location on your **`iOS` physical device** it is probably because the [geolocator 8.2.1 flutter package](https://pub.dev/packages/geolocator) has been updated and you will need to apply the following changes:
414 | - As of now (June 2022), the geolocator package indicates to add both **`NSLocationWhenInUseUsageDescription`** and **`NSLocationAlwaysUsageDescription`** permissions to access **Location Service**. Since iOS 11, the **`NSLocationAlwaysUsageDescription`** property key is [deprecated](https://developer.apple.com/documentation/bundleresources/information_property_list/nslocationalwaysusagedescription). Use instead **only one** of those permissions:
415 | - the **`NSLocationAlwaysAndWhenInUseUsageDescription`** to enable the **Location Service** in foreground and background,
416 | - the **`NSLocationWhenInUseUsageDescription`** to enable the service in foreground only, as [recommended by Apple](https://developer.apple.com/documentation/corelocation/choosing_the_location_services_authorization_to_request).
417 |
418 | To do so, open **`Info.plist`** file by navigating to:
419 |
420 | **`ios > Runner > Info.plist`**
421 |
422 | and add the following line right under the `` tag (your can customise the message in the `` tag, it has to explain why your app needs to have access to that particular permission):
423 |
424 | ```xml
425 |
426 | NSLocationWhenInUseUsageDescription
427 | This app needs access to your location to provide weather data of your current location.
428 |
429 | ```
430 |
431 | - In your code you will have to explicitly ask the user for permission to use the **Location Service**. For that, update the **`getCurrentLocation()`** method in the **`location.dart`** file:
432 | - Short version:
433 | ```dart
434 | Future getCurrentLocation() async {
435 | try {
436 | LocationPermission locationPermission = await Geolocator.requestPermission();
437 |
438 | if (LocationPermission.whileInUse == locationPermission || LocationPermission.always == locationPermission) {
439 | Position position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.lowest);
440 | latitude = position.latitude;
441 | longitude = postion.longitude;
442 | }
443 | } catch (e) {
444 | print(e);
445 | }
446 | }
447 | ```
448 | - Long but more complete version:
449 | ```dart
450 | Future getCurrentLocationCheckingPermissions() async {
451 | bool serviceEnabled;
452 | LocationPermission locationPermission;
453 |
454 | // Test if location services are enabled.
455 | serviceEnabled = await Geolocator.isLocationServiceEnabled();
456 | if (!serviceEnabled) {
457 | // Location services are not enabled don't continue
458 | // accessing the position and request users of the
459 | // App to enable the location services.
460 | return Future.error(
461 | 'Location services are disabled. Please activate them.');
462 | } else {
463 | locationPermission = await Geolocator.checkPermission();
464 | if (LocationPermission.unableToDetermine == locationPermission) {
465 | return Future.error(
466 | 'Unable to determine if location permissions are enabled.');
467 | } else if (LocationPermission.denied == locationPermission ||
468 | LocationPermission.deniedForever == locationPermission) {
469 | print(Future.error('Location permissions are denied: ' +
470 | locationPermission.toString()));
471 | locationPermission = await Geolocator.requestPermission();
472 | }
473 |
474 | if (LocationPermission.whileInUse == locationPermission ||
475 | LocationPermission.always == locationPermission) {
476 | Position position = await Geolocator.getCurrentPosition(
477 | desiredAccuracy: LocationAccuracy.lowest);
478 | this.latitude = position.latitude;
479 | this.longitude = position.longitude;
480 | }
481 | }
482 | }
483 | ```
484 |
485 |
486 | ## Section 14 : Flash Chat App (Lessons 169-194)
487 |
488 | ##### [Go back to Index](#index)
489 |
490 | ### Setting up Firebase
491 |
492 | Following the Appbrewery course, you should have logged in Firebase with your Google account, and created a Firebase project that will be linked with your Flash Chat Flutter project.
493 |
494 | #### Add Firebase to your Flutter project
495 |
496 | - To do so, in the course it is showed that you have to go to your Firebase project and **add a new application**, selecting an **`Android application`** if you plan to deploy your app on Android, and/or an **`iOS application`** if you plan to deploy on iOS.
497 | - As of now **(July 2022)**, it is now possible to directly add a **`Flutter application`** which will save you a lot of time in configuration :confetti_ball: , so choose that option instead, and follow the instructions that will be displayed:
498 |
499 | #### Installing Firebase CLI and connect to it
500 |
501 | - Depending on your Operating System (Windows, macOS or Linux), you will find the steps to do on the [documentation](https://firebase.google.com/docs/cli?authuser=0&hl=fr#install_the_firebase_cli).
502 | - After the installation is done, connect to it by executin the following command in a terminal: **`firebase login`**.
503 |
504 | #### Installing Flutter SDK and creating a Flutter project
505 |
506 | - If you have arrived this far in the course, you have already installed the Flutter SDK a long time ago!
507 | - The Flutter project has alo been created already, it's our Flash Chat Flutter project.
508 |
509 |
510 | #### Installing the FlutterFire CLI
511 | - Open a terminal and run the following command line: **`dart pub global activate flutterfire_cli`** (it doesn't matter in which directory you run this command)
512 |
513 |
514 | #### Executing the FlutterFire CLI
515 |
516 | - Before executing the **FlutterFire CLI**, make sure to change your **`Application ID`** for Android, and your **`iOS Bundle ID`** for iOS to make them **unique and personal** (if you have copied the Flash Chat Flutter project from the Appbrewery course, they are defined with their company ID which you have to change to make it your own):
517 |
518 | - **`Application ID`** for Android:
519 | - Open the **`build.gradle`** file located under **`android > app > build.gradle`**
520 | - Change the **`android > defaultConfig > applicationId`** property (the applicationId should be **co.appbrewery.flash_chat** --> change it for something unique and personal like **com.firstnamelastname.flash_chat**, or something you like that is unique and personal, or your domain name if you own one and wish to use it)
521 |
522 | - **`iOS Bundle ID`** for iOS:
523 | - Right-click on the "ios" folder and choose **`Flutter > Open iOS module in Xcode`**
524 | - Select "Runner" at the top of the left panel (the "Runner" with the blue icon), and in the center panel go to the **General tab**, then under it go to **`Identity > Bundle Identifier`**
525 | - You should find a Bundle Identifier like **co.appbrewery.flashChat** --> change it for something unique and personal like **com.firstnamelastname.flashChat**
526 |
527 | - After changing the **`Application ID`** for Android, and/or the **`iOS Bundle Identifier`** for iOS, open a terminal and go to the **ROOT FOLDER** of your Flutter Flash Chat project, then execute the command line given in the instructions:
528 |
529 | ```shell
530 | flutterfire configure --project=YOUR_FIREBASE_PROJECT_ID_HERE
531 | ```
532 | (You can check what is your Firebase Project ID by either looking on your Firebase account in a browser, or by running the command line **`firebase projects:list`**)
533 | - After running the previous command, you should find in your Flutter project:
534 |
535 | - **`Flutter`**: your Firebase configuration file under **`lib > firebase_options.dart`** (the most important one, it contains both your Android and your iOS API keys to access Firebase services)
536 |
537 | - **`Android`**: your Firebase configuration file under **`android > app > google-services.json`**
538 |
539 | - **`iOS`**: a Firebase identifying file under **`ios > firebase_app_id_file.json`** (if the course is not updated, you might see that you should have a file called **`GoogleService-Info.plist`** instead under **`ios > Runner > GoogleService-Info.plist`** --> I am not an expert with Firebase, but my guess is that the **`GoogleService-Info.plist`** comes up when you configure your Firebase project by adding an iOS application instead of a Flutter application)
540 |
541 |
542 | #### Initialising Firebase
543 |
544 | - To initialise Firebase, start by adding to your Flutter project the **`firebase_core`** plugin:
545 | ```shell
546 | flutter pub add firebase_core
547 | ```
548 | - Make sure that the Firebase configuration of your Flutter application is updated, by running the following command **in the root folder of your Flutter project directory**:
549 | ```shell
550 | flutterfire configure
551 | ```
552 | - Then, if everything's fine, in your **`lib/main.dart`** file, change the main method to use the Firebase initialising method **`Firebase.initializeApp()`**
553 | ```dart
554 | import 'package:firebase_core/firebase_core.dart';
555 | import 'package:flash_chat/firebase_options.dart';
556 |
557 |
558 | Future main() async {
559 | WidgetsFlutterBinding.ensureInitialized();
560 |
561 | await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
562 |
563 | runApp(FlashChat());
564 | }
565 | ```
566 | :exclamation::exclamation: It is **FUNDAMENTAL** that you add the parameter **`options: DefaultFirebaseOptions.currentPlatform`** because it will specify the configuration that will be used depending on the platform your application is running on (Android, iOS, macOS, Windows or Linux) --> feel free to explore the code of **`DefaultFirebaseOptions.currentPlatform`**, you will notice that it corresponds to your **`lib > firebase_options.dart`**, and that it simply verifies the platorm on which your application is running, then returns the appropriate configuration.
567 |
568 | - Finally, stop your application if it was running (to make a fresh start), and try to run it on Android and on iOS to verify that everything works on both platform (try to run it as well on macOS, Windows and/or Linux if you are developing for those platforms).
569 |
570 | If you encounter some errors, please have a look below to find some fixes that may be of help:
571 |
572 | - **`On Android`:**
573 |
574 | - The **`firebase_core`** plugin (as well as all [Firebase plugins](https://firebase.google.com/docs/flutter/setup?authuser=0&hl=fr&platform=ios#available-plugins)) requires at least the **`Android SDK version 31`** now (July 2022), so make the modification if needed under **`android > app > build.gradle`**:
575 |
576 | ```gradle
577 | android {
578 | compileSdkVersion 31
579 | ...
580 | }
581 | ```
582 |
583 | - **`On iOS`:**
584 |
585 | - There was no problem encountered on my side. Please add more details about yours in the **Discussions** section if you have troubles here.
586 |
587 |
588 | #### Adding Firebase Plugins
589 |
590 | - Add the **`firebase_auth`** and the **`cloud_firestore`** plugins in your Flutter project:
591 | ```shell
592 | flutter pub add firebase_auth
593 | flutter pub add cloud_firestore
594 | ```
595 | Click on **`Pub Get`** to make sure that you get the dependencies in your project.
596 |
597 |
598 | - **`On Android`**:
599 |
600 | - You may need to upgrade the **`minSdkVersion`** of the Android part of your Flutter project, under **`android > app > build.gradle`**, because some Firebase plugins have a minimal requirement of the SDK version 21 now (July 2022), like the **`cloud_firestore`** plugin for instance, so apply that change:
601 | ```gradle
602 | android {
603 | ...
604 | defaultConfig {
605 | ...
606 | minSdkVersion 21
607 | ...
608 | }
609 | ...
610 | }
611 | ```
612 |
613 | - Those plugins require as well the **`Android SDK version 31`**, so make sure that you have it updated in your **`android > app > build.gradle`**:
614 |
615 | ```gradle
616 | android {
617 | compileSdkVersion 31
618 | ...
619 | }
620 | ```
621 |
622 |
623 | - After adding the dependencies in the AndroidManifest.xml file and the build.gradle files, you might get this error:
624 | ```shell
625 | ERROR:D8: Cannot fit requested classes in a single dex file (# methods: 104246 > 65536)
626 | com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives:
627 | The number of method references in a .dex file cannot exceed 64K.
628 | ...
629 | * What went wrong:
630 | Execution failed for task ':app:mergeExtDexDebug'.
631 | > A failure occurred while executing com.android.build.gradle.internal.tasks.DexMergingTaskDelegate
632 | > There was a failure while executing work items
633 | > A failure occurred while executing com.android.build.gradle.internal.tasks.DexMergingWorkAction
634 | > com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives:
635 | The number of method references in a .dex file cannot exceed 64K.
636 | Learn how to resolve this issue at https://developer.android.com/tools/building/multidex.html
637 | ```
638 |
639 | To fix this issue, inside the app-level build.gradle file, **`../android/app/build.gradle`**,
640 | the *minSdkVersion* property under the *defaultConfig* block in the *android* block should be set to **`21`** to avoid error.
641 | This is because multiDex support is enabled by default for sdkVersion 21.
642 |
643 | - Additional dependencies have to be added to the app-level build.gradle file, **`../android/app/build.gradle`**:
644 | ```
645 | dependencies {
646 | ...
647 | implementation platform('com.google.firebase:firebase-bom:30.0.1')
648 | implementation 'com.google.firebase:firebase-auth'
649 | implementation 'com.google.firebase:firebase-firestore'
650 | ...
651 | }
652 | ```
653 |
654 | - To enable internet connectivity on a physical device, add `` to the AndroidManifest.xml file.
655 |
656 |
657 |
658 | - **`On iOS`** :
659 |
660 | - Before running the application to check if everything is fine, update **`CocoaPods`**:
661 | ```shell
662 | pod repo update
663 | sudo gem install cocoapods
664 | pod setup
665 | ```
666 | - Run your app to check if it's working:
667 | - If you encounter the following error:
668 | ```shell
669 | Error output from Cocoapods:
670 | [!] Automatically assigning platform 'iOS' with version '9.0' on target 'Runner' because no platform was specified. Please specify a platform for this target in your Podfile.android
671 | Error running pod install
672 | Error launching application on iPhone.
673 | ```
674 | update your **`Podfile`** file under **`ios > Podfile`** by uncommenting the "platform" line and changing the version from 9.0 to 10.0 --> this specifies the minimum OS version that you are going to support for the pod project:
675 | ```
676 | # Uncomment this line to define a global platform for your project
677 | platform :ios, '10.0'
678 | ```
679 | then try to run your app again (it might take a long time, it took me around 30 minutes to make all the cocoapods installation).
680 | You should see in the "Run tab" the information **"Running pod install..."**: Flutter is initiating that to be able to install all of the Firebase plugin packages (firebase_core, firebase_auth and cloud_firestore) as cocoapods to our iOS app.
681 |
682 |
683 |
684 |
685 |
686 | ### Code Part
687 |
688 |
689 | - In **chat_screen.dart** file, the object type for the *loggedInUser* was previously **`FirebaseUser`**, and should now be replaced with **`User`**. Moreover, the Firestore instance is now retrieved with `FirebaseFirestore.instance` instead of `Firestore.instance`:
690 |
691 | ```dart
692 | class _ChatScreenState extends State {
693 | final _firestore = FirebaseFirestore.instance;
694 | final _auth = FirebaseAuth.instance;
695 | User loggedInUser;
696 | String message;
697 | ...
698 | }
699 |
700 | ```
701 |
702 |
703 | - When retrieving the messages from your Firestore Database, you will notice some changes in the API:
704 |
705 | - The `getDocuments` method has been renamed to `get`:
706 | ```dart
707 | _firestore.collection('messages').getDocuments(); // <-- BEFORE
708 |
709 | _firestore.collection('messages').get(); // <-- NOW
710 | ```
711 |
712 | - The **QuerySnapshot** property, **documents**, has been renamed to **docs**.
713 | ```dart
714 | void getMessages() async {
715 | final messages = await _firestore.collection('messages').get();
716 |
717 | for (var message in messages.documents) { // <-- BEFORE
718 | print(message.data());
719 | }
720 |
721 | for (var message in messages.docs) { // <-- NOW
722 | print(message.data());
723 | }
724 | }
725 | ```
726 |
727 | - When using a `StreamBuilder`, it is better to give it the type of data that will stream through, as it will greatly help you when manipulating the `AsyncSnapshot` and the data it contains (`snapshot.data`) - you can find the type of data by looking at the return type of the Firestore snapshots method (`_firestore.collection('messages').snapshots()`):
728 | ```dart
729 | StreamBuilder>>( // <-- By adding the type >...
730 | stream: _firestore.collection('messages').snapshots(),
731 | builder: (BuildContext context, AsyncSnapshot snapshot) {
732 |
733 | final messages = snapshot.data.docs; // <-- ... you will be able to access the 'docs' property because we manipulate a Stream of QuerySnapshot
734 |
735 | for (var message in messages) {
736 | final messageText = message.data()['text']; // <-- ... you will be able to access the Map with `message.data()` because we manipulate a Stream of QuerySnapshot of Map
737 | final sender = message.data()['sender'];
738 | ...
739 | }
740 | ...
741 | },
742 | );
743 | ```
744 |
745 |
746 |
747 |
748 | - To deal with the case where we would have no data in our `StreamBuilder`, we do add a `CircularProgressIndicator` after checking the value of `!snapshot.hasData`. To center it on the screen, add a margin using `MediaQuery` to retrieve the height of the screen (if you want to see how it looks like, you can simply temporarily change the if condition with `true`):
749 | ```dart
750 | StreamBuilder>>(
751 | stream: _firestore.collection('messages').snapshots(),
752 | builder: (context, snapshot) {
753 |
754 | if (!snapshot.hasData) { // <-- change it temporarily to 'if (true)' to quickly see the result
755 | return Container(
756 | margin:
757 | EdgeInsets.only(top: MediaQuery.of(context).size.height / 3),
758 | child: CircularProgressIndicator(
759 | backgroundColor: Colors.lightBlueAccent,
760 | ),
761 | );
762 | }
763 | ...
764 | },
765 | );
766 | ```
767 | :exclamation::information_source: To avoid your `CircularProgressIndicator` to be stretched horizontally and look weird, make sure that the column in which your `MessagesStream` is into doesn't have the property **`crossAxisAlignment: CrossAxisAlignment.stretch`**:
768 | ```dart
769 | body: SafeArea(
770 | child: Column(
771 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
772 | // crossAxisAlignment: CrossAxisAlignment.stretch, // <-- remove this if you have it
773 | children: [
774 | MessagesStream(),
775 | Container(...),
776 | ...
777 | ```
778 |
779 |
780 | - The sorting of the messages in the chat screen is still chaotic even after the reversal in ***lesson 191***.
781 | To fix this problem, we need to add a **timestamp** field to the messages and sort the collection based on it as shown here:
782 | ```dart
783 | ...
784 | TextButton(
785 | onPressed: () {
786 | controller.clear();
787 | _firestore.collection('messages').add({
788 | 'text': messageText,
789 | 'sender': loggedInUser.email,
790 | 'timestamp': FieldValue.serverTimestamp(), // Here is the **timestamp** field.
791 | });
792 | },
793 | ...
794 | ```
795 |
796 | We use the server time instead of generating a timestamp with the user device because:
797 | - Our users may be in different timezones so the time differences will affect our app.
798 | - Some devices could be set to incorrect times.
799 |
800 | ```dart
801 | class MessagesStream extends StatelessWidget {
802 |
803 | @override
804 | Widget build(BuildContext context) {
805 | return StreamBuilder(
806 | stream: _firestore.collection('messages').orderBy('timestamp').snapshots(), // Here, the **.orderBy** sorts the messages according to the server timestamps.
807 | builder: (context, snapshot) {
808 | ...
809 |
810 | ```
811 |
812 |
813 | ### More Configuration on Firebase
814 |
815 | #### Cloud Firestore Authorisation and Security Rules
816 |
817 | - In ***lesson 192***, we change the **Security Rules** of our **Firestore Database** to allow read and write access to **authentified** users only. In the course, we add the following condition **`request.auth.uid != null`**. As of now (July 2022), we have to change this condition with the following one, regarding the [documentation](https://firebase.google.com/docs/rules/rules-and-auth#identify_users): **`request.auth != null`**
818 | ```
819 | rules_version = '2';
820 | service cloud.firestore {
821 | match /databases/{database}/documents {
822 | match /{document=**} {
823 | allow read, write: if
824 | request.auth != null // <-- here it is
825 |
826 | && request.time < timestamp.date(2022, 8, 5); // <-- if you wish to, you can add this condition that will only allow access to your database **before** a specified date
827 | }
828 | }
829 | }
830 | ```
831 | :exclamation::exclamation::information_source: Keep in mind that the above rules are not secure enough at all. They are only good for developing purpose, but the moment you release your application in production (or even when you simply share it with other people that you don't know), you have to reinforce your **Security Rules** to be less permisive and have better and stronger control on whose accessing your database, and what data they have access to.
832 |
833 |
--------------------------------------------------------------------------------
/images/guide/android_studio_sdk_tools.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DetainedDeveloper/App-Brewery-Flutter-Null-Safety/e7d84d33a6fcf29635d1ba75130fd6d72e099290/images/guide/android_studio_sdk_tools.png
--------------------------------------------------------------------------------