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

Myroslava Drobnych
9 min readFeb 22, 2021

Creators of the #30DaysOfFlutter do their best to improve your Flutter Development skills. You can learn something new even during Live Streams. Awesome Flutter Developer Jeroen Meijer (Jay) conducts a live programming session with explanations and jokes. He created the “Rocket Guide” app, which you can repeat too.
If you didn’t have the opportunity to watch Live Stream or it’s difficult for you to learn with video only — don’t worry. We prepared a step-by-step instruction of the whole “Rocket Guide” app for you!

Are you ready? 3…2…1… Liftoff!🚀

  1. Go to https://flutlab.io — Flutter online IDE.

2. Go to your Profile and press the “New Project” button.

3. Create a new project with “Code Base: Stateful Click Counter”. Ignore the warning… ;)

4. Great! The project was successfully created and appeared in your profile. This project contains a simple Click Counter app. Let’s open it!

5. Press the “Build Project” button. FlutLab will perform a Web Build for you.

6. Our project works fine so we can start introducing some changes. Delete all classes from the main.dart file (look at the screenshot below).

7. Replace this fragment of code void main() => runApp(MyApp()); with this one void main() => runApp(RocketGuideApp()); .

8. Good job, you changed the class name! It’s time to create it now.

Let’s create anapp folder insidelib folder. Click the “Create folder” icon (look at the screenshots below):

8. Create the app.dart file inside app folder (see the screenshots below):

9. app.dart file was successfully created. Let’s begin with this line inside it:

import 'package:flutter/material.dart';

Start typing “stl…” letters and you will see the “stateless” hint. If you’ll choose it, FlutLab will build the Stateless Widget itself (look at the screenshots below).

10. Change the “Widget1” class name to “RocketGuideApp” and replace a return null line with these lines of code (press Ctrl+S for saving and formatting):

return MaterialApp(
home: HomeScreen(),
);

11. Let’s import a new file to the main.dart. Go to main.dart and write this line of code:

import 'package:rocket_guide/app/app.dart';

12. Create a home folder inside the lib folder. After that, create a home_screen.dart file inside home.

Add import 'package:flutter/material.dart';and StatelessWidget with this code:

class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Rocket Guide'),
),
);
}
}

13. Back to app.dart and add the import line

import 'package:rocket_guide/home/home_screen.dart';

14. Press the “Hot Reload” or “Build Project” button.

15. Let’s create a theme for our “Rocket Guide App”. In the app folder create thetheme.dart file. Add this code to it

import 'package:flutter/material.dart';class AppTheme {
static ThemeData light() {
return ThemeData(
primaryColor: _primaryColor,
);
}
static ThemeData dark() {
return ThemeData(
brightness: Brightness.dark,
primaryColor: _primaryColor,
);
}

static const _primaryColor = Colors.black;
}

Go to app.dart and import the theme.dart file

import 'package:rocket_guide/app/theme.dart';

add new lines to MaterialApp():

theme: AppTheme.light(),
darkTheme: AppTheme.dark(),

15. Press the “Hot Reload” or “Build Project” button. Wow, it works! ;)

16. Let’s add some details to our app.

In home folder create rocket_list_tile.dart file and add this code

import 'package:flutter/material.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 ListTile(
onTap: onTap,
title: const Text('Hello Rocket'),
subtitle: const Text('I am a subtitle'),
);
}
}

18. Go to home_screen.dart . Let’s add body to the Scaffold():

body: ListView(
children: [
RocketListTile(
onTap: () {},
rocket: null,
),
],
),

Add import 'package:rocket_guide/home/rocket_list_tile.dart'; too

19. Open the lib folder. Create a backend folder and backend.dart file inside it. Add this code to the backend.dart file:

import 'package:meta/meta.dart';class Rocket{
const Rocket({
@required this.name,
@required this.description,
this.flickrImages = const [],
}) : assert(name != null), assert(description != null);

final String name;
final String description;
final List<String> flickrImages;
}

20. It’s time to add the “meta” package to pubspec.yaml

meta: ^1.3.0-nullsafety.3

21. Open rocket_list_tile.dart file and add this to imports:

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

22. Go to home_screen.dart and do the same

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

