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

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

Avatar for CocoaHeads CocoaHeads
September 29, 2018

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

Avatar for CocoaHeads

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