mercredi 3 février 2021
A developer journey with Flutter
Allez sur www.menti.com et utilisez le code 41 48 08 9
pour du une app iOS/Android
prix
temps
compétences
performances
UI
animations
taille de l'app
x2
Florian Paillard
http://whois.sertook.com/
# ~ curl http://whois.sertook.com/florian-paillard
{
name: "Florian Paillard",
company: "Eyeo",
function: "Mobile developer",
birthday: "676133927",
projects: {
eyeo: {
adblock-browser: "ads free chromium browser",
crumbs: "Privacy tools"
},
lm: {
pandalab : "devicelab software",
pandroid : "open source mobile archetype",
delivery : "open source delivery plugin",
colibri-apps: "store apps",
innovation: "VR, wear os, chatbot, prototyping ..."
},
sertook: {
robolytics : "mobile analytics tag manager",
one-jitsu : "Jiu-jitsu practitioners network",
booth-app : "FatBooth, AgingBooth, ..."
}
},
links : {
twitter: "@paillard_f",
linkedin : "http://whois.sertook.com",
robolytics : "https://robolytics.sertook.com"
}
}
2014 - 2016
Java
6k utilisateurs
3.6/5.0
2012 - 2021
Objectif - C
1M utilisateurs actifs
4.4/5.0
Client
Moi
Named and optional parameters
class Person {
String firstName;
String lastName;
int age;
String occupation;
Person({this.firstName, this.lastName, this.age, this.occupation = 'unknown'});
}
final joe = Person(
firstName: 'Joe',
lastName: 'Doe',
occupation: 'plumber',
age: 32,
);
Cascade notation
final element = Element.div();
final style = element.style;
style.width = '50%';
style.height = '4em';
style.padding = '1em';
append(element);
append(Element.div()
..style.width = '50%'
..style.height = '4em'
..style.padding = '1em');
Null Safety
print(Person().lastName?.toLowerCase()?.indexOf('a')); // null
print(Person().lastName?.toLowerCase()?.indexOf('a') ?? -1); // -1
String s;
void something(String newValue) {
// if s is null, it gets a new value!
s ??= newValue;
}
Collection if/for and spreads
print(['run', if (Platform.isWindows) '.exe']);
final numbers = Iterable.generate(10, (index) => index + 1); // 1 to 10
final evenNumbers = [0, for (var n in numbers) if (n % 2 == 0) n];
print(evenNumbers); // [0, 2, 4, 6, 8, 10]
List<int> withBothEnds(int endElement, List<int> list) =>
[endElement, ...list, endElement];
// prints "Both ends: [42, 1, 2, 3, 42]"
print("Both ends: ${withBothEnds(42, [1, 2, 3])}");
Asynchronous Programming
Future main() async {
var server = await HttpServer.bind(
InternetAddress.loopbackIPv4,
4040,
);
print('Listening on localhost:${server.port}');
await for (HttpRequest request in server) {
request.response.write('Hello, world!');
await request.response.close();
}
}
Extension
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
// ···
}
print('42'.parseInt()); // Use an extension method.
https://flutter.dev/
https://www.youtube.com/channel/UCwXdFgeE9KYzlDdR7TG9cMw
class MainActivity : FlutterFragmentActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
flutterEngine.plugins.add(InfosPlugin())
}
}
class InfosPlugin : MethodCallHandler, FlutterPlugin, ActivityAware {
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
val messenger = binding.binaryMessenger
val channel = MethodChannel(messenger, "com.tayasui.sketches/infos")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"getFlavorName" -> {
result.success(BuildConfig.FLAVOR)
}
}
}
}
var infosChannel = const MethodChannel('com.tayasui.sketches/infos');
var flavor = await infosChannel.invokeMethod("getFlavorName");
//infosChannel.setMethodCallHandler((MethodCall call) {});
Encore plus facile avec
class DrawingViewPlugin : FlutterPlugin {
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
binding.platformViewRegistry.registerViewFactory(
"com.tayasui.sketches/drawingview",
DrawingViewFactory(binding.binaryMessenger)
)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
}
}
class MainActivity : FlutterFragmentActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
flutterEngine.plugins.add(InfosPlugin())
flutterEngine.plugins.add(DrawingViewPlugin())
}
}
class FlutterDrawingView(context: Context, messenger: BinaryMessenger, id: Int) :
PlatformView, MethodChannel.MethodCallHandler {
private val methodChannel: MethodChannel =
MethodChannel(messenger, "com.tayasui.sketches/drawingview_$id")
private val view: DrawingView by lazy { DrawingView(context, this) }
override fun getView(): View {
return view
}
override fun dispose() {
view.dispose()
}
}
class DrawingViewFactory(private val messenger: BinaryMessenger) : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(context: Context, id: Int, o: Any?): PlatformView {
return FlutterDrawingView(context, messenger, id)
}
}
@override
Widget build(BuildContext context) {
return AndroidView(
viewType: 'com.tayasui.sketches/drawingview',
onPlatformViewCreated: this._onPlatformViewCreated,
hitTestBehavior: PlatformViewHitTestBehavior.transparent,
);
}
void _onPlatformViewCreated(int id) {
var viewChannel = new MethodChannel('com.tayasui.sketches/drawingview_$id')
}
class DrawingView(context: Context) : GLSurfaceView(context){}
class GLSurfaceView(context: Context){
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mDetached && (mRenderer != null)) {
int renderMode = RENDERMODE_CONTINUOUSLY;
if (mGLThread != null) {
renderMode = mGLThread.getRenderMode();
}
mGLThread = new GLThread(mThisWeakRef);
if (renderMode != RENDERMODE_CONTINUOUSLY) {
mGLThread.setRenderMode(renderMode);
}
mGLThread.start();
}
mDetached = false;
}
@Override
protected void onDetachedFromWindow() {
if (mGLThread != null) {
mGLThread.requestExitAndWait();
}
mDetached = true;
super.onDetachedFromWindow();
}
}
ScrollConfiguration(
behavior: SketchesBehavior(),
child: child,
)
class SketchesBehavior extends ScrollBehavior {
@override
ScrollPhysics getScrollPhysics(BuildContext context) =>
BouncingScrollPhysics();
}
AlertDialog(
title: Text(this.title),,
actions: <Widget>[
FlatButton(
child: Text(this.negative ?? S.of(context).s_cancel),
onPressed: this.onNegative ?? () => Navigator.of(context).pop(false),
),
FlatButton(
child: Text(this.positive ?? S.of(context).s_ok),
onPressed: this.onPositive ?? () => Navigator.of(context).pop(true),
),
],
);
CupertinoAlertDialog(
title: Text(this.title),
actions: <Widget>[
CupertinoDialogAction(
child: Text(this.negative ?? S.of(context).s_cancel),
onPressed: this.onNegative ?? () => Navigator.of(context).pop(false),
),
CupertinoDialogAction(
isDefaultAction: true,
child: Text(this.positive ?? S.of(context).s_ok),
onPressed: this.onPositive ?? () => Navigator.of(context).pop(true),
),
],
);
CupertinoSwitch(
activeColor: CupertinoColors.systemGreen,
trackColor: CupertinoColors.secondarySystemFill,
)
Plan A : Utilisation du thème et des attributs
const double _kTrackWidth = 51.0;
const double _kTrackHeight = 31.0;
const double _kTrackRadius = _kTrackHeight / 2.0;
const double _kTrackInnerStart = _kTrackHeight / 2.0;
const double _kSwitchWidth = 59.0;
const double _kSwitchHeight = 39.0;
class _RenderCupertinoSwitch extends RenderConstrainedBox {
@override
void paint(PaintingContext context, Offset offset) {
//...
}
}
Plan B : Surcharger le composant
Plan C : Refaire le composant de 0
Plan D : copier le composant
CupertinoSwitch (541) ExpansionPanelList (726) ImplicitlyAnimatedReorderableList (902)
ScaleGestureRecognizer (501)
PanGestureRecognizer
(161)
5 semaines
POC
Publication
novembre 2019
janvier 2020
septembre 2020
1 à 2 ms
StandardMessageCodec
BinaryCodec
JsonMessageCodec
StringCodec
public MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec){}
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
image: ^2.1.14
logger: ^0.9.1
flutter_colorpicker: 0.3.4
provider: ^4.1.3
flutter_svg: ^0.19.1
flutter_staggered_animations: ^0.1.2
animations: ^1.0.0+5
path_provider: ^1.6.9
shared_preferences: ^0.5.12
cupertino_icons: ^1.0.0
json_annotation: ^3.0.1
xml: ^4.2.0
uuid: ^2.0.4
file_picker: ^2.1.5
exif: 1.0.3
esys_flutter_share: ^1.0.2
receive_sharing_intent: ^1.4.2
firebase_core: 0.5.0+1
firebase_crashlytics: ^0.2.1+1
firebase_remote_config: ^0.4.0+1
simple_animations: ^2.2.1
mutex: ^1.0.3
native_device_orientation: ^0.4.3
in_app_purchase: ^0.3.4+16
vibration: ^1.7.1
permission_handler: ^5.0.1+1
ext_storage: ^1.0.3
archive: ^2.0.5
implicitly_animated_reorderable_list: ^0.3.2
in_app_review: ^1.0.2
package_info: ^0.4.3+2
flutter_custom_tabs: ^0.6.0
flutter_svg
in_app_purchase
shared_preferences
firebase_*
file_picker
json_serializable/json_annotation
Widget:
Provider:
6k
3.6 star
150k
4.2 star
VS