“30 Days Of Flutter” with FlutLab: Code Party 9
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!🚀
- 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!