Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Пишем под iOS на Flutter

CocoaHeads
September 29, 2018

Пишем под iOS на Flutter

CocoaHeads

September 29, 2018
Tweet

More Decks by CocoaHeads

Other Decks in Programming

Transcript

  1. О ЧЕМ ПОЙДЕТ РЕЧЬ ▸ Краткий обзор способов совместного использования

    кодовой базы ▸ Что такое Flutter ▸ Почему Dart ▸ Структура проекта на Flutter ▸ Архитектура приложений ▸ Тестирование ▸ Полезные виджеты ▸ Выводы 2
  2. bit.ly/2wHkB2B 3 ‣ C++ (Djini) ‣ Swift ‣ Java (j2objc)

    ‣ JS (React Native, Cordova, …) ‣ C# (Xamarin) ‣ Rust, Kotlin Native ‣ Flutter СПОСОБЫ СОВМЕСТНОГО ИСПОЛЬЗОВАНИЯ КОДОВОЙ БАЗЫ
  3. 4 FLUTTER ‣ Современный фреймворк для разработки реактивных мобильных приложений

    ‣ Быстрый движок отрисовки 2D ‣ Богатый инструментарий для разработчика ‣ Большая библиотека готовых виджетов (Material, Cupertino)
  4. http://googlecode.blogspot.com/2011/10/dart-language-for-structured-web.html 5 ‣ Разработан в Google, представлен в октябре 2011

    ‣ Лаконичный, строго типизированный, объектно-ориентированный язык ‣ Используется для создания веб, серверных и мобильных приложений ‣ JIT и AOT компиляция ‣ Dart 2.0, Февраль 2018 DART
  5. Framework (Dart) Material Widgets Rendering Animation Foundation Cupertino Painting Gestures

    Skia Dart Text Engine (C++) Render Surface Native Plugins Packaging Embedder Thread Setup Event Loop Interop 6
  6. 7 ENGINE ‣ Platform Task Runner ‣ UI Task Runner

    ‣ GPU Task Runner ‣ IO Task Runner
  7. 8 $ flutter create -i swift -a kotlin hello_cocoa_heads …

    All done! In order to run your application, type: $ cd hello_cocoa_heads $ flutter run Your main program file is lib/main.dart in the hello_cocoa_heads directory. СОЗДАНИЕ ПРОЕКТА
  8. 9 / !"" ios # !"" Flutter # # !""

    AppFrameworkInfo.plist # # !"" Debug.xcconfig # # !"" Generated.xcconfig # # %"" Release.xcconfig # !"" Runner # # !"" AppDelegate.swift # # !"" Assets.xcassets # # # !"" AppIcon.appiconset # # # # !"" Contents.json # # # # !"" [email protected] # # # # !"" ... # # # %"" LaunchImage.imageset # # # !"" Contents.json # # # !"" LaunchImage.png # # # !"" ... # # # %"" README.md # # !"" Base.lproj # # # !"" LaunchScreen.storyboard # # # %"" Main.storyboard # # !"" GeneratedPluginRegistrant.h # # !"" GeneratedPluginRegistrant.m # # !"" Info.plist # # %"" Runner-Bridging-Header.h # !"" Runner.xcodeproj # # !"" project.pbxproj # # !"" project.xcworkspace # # # %"" contents.xcworkspacedata # # %"" xcshareddata # # %"" xcschemes # # %"" Runner.xcscheme # %"" Runner.xcworkspace # %"" contents.xcworkspacedata !"" lib # %"" main.dart !"" pubspec.lock !"" pubspec.yaml %"" test %"" widget_test.dart
  9. 10 import UIKit import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate

    { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } AppDelegate.swfit
  10. 11 import 'package:flutter/cupertino.dart'; void main() => runApp(MyApp()); class MyApp extends

    StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return CupertinoApp( home: Container( alignment: Alignment.center, child: Text("Hello Cocoa Heads"), ), ); } } lib/main.dart
  11. 12 name: hello_cocoa_heads description: A new Flutter project. version: 1.0.0+1

    environment: sdk: ">=2.0.0-dev.68.0 <3.0.0" dependencies: flutter: sdk: flutter cupertino_icons: ^0.1.2 dev_dependencies: flutter_test: sdk: flutter flutter: uses-material-design: true assets: - images/a_dot_burr.jpeg - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.io/assets-and-images/#resolution-aware. # For details regarding adding assets from package dependencies, see # https://flutter.io/assets-and-images/#from-packages fonts: - family: Schyler fonts: - asset: fonts/Schyler-Regular.ttf - asset: fonts/Schyler-Italic.ttf style: italic - family: Trajan Pro fonts: - asset: fonts/TrajanPro.ttf - asset: fonts/TrajanPro_Bold.ttf weight: 700
  12. 13

  13. 15 UI User Input Update State State State changes Update

    state Rebuild widget and update UI STATEFUL WIDGET LIFECYCLE
  14. 16 import 'package:flutter/cupertino.dart'; class MyWidget extends StatefulWidget { @override _MyWidgetState

    createState() => _MyWidgetState(); } class _MyWidgetState extends State<MyWidget> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Column( children: <Widget>[ Text('$_counter'), CupertinoButton( onPressed: _incrementCounter, child: Text('Push me'), ), ], ), }; }
  15. iOS host App Delegate iOS platform APIs 3rd party APIs

    FlutterViewController State Flutter App FlutterMethodChannel MethodChannel METHOD CHANNEL 18
  16. 19 import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class _MyHomePageState extends

    State<MyHomePage> { static const platform = const MethodChannel('samples.flutter.io/battery'); String _batteryLevel = 'Unknown battery level.'; Future<Null> _getBatteryLevel() async { String batteryLevel; try { final int result = await platform.invokeMethod('getBatteryLevel'); batteryLevel = 'Battery level at $result % .'; } on PlatformException catch (e) { batteryLevel = "Failed to get battery level: '${e.message}'."; } setState(() { _batteryLevel = batteryLevel; }); } method_channel.dart
  17. 20 @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application(

    _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { let controller : FlutterViewController = window?.rootViewController as! FlutterViewController; let batteryChannel = FlutterMethodChannel.init(name: "samples.flutter.io/battery", binaryMessenger: controller); batteryChannel.setMethodCallHandler({ (call: FlutterMethodCall, result: FlutterResult) -> Void in if ("getBatteryLevel" == call.method) { self.receiveBatteryLevel(result: result); } else { result(FlutterMethodNotImplemented); } }); return super.application(application, didFinishLaunchingWithOptions: launchOptions); } } private func receiveBatteryLevel(result: FlutterResult) { let device = UIDevice.current; device.isBatteryMonitoringEnabled = true; if (device.batteryState == UIDeviceBatteryState.unknown) { result(FlutterError.init(code: "UNAVAILABLE", message: "Battery info unavailable", details: nil)); } else { result(Int(device.batteryLevel * 100)); } }
  18. 21 // Consuming events on the Dart side. const channel

    = EventChannel('foo'); channel.receiveBroadcastStream().listen((dynamic event) { print('Received event: $event'); }, onError: (dynamic error) { print('Received error: ${error.message}'); }); event_channel.dart
  19. 22 @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate, FlutterStreamHandler { private var

    eventSink: FlutterEventSink? override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) // ... let eventChannel = FlutterEventChannel(name: "foo", binaryMessenger: controller) eventChannel.setStreamHandler(self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } public func onListen(withArguments arguments: Any?, eventSink: @escaping FlutterEventSink) -> FlutterError? { this.eventSink = eventSink eventSink("foo") return nil } public func onCancel(withArguments arguments: Any?) -> FlutterError? { eventSink = nil return nil } }
  20. 23 ‣ MV* ‣ RxDart ‣ BLoC (Business Logic Component)

    АРХИТЕКТУРА ПРИЛОЖЕНИЙ
  21. https://flutter.io/testing/ 25 ‣ Юнит-тестирование ‣ Виджет тестирование (как вне устройства,

    так и на устройстве) ‣ Интеграционное тестирование ТЕСТИРОВАНИЕ
  22. 26 import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:hello_cocoa_heads/main.dart'; void main() {

    testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget(new CupertinoApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsNothing); // Tap the '+' icon and trigger a frame. await tester.tap(find.byIcon(Icons.add)); await tester.pump(); // Verify that our counter has incremented. expect(find.text('0'), findsNothing); expect(find.text('1'), findsOneWidget); }); }
  23. 27 // No Safe Area Widget _buildTextComposer() => Container( child:

    Row( children: <Widget>[ Flexible( child: TextField("Send a message"), ), CupertinoButton( child: Text("Send"), ) ], ), ); SAFE AREA
  24. 28 SAFE AREA // With Safe Area Widget _buildTextComposer() =>

    SafeArea( child: Container( child: Row( children: <Widget>[ Flexible( child: TextField("Send a message"), ), CupertinoButton( child: Text("Send"), ) ], ), ), );
  25. 33 ‣ Активно развивается, один из популярных репо на GitHub

    ‣ Позволяет иметь общий код для мобильных и веб приложений ‣ Hot Reload ‣ Гибридные приложения ‣ Пока не умеет карты ‣ Платформенный код писать надо ВЫВОДЫ
  26. 34 ССЫЛКИ ‣ flutter.io ‣ https://github.com/Solido/awesome-flutter ‣ Why Flutter uses

    Dart https://hackernoon.com/why-flutter-uses-dart- dd635a054ebf ‣ Flutter Rendering https://www.youtube.com/watch?v=UUfXWzp0-DU