A presentation at DevFest Nantes 2018 in in Nantes, France by Horacio Gonzalez
Codelab Flutter Devfest Nantes 2018 Horacio Gonzalez & Pierre Tibulle @LostInBrittany Codelab Flutter @ptibulle
Horacio Gonzalez @LostInBrittany Spaniard lost in Brittany, developer, dreamer and all-around geek @LostInBrittany Codelab Flutter @ptibulle
Pierre Tibulle @ptibulle Developer, Jobcrafter and Maker @LostInBrittany Codelab Flutter @ptibulle
Before we begin If you don't have these tools, download them: ● Flutter SDK https://flutter.io/get-started/install/ ● Android Studio or Visual Studio Code with Flutter plugins/extensions https://flutter.io/get-started/editor/#androidstudio @LostInBrittany Codelab Flutter @ptibulle
Testing your install Create and test your first app https://flutter.io/get-started/test-drive/ @LostInBrittany Codelab Flutter @ptibulle
URL to copy/paste the code https://goo.gl/Yqo3Am @LostInBrittany Codelab Flutter @ptibulle
Let's look at the demo app ● The app is a stateless widget ● With a stateful widget inside ○ createState() method @LostInBrittany Codelab Flutter @ptibulle
Let's begin with a Hello World... import 'package:flutter/material.dart'; void main() { runApp( new Center( child: new Text( 'Hello, world!', textDirection: TextDirection.ltr, ), ), ); } You will never do that... @LostInBrittany Codelab Flutter @ptibulle
Extending stateless widget import 'package:flutter/material.dart'; class MyAppBar extends StatelessWidget { @override Widget build(BuildContext context) { return new Center( child: new Text( 'Hello, world!', textDirection: TextDirection.ltr, ), ); } } void main() { runApp(new MyAppBar()); } A widget’s main job is to implement a build function @LostInBrittany Codelab Flutter @ptibulle
Material Design App @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Galaxy', home: new Scaffold( appBar: new AppBar( title: const Text('Flutter Galaxy'), ), body: const Center( child: const Text('Hello World'), ), ), ); } Use the MaterialApp Widget @LostInBrittany Codelab Flutter @ptibulle
Add a Stateful Widget Create a new widget in the main.dart class GalaxiesState extends State<Galaxies> { // TODO Add build method } class Galaxies extends StatefulWidget { @override GalaxiesState createState() => new GalaxiesState(); } Add the build method in the GalaxiesState class @override Widget build(BuildContext context) { final String galaxy = "Milky Way"; return new Text(galaxy, textDirection: TextDirection.ltr); } @LostInBrittany Codelab Flutter @ptibulle
Use the new Widget Update the build of MyApp to call the new widget class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Galaxy', home: new Scaffold( appBar: new AppBar( title: const Text('Flutter Galaxy'), ), body: new Center( // const => new (no more a const) child: new Galaxies(), // call the new widget. ), ), ); } } @LostInBrittany Codelab Flutter @ptibulle
Create a scrolling listview 1/3 Create a list of galaxies and a TextStyle class GalaxiesState extends State<Galaxies> { final List<String> _galaxies = ["Milky Way Galaxy", "Andromeda Galaxy", "Black Eye Galaxy", "Galaxy UGC10445"]; final TextStyle _biggerFont = const TextStyle(fontSize: 18.0); Create a ListTile widget row as a method in GalaxyState Widget _buildRow(String galaxy) { return new ListTile( title: new Text( galaxy, style: _biggerFont, ), ); } @LostInBrittany Codelab Flutter @ptibulle
Create a scrolling listview 2/3 Then create another method to build the whole list: Widget _buildGalaxies() { return new ListView.builder( padding: const EdgeInsets.all(16.0), itemCount: _galaxies.length * 2, itemBuilder: (BuildContext _context, int i) { // Add a one-pixel-high divider on odd row if (i.isOdd) return new Divider(); // Else build a galaxy row final int index = i ~/ 2; return _buildRow(_galaxies[index]); } ); } @LostInBrittany Codelab Flutter @ptibulle
Create a scrolling listview 3/3 Update the build method of GalaxiesState return new Scaffold ( appBar: new AppBar( title: new Text('Flutter Galaxy'), ), body: _buildGalaxies(), ); Update the build method of MyApp return new MaterialApp( title: 'Flutter Galaxy', home: new Galaxies(), ); @LostInBrittany Codelab Flutter @ptibulle
Congratulation : Step 1 complete Your first minimal flutter App is OK ! In case of emergency, you can download this step : https://gitlab.com/ptibulle1/flutter_galaxy/tree/galaxy1 @LostInBrittany Codelab Flutter @ptibulle
Import Real Data : Galaxies Download the file “galaxies.json” : https://gitlab.com/ptibulle1/flutter_galaxy/blob/master/assets/data/galaxies.json and save it : ../assets/data/galaxies.json Update the pubspec.yaml to add the new asset (indent is important !) : … # To add assets to your application, add an assets section, like this: assets: - assets/data/galaxies.json ... @LostInBrittany Codelab Flutter @ptibulle
Create a galaxy class Create a galaxy.dart file class Galaxy { final String id; final String title; final String previewHref; final String description; Galaxy({ this.id, this.title, this.previewHref, this.description, }); } @LostInBrittany Codelab Flutter @ptibulle
Use Data : Galaxies 1/3 Import libraries and the new Galaxy class in the main.dart import 'dart:convert'; import 'dart:async'; import 'galaxy.dart'; Load the JSON file in a new asynchronous function of GalaxiesState (Begin) /// Retrieves a list of [galaxy] Future<void> _retrieveLocalGalaxies() async { final json = DefaultAssetBundle.of(context).loadString('assets/data/galaxies.json'); final data = JsonDecoder().convert(await json); if (data is! Map) { throw ('Data retrieved from API is not a Map'); } var collection = data['collection']; if (collection is! Map) { throw ('Collection is not a Map'); } ... @LostInBrittany Codelab Flutter @ptibulle
Use Data : Galaxies 2/3 Load the json file in a new asynchronous function of GalaxiesState (end) … var items = collection['items']; List<Galaxy> galaxies = []; for (var jsonMap in items) { var links = jsonMap['links'][0]; var previewHref = links['href']; var data = jsonMap['data'][0]; var title = data['title']; var id = data['nasa_id']; var description = data['description']; var galaxy = new Galaxy(id: id,title: title, previewHref: previewHref, description: description); galaxies.add(galaxy); } setState(() { _galaxies.clear(); _galaxies.addAll(galaxies); }); } @LostInBrittany Codelab Flutter @ptibulle
Use Data : Galaxies 3/3 Use the Galaxy class instead of String List<Galaxy> _galaxies = []; Widget _buildRow(Galaxy galaxy) { return new ListTile( title: new Text( galaxy.title, style: _biggerFont, ), ); } Surcharge the lifecycle function @override Future<void> didChangeDependencies() async { super.didChangeDependencies(); if (_galaxies.isEmpty) { await _retrieveLocalGalaxies(); } } @LostInBrittany Codelab Flutter @ptibulle
Congratulation : Step 2 complete In case of emergency, you can download this step : https://gitlab.com/ptibulle1/flutter_galaxy/tree/galaxy2 @LostInBrittany Codelab Flutter @ptibulle
Add a preview image Add a preview image in the leading part of the ListTile Widget _buildRow(Galaxy galaxy) { return new ListTile( leading: Container( width: 50.0, height: 50.0, decoration: new BoxDecoration( shape: BoxShape.circle, image: new DecorationImage( fit: BoxFit.fill, image: NetworkImage(galaxy.previewHref), ), ), ), … @LostInBrittany Codelab Flutter @ptibulle
Make it “clickable” Put the ListTile in an InkWell widget Widget _buildRow(Galaxy galaxy) { return InkWell( borderRadius: BorderRadius.circular(8.0), onTap: () => null, child: new ListTile( ... ), ); } @LostInBrittany Codelab Flutter @ptibulle
Navigate to the galaxy ! Use the “navigator” in a new function void _navigateToGalaxy(BuildContext context, Galaxy galaxy) { Navigator.of(context).push(MaterialPageRoute<Null>( builder: (BuildContext context) { return Scaffold( appBar: AppBar( elevation: 1.0, title: Text(galaxy.title), centerTitle: true, ), body: new Text(galaxy.description), ); }, )); } Call the function in the tap of the InkWell onTap: () => _navigateToGalaxy(context, galaxy), @LostInBrittany Codelab Flutter @ptibulle
Create a galaxy route widget Create a file galaxy_route.dart import 'package:flutter/material.dart'; import 'galaxy.dart'; class GalaxyRoute extends StatelessWidget { final Galaxy galaxy; const GalaxyRoute({ @required this.galaxy, }) : assert(galaxy != null); @override Widget build(BuildContext context) { return new Text(galaxy.description); } } Import and call this class in main.dart import 'galaxy_route.dart'; void _navigateToGalaxy(BuildContext context, Galaxy galaxy) { ... body: GalaxyRoute(galaxy: galaxy), @LostInBrittany Codelab Flutter @ptibulle
Improve the galaxy route widget Create a bigger font final TextStyle _biggerFont = const TextStyle(fontSize: 18.0); And build the widget @override Widget build(BuildContext context) { String urlImage = galaxy.previewHref.replaceAll("thumb", "orig"); return new ListView( children: <Widget>[ new Padding( padding: const EdgeInsets.all(16.0), child: new Text(galaxy.description, style: _biggerFont, )), Image.network(urlImage), ], ); } @LostInBrittany Codelab Flutter @ptibulle
The image is loading... Import a package in pubspec.yaml transparent_image: ^0.1.0 And build the widget with a progress indicator import 'package:transparent_image/transparent_image.dart'; // Image.network(urlImage), new Stack( children: <Widget>[ new Padding( padding: const EdgeInsets.all(50.0), child: Center(child: CircularProgressIndicator()) ), Center( child: FadeInImage.memoryNetwork( placeholder: kTransparentImage, image: urlImage ), ), ], ), @LostInBrittany Codelab Flutter @ptibulle
Congratulation : Step 3 complete In case of emergency, you can download this step : https://gitlab.com/ptibulle1/flutter_galaxy/tree/galaxy3 @LostInBrittany Codelab Flutter @ptibulle
Fetch data from the internet Import http package in pubspec.yaml http: ^0.11.0 and in main.dart call the nasa api and use a spinner while the data is empty import 'package:http/http.dart' as http; Future<void> _retrieveLocalGalaxies() async { final response = await http.get('https://images-api.nasa.gov/search?q=galaxy&media_type=image'); var json; if (response.statusCode == 200) { json = response.body; } else { json = DefaultAssetBundle.of(context).loadString('assets/data/galaxies.json'); } ... Widget _buildGalaxies() { if (_galaxies.isEmpty) return new Padding(padding: const EdgeInsets.all(50.0), child: Center(child: CircularProgressIndicator())); ... @LostInBrittany Codelab Flutter @ptibulle
A zoomable image page 1/2 Import a package in pubspec.yaml zoomable_image: ^1.2.1 Create a galaxy_zoom.dart file import 'package:flutter/material.dart'; import 'package:zoomable_image/zoomable_image.dart'; import 'galaxy.dart'; class GalaxyZoom extends StatelessWidget { final Galaxy galaxy; const GalaxyZoom({ @required this.galaxy, }) : assert(galaxy != null); @override Widget build(BuildContext context) { String urlImage = galaxy.previewHref.replaceAll("thumb", "orig"); return new ZoomableImage(NetworkImage(urlImage)); } } @LostInBrittany Codelab Flutter @ptibulle
A zoomable image page 2/2 Update galaxy_route.dart import 'galaxy_zoom.dart'; /// Navigates to the [GalaxyZoom]. void _navigateToGalaxyZoom(BuildContext context, Galaxy galaxy) { Navigator.of(context).push(MaterialPageRoute<Null>( builder: (BuildContext context) { return Scaffold( appBar: AppBar(elevation: 1.0, title: Text(galaxy.title), centerTitle: true, ), body: GalaxyZoom(galaxy: galaxy), ); }, )); } // Detect Tap on the image new GestureDetector( onTap: () => _navigateToGalaxyZoom(context, galaxy), child: new Stack( ... ), ), @LostInBrittany Codelab Flutter @ptibulle
Create a dark galaxy theme In the main.dart add a ThemeData to the MaterialApp return new MaterialApp( title: 'Flutter Galaxy', theme: ThemeData( brightness: Brightness.dark, // Default style for widgets primaryColor: Colors.blueGrey[900],// AppBar dividerColor: Colors.blueGrey[600], // Divider scaffoldBackgroundColor: Colors.black, // Background splashColor: Colors.blueGrey[200], // InkWell accentColor: Colors.pink, // spinner ), home: new Galaxies(), ); All pages are impacted by the Theme ... Modify the colors and play with the hot reload ! https://gitlab.com/ptibulle1/flutter_galaxy/tree/galaxy4 @LostInBrittany Codelab Flutter @ptibulle
Congratulation : Step 4 complete This is the end of the codelab ! In case of emergency, you can download this step : https://gitlab.com/ptibulle1/flutter_galaxy/tree/galaxy4 @LostInBrittany Codelab Flutter @ptibulle
Thank you! @LostInBrittany Codelab Flutter @ptibulle
Flutter est un SDK open-source pour créer des application iOS et Android performantes et adaptées à l’OS. ‘Un peu comme React Native ou Xamarin ?’ vous pourriez vous demander ? Oui, un peu… mais avec une intégration avec les widgets natifs et des niveaux de performance qui vont au delà de ce que Xamarin ou React Native peuvent proposer, le tout avec très peu de lignes de code.
Dans ce bootcamp nous allons faire une introduction pratique à Flutter. Et lorsque je dis pratique, c’est du pratique, vous allez coder… Vous arrivez à la séance équipés d’un ordinateur portable, avec Android Studio ou VS Code et vous partez à la fin en ayant développé une petite application Flutter tournant sur Android et iOS, et plus important encore, en ayant intégré les principes basiques du développement sur cette plate-forme.
Le Bootcamp Flutter est donc une introduction accélérée et pratique au développement d’applications mobiles avec Flutter. Vous allez apprendre, mettre les mains dans le cambouis, coder et vous allez enfin cocher la case "Apprendre à coder des applications mobiles" dans votre ToDo-list.
The following resources were mentioned during the presentation or are useful additional information.
Here’s what was said about this presentation on social media.
Salle comble au #DevFestNantes pour le codelab @flutterio, animé par le duo @LostInBrittany et @ptibulle 👌 pic.twitter.com/yJVdBIB3Ij
— Nicolas Pennec (@NicoPennec) October 19, 2018
C'est parti pour le #codlab #flutter avec @P4SC4L_VINCENT organisé par @ptibulle et @LostInBrittany !! #DevFestNantes #DevFest18 #devfest #dart pic.twitter.com/tZIpBp9UtW
— Adrien LASSELLE (@AdrienLASSELLE) October 19, 2018
Ca code toujours au Codelab @flutterio de @ptibulle et Horacio (aka @LostInBrittany ) #DevFedt pic.twitter.com/jCGxkz6Aq6
— ASI (@ASI_Informatic) October 19, 2018