Add this code with some rocket details to home_screen.dart file too

const _rocket = Rocket(name: ‘Falcon Heavy’, description: ‘Hello world’);

Replace rocket: null, with rocket: _rocket, then

23. Press the “Build Project” button or “Hot Reload”.

24. Let’s make some more changes to our theme. It will be cool to add fonts and some details to colors.

Open the pubspec.yaml file and add this package:

google_fonts: ^1.1.2

25. Open the theme.dart file. Remove all content from the file and replace it with this code:

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
class AppTheme {
static ThemeData light() {
final textTheme = _getTextTheme(brightness: Brightness.light);
return ThemeData(
primaryColor: _primaryColor,
accentColor: _accentColor,
textTheme: textTheme,
primaryTextTheme: textTheme,
);
}
static ThemeData dark() {
final textTheme = _getTextTheme(brightness: Brightness.dark);

return ThemeData(
brightness: Brightness.dark,
primaryColor: _primaryColor,
accentColor: _accentColor,
textTheme: textTheme,
primaryTextTheme: textTheme,
);
}
static const _primaryColor = Colors.black;
static const _accentColor = Colors.white;
static _getTextTheme({@required Brightness brightness}) {
final themeData = ThemeData(brightness: brightness);
return GoogleFonts.exo2TextTheme(themeData.textTheme).copyWith(
headline1: GoogleFonts.orbitron(),
headline2: GoogleFonts.orbitron(),
headline3: GoogleFonts.orbitron(),
headline4: GoogleFonts.orbitron(),
headline5: GoogleFonts.orbitron(),
headline6: GoogleFonts.orbitron(),
);
}
}

26. Press the “Build Project” button. It looks good, isn’t it?

27. Let’s add more details to the description of rockets. Go to the backend.dart file and replace all code with this (Ctrl+S)

import 'package:meta/meta.dart';class Backend {
const Backend(this.hostUrl);
final String hostUrl;
Future<List<Rocket>> getRockets() async {
final url = '$hostUrl/rockets';
return const [
Rocket(
id: 'flacon_1',
name: 'Falcon 1',
description: 'The Falcon 1 was an expendable launch system privately '
'developed and manufactured by SpaceX during 2006–2009. '
'On 28 September 2008, Falcon 1 became the first '
'privately-developed liquid-fuel launch vehicle to go into orbit '
'around the Earth.',
active: false,
boosters: 0,
flickrImages: [
'https://imgur.com/DaCfMsj.jpg',
'https://imgur.com/azYafd8.jpg',
],
),
Rocket(
id: 'falcon_heavy',
name: 'Falcon Heavy',
description: 'With the ability to lift into orbit over 54 metric tons '
'(119,000 lb) — a mass equivalent to a 737 jetliner loaded with '
'passengers, crew, luggage and fuel — Falcon Heavy can lift more '
'than twice the payload of the next closest operational vehicle, '
'the Delta IV Heavy, at one-third the cost.',
active: true,
boosters: 2,
flickrImages: [
'https://farm5.staticflickr.com/4599/38583829295_581f34dd84_b.jpg',
'https://farm5.staticflickr.com/4645/38583830575_3f0f7215e6_b.jpg',
'https://farm5.staticflickr.com/4696/40126460511_b15bf84c85_b.jpg',
'https://farm5.staticflickr.com/4711/40126461411_aabc643fd8_b.jpg',
],
),
];
}
}
class Rocket {
const Rocket({
@required this.id,
@required this.name,
@required this.description,
@required this.active,
@required this.boosters,
@required this.flickrImages,
});
final String id;
final String name;
final String description;
final bool active;
final int boosters;
final List<String> flickrImages;
}

28. Go to app.dart file and replace home: HomeScreen(), with this code:

home: Provider.value(
value: backend,
child: HomeScreen(),
),

29. Go to home_screen.dart file and remove this line:

const _rocket = Rocket(name: ‘Falcon Heavy’, description: ‘Hello world’);

30. Go to main.dart and replace all with this code:

import 'package:flutter/material.dart';
import 'package:rocket_guide/app/app.dart';
import 'package:rocket_guide/backend/backend.dart';
void main() {
final backend = Backend('https://api.spacexdata.com/v4');
runApp(
RocketGuideApp(
backend: backend,
),
);
}

