Slide 1

Slide 1 text

Flutter

Slide 2

Slide 2 text

Florent Champigny Florent37 florent_champ http://bit.ly/AndroidDevFr Slack : Android Dev FR

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Flutter Découverte Design Affichage de listes Interaction avec webservice Appel à du code natif android

Slide 5

Slide 5 text

Découvrez Flutter

Slide 6

Slide 6 text

Découvrez Flutter https://github.com/flutter Fuschia

Slide 7

Slide 7 text

Découvrez Flutter https://github.com/flutter VS Code Android Studio Vim…

Slide 8

Slide 8 text

Découvrez Flutter https://flutter.dev/ • Design beautiful apps • Productively build apps • Create faster apps (60fps) • Publish mobile, web, & desktop apps

Slide 9

Slide 9 text

Android iOS Mac Linux Windows Web (beta)

Slide 10

Slide 10 text

Skia

Slide 11

Slide 11 text

+ Flare

Slide 12

Slide 12 text

La puissance de Flutter Produire une application rapidement

Slide 13

Slide 13 text

Declarative Tree no more View (xml, xib) / Controller Reactive programming Dart 2.0 interpreted on debug, compiled on release Flutter Hot reload

Slide 14

Slide 14 text

Flutter Twitter on Android

Slide 15

Slide 15 text

Design avec Flutter

Slide 16

Slide 16 text

TweetView #atomicDesign

Slide 17

Slide 17 text

TweetLabels TweetLink TweetButtons Text Image TweetView #atomicDesign

Slide 18

Slide 18 text

TweetLabels TweetLink TweetButtons Text Image Column TweetView #atomicDesign

Slide 19

Slide 19 text

TweetLabels TweetLink TweetButtons Text Image Row TweetView #atomicDesign

Slide 20

Slide 20 text

TweetLabels TweetLink TweetButtons Text Image Column TweetView #atomicDesign

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

class TweetLabels extends StatelessWidget { @override Widget build(BuildContext context) { return Row( children: [ Text( "La plume du figaro" ), SizedBox(width: 6), //spacing Text( "@FigaroPlume" ), SizedBox(width: 6), //spacing Text( "27 mins" ), ], ); } }

Slide 23

Slide 23 text

Text( "La plume du figaro", style: TextStyle( color: Colors.black, fontWeight: FontWeight.w700, fontSize: 16, ), ), SizedBox(width: 6), //spacing Text( "@FigaroPlume", style: TextStyle( color: Colors.grey, fontWeight: FontWeight.w400, fontSize: 14, ), ), SizedBox(width: 6), //spacing Text( "27 mins", style: TextStyle( color: Colors.grey, fontWeight: FontWeight.w600, fontSize: 14, ), ),

Slide 24

Slide 24 text

TweetLabels( accountName: "La plume du figaro", accountPseudo: "@FigaroPlume", tweetTime: "27 mins", ) What we want #atomicDesign

Slide 25

Slide 25 text

class TweetLabels extends StatelessWidget { String accountName; String accountPseudo; String tweetTime; TweetLabels({Key key, this.accountName, this.accountPseudo, this.tweetTime}) : super(key: key); @override Widget build(BuildContext context) { return Row( children: ... ); } } StatelessWidgets : updated when re-created StatefullWidget : can be updated with a setState method

Slide 26

Slide 26 text

Row( children: [ Text( this.accountName, //"La plume du figaro", maxLines: 1, style: … ), SizedBox(width: 6), Flexible( child: Text( this.accountPseudo, //"@FigaroPlume", maxLines: 1, overflow: TextOverflow.ellipsis, style: … ), ), SizedBox(width: 6), Text( this.tweetTime, //"27 mins", style: … ), ], );

Slide 27

Slide 27 text

TweetLabels( accountName: "La plume du figaro", accountPseudo: "@FigaroPlume", tweetTime: "27 mins", ) What we want #atomicDesign

Slide 28

Slide 28 text

Interaction utilisateur avec Flutter

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

class TweetButtons extends StatelessWidget { @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset("assets/twitter-icons/twitter-reply-outline.png"), SizedBox(width: 4), //margin Text("1") ], ); } }

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

Les développeurs mobile

Slide 33

Slide 33 text

Les développeurs web F5

Slide 34

Slide 34 text

return Row( children: [ Expanded( flex: 1, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset("assets/twitter-icons/twitter-reply-outline.png"), SizedBox(width: 4), Text("1") ])), Expanded( flex: 1, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset("assets/twitter-icons/twitter-retweet.png"), SizedBox(width: 4), Text("1") ])), Expanded( flex: 1, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset("assets/twitter-icons/twitter-like-outline.png"), SizedBox(width: 4), Text("1") ])), Expanded( flex: 1, child: Image.asset("assets/twitter-icons/twitter-share.png"), ) ]); Expanded( flex: 1, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset("assets/twitter-icons/twitter-reply-outline.png"), SizedBox(width: 4), Text("1") ])) Expanded( flex: 1, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset("assets/twitter-icons/twitter-reply-outline.png"), SizedBox(width: 4), Text("1") ]))

