“30 Days Of Flutter” with FlutLab: Code Party 11

Myroslava Drobnych
9 min readFeb 27, 2021

Do you want to become a successful Flutter Developer? You have to learn from professionals then. Jeroen Meijer (Jay) is the right person to learn from. During #30DaysOfFlutter Live Streams he developed a “Rocket Guide” app that you can also replicate. This article is the final part of this exciting journey. As a result, you’ll get an awesome app with data stored in the Firestore Cloud.

If you missed previous “Code Parties” of this project, don’t worry. You can explore them following the links below:
“30 Days Of Flutter” with FlutLab: Code Party 9 — “Rocket Guide” app, part 1.
“30 Days Of Flutter” with FlutLab: Code Party 10 — “Rocket Guide” app, part 2.

If you want to get the latest version of the “Rocket Guide” app, pick it at FlutLab Widget Bay: “Rocket Guide — part 2”

Or, clone it from GitHub (https://github.com/mdrobnych/rocket_guide_part_2) to FlutLab. This repo is a replica of the original code from FlutterCommunity GitHub

It’s time to start the third “Code Party” of the “Rocket Guide” App! Our journey has to continue!

  1. Let’s add Firebase!
    Go to https://firebase.google.com/. Sign in or Sign up to your Firebase account. Press the “Create a project” button.

2. Give a name to your Firebase project and click “Continue”.

3. We are not planning to use Analytics, so you can turn it off. Click “Create project” then.

4. Great! Your Firebase project is ready to use. Click “Continue”.

5. Select “Authentication” in the left menu and click the “Get started” button.

6. Scroll down and find the Anonymous field. Turn it on and press the “Save” button.

7. Select “Cloud Firebase” in the left menu and click the “Create database” button.

8. In the Secure rules for Cloud Firestore dialog, select “Start in test mode”, and click Next then.

Important: Test mode allows any user to read and write to your database, which is convenient during development. However, this can be a security risk. Before releasing any app to production, you should add security rules.

9. Select your location setting and click the “Enable” button.

10. Greate, you can create a database now. Press the “Start Collection” link.

11. Set the collection’s name to users and click Next then.

12. Fill in all fields the same way as shown below. Click “Save”

13. Now you have ready to use Storage on the Cloud Firestore.

14. Select the “Project Overview” in the left menu. We are going to create an app for a web platform. So, click the button with the “Web” tooltip.

However, if you want to build the “Rocket guide” app for iOS or Android, use appropriate for your target Firebase settings.

15. Enter the name of your app and click the “Register app” button.

16. Copy this fragment of code and click “Continue to console”.

17. Back to FlutLab.

Find a web folder inside your project files’ tree. If your project doesn’t have a web folder, don’t panic! Download this zip file. Click on therocket_guide folder and some icons will appear near it. Click the “Upload” icon.

18. In the opened window, select your zip and press the “Upload” button.

19. Great, you have a web folder now!

20. Open theindex.html file. After a <body> tag, paste the fragment of code that you’ve copied in the Firebase SDK. Add these lines too (see the screenshot below):

<script src="https://www.gstatic.com/firebasejs/8.2.9/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.2.9/firebase-firestore.js"></script>

21. Open the pubspec.yaml file and add a dependency for Firebase:
-firebase_core: 0.7.0 : a Flutter plugin to use the Firebase Core API, which enables connecting to multiple Firebase apps.
-firebase_auth: 0.20.1 : a Flutter plugin to use the Firebase Authentication API.
-cloud_firestore: ^0.16.0+1 : a Flutter plugin to use the Cloud Firestore API.

22. Openbackend.dart file and add these imports:

import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

Add this code too (press Ctrl+S for saving and formatting):

Stream<User> get currentUserStream=>FirebaseAuth.instance.userChanges();Future<void> signUp() async {
await FirebaseAuth.instance.signInAnonymously();
}
Stream<List<String>> get favoritedRockets {
final userId = FirebaseAuth.instance.currentUser.uid;
return FirebaseFirestore.instance
.collection('users')
.doc(userId)
.snapshots()
.map((documentSnapshot) {
if (!documentSnapshot.exists) {
return [];
}
return documentSnapshot.data()['favorited_rockets'] as List<String>;
});
}

23. Go to main.dart and add this to imports:

import 'package:firebase_core/firebase_core.dart';

Write these lines too:

Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();

24. Press the “Build Project” button. Great, all works well ;)

25. Open app.dart and add this to imports:

import 'package:firebase_auth/firebase_auth.dart';

Replace Widget build(BuildContext context) {…} with this code:

Widget build(BuildContext context) {
return Provider.value(
value: backend,
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: AppTheme.light(),
darkTheme: AppTheme.dark(),
home: StreamBuilder<User>(
stream: backend.currentUserStream,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return SignUpScreen();
}
return HomeScreen();
}
),
),
);
}

26. Let’s create a sign_up folder inside the lib folder. Click the “Create folder” icon (look at the screenshots below):

27. Create the sign_up_screen.dart file inside a sign_up folder (see the screenshots below):

28. sign_up_screen.dart file was successfully created! Let’s start it with this line:

import 'package:flutter/material.dart';

Start typing “stf…” letters and you will see the “stateful” hint. If you’ll choose it, FlutLab will build the Stateful Widget itself. Change the “Widget1” class name to SignUpScreen”. Let’s add some text to the Widget.