31. It’s time to add a provider package to pubspec.yaml file

provider: ^4.3.3

32. Open theapp.dart file and replace all with this code:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:rocket_guide/app/theme.dart';
import 'package:rocket_guide/backend/backend.dart';
import 'package:rocket_guide/home/home_screen.dart';
class RocketGuideApp extends StatelessWidget {
const RocketGuideApp({
@required this.backend,
}) : assert(backend != null);
final Backend backend; @override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: AppTheme.light(),
darkTheme: AppTheme.dark(),
home: Provider.value(
value: backend,
child: HomeScreen(),
),
);
}
}

33. Go to home_screen.dart and replace all with new code too:

import 'package:flutter/material.dart';
import 'package:rocket_guide/home/rocket_list_tile.dart';
import 'package:rocket_guide/backend/backend.dart';
import 'package:provider/provider.dart';
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Rocket Guide'),
),
body: FutureBuilder(
future: context.read<Backend>().getRockets(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Center(
child: Text('An error occurred.'),
);
} else if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
} else {
final rockets = snapshot.data;
return ListView(
children: [
for (final rocket in rockets)
RocketListTile(
rocket: rocket,
onTap: () {},
),
],
);
}
},
),
);
}
}

34. Open the rocket_list_tile.dart file, and replace these lines of code inside ListTile()

title: const Text(‘Hello Rocket’),
subtitle: const Text(‘I am a subtitle’),

with these lines

title: Text(rocket.name),
subtitle: Text(rocket.description),

34. Press the “Build Project” button.

35. Our app already looks good, but it still needs some improvements. Let’s add pictures, effects and slightly reduce the description.

Open the rocket_list_tile.dart file, delete its content, and replace it with this code:

import 'package:flutter/material.dart';
import 'package:rocket_guide/backend/backend.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 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: Hero(
tag: 'hero-${rocket.id}-name',
child: Text(rocket.name),
),
subtitle: Text(
rocket.description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
trailing: const Icon(Icons.chevron_right_sharp),
);
}
}

36. Press the “Hot Reload” button. What do you think about the result? It’s impressive, isn’t it?

37. Let’s implement navigation.

Create a rocket_details folder inside the lib folder. Create therocket_details_screen.dart file inside rocket_details . Add this code to it:

import 'package:flutter/material.dart';
import 'package:rocket_guide/backend/backend.dart';
class RocketDetailsScreen extends StatelessWidget {
const RocketDetailsScreen({
Key key,
@required this.rocket,
}) : assert(rocket != null),
super(key: key);
final Rocket rocket;

@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Scaffold(
appBar: AppBar(
title: Text(rocket.name),
),
body: ListView(
children: [
if (rocket.flickrImages.isNotEmpty) _ImageHeader(rocket: rocket),
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
rocket.name,
style: textTheme.headline6,
),
const SizedBox(height: 16.0),
Text(
rocket.description,
style: textTheme.subtitle1,
),
],
),
),
],
),
);
}
}
class _ImageHeader extends StatelessWidget {
const _ImageHeader({
Key key,
@required this.rocket,
}) : super(key: key);
final Rocket rocket;

@override
Widget build(BuildContext context) {
return SizedBox(
height: 250,
child: Hero(
tag: 'hero-${rocket.id}-image',
child: PageView(
children: [
for (final url in rocket.flickrImages)
Image.network(
url,
fit: BoxFit.cover,
),
],
),
),
);
}
}

38. Go to home_screen.dart file and add this line:

import 'package:rocket_guide/rocket_details/rocket_details_screen.dart';

Replace return ListView(…) with

return ListView(
children: [
for (final rocket in rockets) ... [
RocketListTile(
rocket: rocket,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) {
return RocketDetailsScreen(rocket: rocket);
},
),
);
},
),
const Divider(height: 0.0),
]
],
);

39. Press the “Hot Reload” or “Build Project” button.

My congratulations, it looks fantastic! You’re the one who managed to create such an app from scratch :)

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

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

Next version of “Rocket Guide”: “30 Days Of Flutter” with FlutLab: Code Party 10

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!

--

--