Slide 35

Slide 35 text

Expanded( flex: 1, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset("assets/twitter-icons/twitter-reply-outline.png"), SizedBox(width: 4), Text("1") ]))

Slide 36

Slide 36 text

Expanded( flex: 1, child: GestureDetector( onTap: () { //clicked }, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset("assets/twitter-icons/twitter-reply-outline.png"), SizedBox(width: 4), Text("1") ]), ))

Slide 37

Slide 37 text

TweetButtons( commentClicked: ( ) { //called when user clicks }, ) What we want #atomicDesign

Slide 38

Slide 38 text

class TweetButtons extends StatelessWidget { TweetButtons({Key key}) : super(key: key); @override Widget build(BuildContext context) { return Row( children: [ Expanded( flex: 1, child: GestureDetector( onTap: () { /* click happened */ }, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset("assets/twitter-icons/twitter-reply-outline.png"), SizedBox(width: 4), Text("1") ], ),

Slide 39

Slide 39 text

class TweetButtons extends StatelessWidget { Function commentClicked; TweetButtons({Key key, this.commentClicked}) : super(key: key); @override Widget build(BuildContext context) { return Row( children: [ Expanded( flex: 1, child: GestureDetector( onTap: commentClicked, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset("assets/twitter-icons/twitter-reply-outline.png"), SizedBox(width: 4), Text("1") ], ),

Slide 40

Slide 40 text

TweetButtons( commentClicked: () { //called when user clicks }, ) #atomicDesign

Slide 41

Slide 41 text

TweetButtons( commentClicked: () { }, likeClicked: () { }, retweetClicked: () { }, shareClicked: () { }, ) #atomicDesign

Slide 42

Slide 42 text

Afficher des listes avec Flutter

Slide 43

Slide 43 text

Afficher des listes avec Flutter Android ViewHolder Adapter RecyclerView LinearLayoutManager Recycled views Listeners for Click

Slide 44

Slide 44 text

Afficher des listes avec Flutter Widgets ListView

Slide 45

Slide 45 text

void main() => runApp(MyApp()); class MyApp extends StatelessWidget { Tweet tweet1 = Tweet(…); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Twitter', theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( body: TweetView(tweet: tweet1) ), ); } }

Slide 46

Slide 46 text

void main() => runApp(MyApp()); class MyApp extends StatelessWidget { Tweet tweet1 = Tweet(…); Tweet tweet2 = Tweet(…); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Twitter', theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( body: ListView(children: [ TweetView(tweet: tweet1), TweetView(tweet: tweet2), ] ), ); } }

Slide 47

Slide 47 text

void main() => runApp(MyApp()); class MyApp extends StatelessWidget { List tweets = […]; @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Twitter', theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( body: ListView(children: tweets .map( (t) => TweetView(tweet: t) ) .toList() ), ); } }

Slide 48

Slide 48 text

Appel réseau avec Flutter

Slide 49

Slide 49 text

Appel réseau avec Flutter Afficher une image depuis une url

Slide 50

Slide 50 text

Afficher une image depuis une url

Slide 51

Slide 51 text

"http://lorempixel.com/800/800/" Afficher une image depuis une url

Slide 52

Slide 52 text

Image.network( "http://lorempixel.com/800/800/", fit: BoxFit.cover, height: 140, ) Afficher une image depuis une url

Slide 53

Slide 53 text

Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Image.network( "http://lorempixel.com/800/800/", fit: BoxFit.cover, height: 140, ), ... ) Afficher une image depuis une url

Slide 54

Slide 54 text

Appel réseau avec Flutter Télécharger et parser du json

Slide 55

Slide 55 text

dependencies: http: ^0.12.0+1 json_annotation: ^2.0.0 dev_dependencies: json_serializable: ^2.0.0 build_runner: ^1.0.0 pubspecs.yaml https://pub.dev/packages/ Flutter Télécharger et parser du json

Slide 56

Slide 56 text

https://pub.dev/packages/

Slide 57

Slide 57 text

GET https://api.tweeter.com/myTweets Télécharger et parser du json [ { "tweet": "Ces mots qu'on utilise mal sans le savoir", "id": 1001, "author" : { "imageUrl": "http://lorempixel.com/400/400/", "name": "La plume du figaro", "pseudo" "@FigaroPlume" }, "link" : { "imageUrl": "http://lorempixel.com/800/800/", "description": "Ces mots qu'on utilise mal sans le savoir", "website": "lefigaro.fr" }, "comments": 1, "retweets": 0, "likes": 0 } ]

Slide 58

Slide 58 text

class Tweet { int id; String text; Author author; Link link; int comments; int retweets; int likes; } class Author { String imageUrl; String name; String pseudo; } class Link { String imageUrl; String description; String website; } Télécharger et parser du json

Slide 59

Slide 59 text

class Tweet { int id; String text; Author author; Link link; int comments; int retweets; int likes; } Télécharger et parser du json

Slide 60

Slide 60 text

class Tweet { int id; String text; Author author; Link link; int comments; int retweets; int likes; Tweet({this.id, this.text, this.author, this.link, this.comments, this.retweets, this.likes}); } Télécharger et parser du json

Slide 61

Slide 61 text

import 'package:json_annotation/json_annotation.dart'; @JsonSerializable() class Tweet { int id; String text; Author author; Link link; int comments; int retweets; int likes; Tweet({this.id, this.text, this.author, this.link, this.comments, this.retweets, this.likes}); }

Slide 62

Slide 62 text

import 'package:json_annotation/json_annotation.dart'; part 'Tweet.g.dart'; @JsonSerializable() class Tweet { int id; @JsonKey(name: "tweet") String text; Author author; Link link; int comments; int retweets; int likes; Tweet({this.id, this.text, this.author, this.link, this.comments, this.retweets, this.likes}); factory Tweet.fromJson(Map json) => _$TweetFromJson(json); Map toJson() => _$TweetToJson(this); }

Slide 63

Slide 63 text

>_ flutter packages pub run build_runner build Generates all *.g.dart Télécharger et parser du json

Slide 64

Slide 64 text

GET https://api.tweeter.com/myTweets Télécharger et parser du json [ { "tweet": "Ces mots qu'on utilise mal sans le savoir", "id": 1001, "author" : { "imageUrl": "http://lorempixel.com/400/400/", "name": "La plume du figaro", "pseudo" "@FigaroPlume" }, "link" : { "imageUrl": "http://lorempixel.com/800/800/", "description": "Ces mots qu'on utilise mal sans le savoir", "website": "lefigaro.fr" }, "comments": 1, "retweets": 0, "likes": 0 } ]

Slide 65

Slide 65 text

import 'dart:convert'; import 'package:http/http.dart' as http; import 'dart:async'; import 'models/Tweet.dart'; class Webservice { Future> myTweets() async { final response = await http.get( "https://api.tweeter.com/myTweets" ); if (response.statusCode == 200) { var jsonList = json.decode(response.body); return jsonList.map( (model) => Tweet.fromJson(model) ).toList(); } return null; } }

Slide 66

Slide 66 text

class MyApp extends StatelessWidget { List tweets = [...]; @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Twitter', theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( body: ListView(children: tweets.map( (t) => TweetView(tweet: t) ).toList() ); ), ); } }

Slide 67

Slide 67 text

class MyApp extends StatelessWidget { Webservice webservice = Webservice(); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Twitter', theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( body: FutureBuilder( future: webservice.myTweets(), builder: (context, snapshot) { if (snapshot.hasData) { List tweets = snapshot.data; return ListView(children: tweets.map( (t) => TweetView(tweet: t) ).toList() ); } else { return ProgressIndicator(); } }),

Slide 68

Slide 68 text

Appel natif avec Flutter Appeler du code natif android

Slide 69

Slide 69 text

A flutter project contains 3 directories - lib : for dart files - android : an android project - ios : an xcode project (with CocoaPods) Use Platform Channels to communicate between platforms Flutter Platform Channels can be opened from each platform - android -> Flutter - Flutter -> android - Flutter -> android

Slide 70

Slide 70 text

Flutter -Flutter -> android import 'package:flutter/services.dart'; class ShareManager { MethodChannel _channel = MethodChannel('flutter.twitter/share'); void share(String link) async { try { await _channel.invokeMethod('share', link); } catch (e) { print(e); } } } On Flutter

Slide 71

Slide 71 text

Flutter -Flutter -> android TweetButtons( shareClicked: () { ShareManager().share("http://twitter.com/${tweet.id}"); }, retweetClicked: () {}, likeClicked: () {}, commentClicked: () {}, ) On Flutter

Slide 72

Slide 72 text

Flutter -Flutter -> android class MainActivity : FlutterActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) GeneratedPluginRegistrant.registerWith(this) //insert your code here } } /android/…/MainActivity.kt On android

Slide 73

Slide 73 text

Flutter -Flutter -> android //insert your code here val shareChannel = MethodChannel(flutterView, "flutter.twitter/share") shareChannel.setMethodCallHandler { methodCall, result -> when (methodCall.method) { "share" -> { val url = methodCall.arguments as String startActivity( Intent.createChooser(Intent().apply { action = Intent.ACTION_SEND type = "text/plain" putExtra(Intent.EXTRA_TEXT, "Please open $url") }, "Share")) result.success(null) } } } On android

Slide 74

Slide 74 text

Flutter -Flutter -> android

Slide 75

Slide 75 text

Flutter -Flutter -> android

Slide 76

Slide 76 text

Flutter Découverte Design Affichage de listes Interaction avec webservice Appel à du code natif android

Slide 77

Slide 77 text

Flutter - go further RxDart Bloc (Pattern) Silvers in ListView Hummingbird

Slide 78

Slide 78 text

THANKS ! Florent37 florent_champ Slack : Android Dev FR http://tinyurl.com/sunnyFlutter