class SignUpScreen extends StatefulWidget {
@override
_SignUpScreenState createState() => _SignUpScreenState();
}
class _SignUpScreenState extends State<SignUpScreen> {
var isLoading = false;
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Scaffold(
body: SizedBox(
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Welcome to',
style: textTheme.headline5,
),
Text(
'Rocket Guide',
style: textTheme.headline4,
),
],
),
),
);
}
}

29. Go to app.dart and add:

import 'package:rocket_guide/sign_up/sign_up_screen.dart';

Press the “Build Project” button. Oh, here it is :)

30. Back to sign_up_screen.dart. Let’s add a button for sign-up action.

const SizedBox(height: 16.0),
ElevatedButton.icon(
onPressed: () {},
icon: const Text('🚀'),
label: const Text('Sign Up'),
),

Click the “Hot Reload” button.

31. Let’s make our “Sign Up” button interactive. Find ElevatedButton.icon(…) and replace it with this code:

ElevatedButton.icon(
onPressed: isLoading
? null
: () {
final backend = context.read<Backend>();
backend.signUp();
setState(() {
isLoading = true;
});
},
icon: const Text('🚀'),
label: isLoading
? const CircularProgressIndicator()
: const Text('Sign Up'),
),

Add these lines to imports too:

import 'package:provider/provider.dart';
import 'package:rocket_guide/backend/backend.dart';

32. Open backend.dart and add signOut:

Future<void> signOut() async {
await FirebaseAuth.instance.signOut();
}

33. It remains to add the “Sign Out” button to the screen. Open home_screen.dart and add this part to AppBar:

leading: IconButton(
onPressed: () {
context.read<Backend>().signOut();
},
icon: const Icon(Icons.logout),
),

34. Click the “Build Project” button. Perfect, all works well!

35. Let’s add the “mark as favorite” button. Open the rocket_details_screen.dart file. Find Scaffold and add this code (see the screenshot below):

actions: [
StreamBuilder<List<String>>(
stream: context.read<Backend>().favoritedRockets,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
final favoritedRockets = snapshot.data;
final isRocketFavorited = favoritedRockets.contains(rocket.id);
return IconButton(
onPressed: () {},
icon: isRocketFavorited
? const Icon(
AntIcons.heart,
color: Colors.redAccent,
)
: const Icon(AntIcons.heart_outline),
);
}
),
],

Add this to imports:

import 'package:provider/provider.dart';

Press the “Hot Reload” button.

36. Let’s add some interaction to our icon. Find onPressed: () {} and add this code inside it:

context.read<Backend>().setFavoritedRocket(
id: rocket.id,
favorited: !isRocketFavorited,
);

37. Let’s save “favorite” rockets to Firebase. Open backend.dart and add this code:

Future<void> setFavoritedRocket({
@required String id,
@required bool favorited,
}) async {
final currentFavoritedRockets = await favoritedRockets.first;

if (favorited && !currentFavoritedRockets.contains(id)) {
currentFavoritedRockets.add(id);
} else if (!favorited && currentFavoritedRockets.contains(id)){
currentFavoritedRockets.remove(id);
}
final userId = FirebaseAuth.instance.currentUser.uid; await FirebaseFirestore.instance
.collection('users')
.doc(userId)
.set({'favorited_rockets': currentFavoritedRockets});
}

Find this line:

return List<String>.from(documentSnapshot.data()['favorited_rockets']);

and replace it with this one:

return List<String>.from(documentSnapshot.data()['favorited_rockets']);

38. Let’s check if all this works ;) Open Cloud Firestore. Press “Hot Reload” in FlutLab. Add one of the rockets to favorited_rockets.

Great, it works!

39. Let’s sign favorite rockets with little hearts on the main screen!

Open rocket_list_tile.dart. Replace all content of the file with this:

import 'package:flutter/material.dart';
import 'package:rocket_guide/backend/backend.dart';
import 'package:provider/provider.dart';
import 'package:ant_icons/ant_icons.dart';
class RocketListTile extends StatelessWidget {
const RocketListTile({
Key key,
@required this.rocket,
@required this.onTap,
}) : assert(rocket != null),
super(key: key);
final Rocket rocket;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return StreamBuilder<List<String>>(
stream: context.read<Backend>().favoritedRockets,
builder: (context, snapshot) {
final hasFavorited = snapshot.hasData && snapshot.data.contains(rocket.id);
return ListTile(
isThreeLine: true,
onTap: onTap,
leading: rocket.flickrImages.isEmpty
? null
: Hero(
tag: 'hero-${rocket.id}-image',
child: Material(
clipBehavior: Clip.antiAlias,
borderRadius: BorderRadius.circular(8.0),
child: AspectRatio(
aspectRatio: 3 / 2,
child: Image.network(
rocket.flickrImages.first,
fit: BoxFit.cover,
),
),
),
),
title: Row(
mainAxisSize: MainAxisSize.min,
children: [
Hero(
tag: 'hero-${rocket.id}-name',
child: Text(rocket.name),
),
if (hasFavorited) ...const [
SizedBox(width: 4.0),
Icon(
AntIcons.heart,
color: Colors.redAccent,
size: 16.0,
),
],
],
),
subtitle: Text(
rocket.description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
trailing: const Icon(Icons.chevron_right_sharp),
);
});
}
}

40. Click the “Hot Reload” button. Congrats! You did it!

If you need more information about the “Rocket Guide” app then watch the #30DaysOfFlutter live stream record.

Awesome! You finished the 25th-day of the agenda #30DaysOfFlutter!

If you’ve stacked with some problems, visit FlutLab Widget Bay and find the source code of this challenge under the 30 Days Of Flutter category. You can just fork this Widget Bay project into your FlutLab account and play with it.
Good luck!

--

--