狠狠撸
Submit Search
贵濒耻迟迟别谤移行の苦労と、乗り越えた先に得られたもの
?
2 likes
?
4,760 views
K
KeisukeKiriyama
Follow
じゃらんにおいて贵濒耻迟迟别谤移行を行った际の知见の共有
Read less
Read more
1 of 115
Download now
Download to read offline
More Related Content
贵濒耻迟迟别谤移行の苦労と、乗り越えた先に得られたもの
1.
Flutter移行の苦労と、? 乗り越えた先に得られたもの? Recruit Co., Ltd.
Keisuke Kiriyama? 1?
2.
? Recruit Co.,
Ltd.? ? iOS / Flutter ? ? じゃらんアプリ開発T? Keisuke Kiriyama?
3.
3? 旅を、もっと豊かに? 宿?ホテル予約アプリ?
4.
現在じゃらんは? Flutterへの移行に挑戦しています!? 4?
5.
Flutterとは? ● Google製のクロスプラットフォームSDK? ? ● 単一のソースコードで、複数のプラットフォームの? アプリケーションを構築可能? ? ●
開発言語: Dart? 5?
6.
Flutterのコミュニティ? ● 2018年12月のver1.0リリース以降、? Flutterを使用する開発者は増え続け? 現在は200万人を超えた? ? ● 国内においてもFlutterの記事や話題を目にする機会が増 え、日に日に盛り上がりを感じている? 6? Flutter
Spring 2020 Update.:https://medium.com/flutter/flutter-spring-2020-update-f723d898d7af, (参照2020-08-02)
7.
国内におけるFlutterのプロダクション採用? ● しかし、国内においてFlutterを? プロダクションに採用している例はそれほど多くない? ● 弊社においてもFlutterを採用したのはじゃらんが初? ? 7?
8.
Flutterどうなの?? 8? 実際メリット 得られるの? 課題はないの?
9.
Flutterを採用して実際どうだったのかをお伝えします? 話すこと? 9?
10.
Flutterを採用して実際どうだったのかをお伝えします? 1. どんな技術的課題に直面したのか? ? 話すこと? 10?
11.
Flutterを採用して実際どうだったのかをお伝えします? 1. どんな技術的課題に直面したのか? 2. 課題を乗り越えた結果、どんなメリットを得られたのか? ? 話すこと? 11?
12.
発表のゴール? ● Flutter開発経験者? →直面した課題と得られたメリットを知り? 技術選定の際の判断材料になる? ? ● Flutter開発未経験者? →まずはFlutter触ってみたいと思ってもらう? 12?
13.
1. 前提の共有? ○ じゃらんのFlutter移行? ○
Flutterのレイアウト構築? ? 2. 直面した課題? ? 3. 得られたメリット? ? 4. まとめ? 説明の流れ? 13?
14.
じゃらんのFlutter移行? 14?
15.
Flutter採用の背景? ● じゃらんアプリはiOS/Android共にリリースから? 10年を迎え、長年に渡る開発が行われてきた? ? ? ? ● 上記課題を解決するために、リプレースを検討? 15? プロジェクトの大規模化によ るビルド時間の増加 プロジェクト全体の コードが古くなっている
16.
じゃらんアプリのリプレース検討? ● クロスプラットフォーム技術の検討? ○ iOS/Android開発工数? ○
リプレースコスト? ? ● クロスプラットフォーム技術の中でも? Flutterの開発生産性が最も高いと実感し? Flutterの採用を決断? 16? 半減?
17.
17?
18.
18?
19.
19? ここから先の画面は? 全てFlutterで実装?
20.
Flutterへの段階的移行? ● Add-to-app(Add Flutter
to existing app)を使用? ● 既存のネイティブプロジェクトにFluterプロジェクトを部分的 に組み込む仕組み? ? 20? じゃらん? アプリ? Swift? Objective-c? じゃらん遊び?体験?? Flutterプロジェクト?
21.
Flutterへの段階的移行? ● Add-to-app(Add Flutter
to existing app)を使用? ● 既存のネイティブプロジェクトにFluterプロジェクトを部分的 に組み込む仕組み? ? 21? じゃらん? アプリ? Swift? Objective-c? じゃらん遊び?体験?? Flutterプロジェクト? Flutterモジュール?
22.
Flutterのレイアウト構築? 22?
23.
Flutterのレイアウト構築? ● Widget? ○ UIの構成情報を保持するクラス? ? ●
Widgetをツリー上に構成することによって? UIの構築を行う? ? 23?
24.
24? class HomePage extends
StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Flutter Demo Home Page'), ), body: Center( child: Text( 'Hello iOSDC!!', style: TextStyle( fontSize: 30, ), ...
25.
25? class HomePage extends
StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Flutter Demo Home Page'), ), body: Center( child: Text( 'Hello iOSDC!!', style: TextStyle( fontSize: 30, ), ...
26.
26? class HomePage extends
StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Flutter Demo Home Page'), ), body: Center( child: Text( 'Hello iOSDC!!', style: TextStyle( fontSize: 30, ), ...
27.
27? class HomePage extends
StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Flutter Demo Home Page'), ), body: Center( child: Text( 'Hello iOSDC!!', style: TextStyle( fontSize: 30, ), ... ● 』UI部品だけではなく、 「画面の中心に表示」 の様なUIの構成情報も Widgetで表現する
28.
直面した課題? 28?
29.
直面した課題? 29? 1. タブ切り替えのパフォーマンス? 2. Flutterの画面が初期化されない? 3.
ネットワーク通信がproxyサーバーを経由しない? 4. Google Mapのクラッシュ?
30.
直面した課題? 30? 1. タブ切り替えのパフォーマンス? 2. Flutterの画面が初期化されない? 3.
ネットワーク通信がproxyサーバーを経由しない? 4. Google Mapのクラッシュ?
31.
● タブを使用して表示する情報を? 切り替えるページが存在する? ? ● このタブのページでは、? 口コミの一覧や、プランの一覧を? リストで表示する? タブの切り替えをする画面? 31?
32.
発生した問題? ● リストのアイテムを大量に読み込んでタブを切り替える? ? ? ● タブ切り替えのアニメーションが重くなってしまう? ●
まれにタブ切り替えのタイミングで? アプリがクラッシュすることがある? 32?
33.
class TabPage extends
StatelessWidget { @override Widget build(BuildContext context) { return DefaultTabController( ... child: Scaffold( appBar: AppBar( title: Text('Tab Sample'), bottom: TabBar(tabs: <Widget>[ const Tab(child: Text('Tab A')), const Tab(child: Text('Tab B')) ])), body: TabBarView( children: <Widget>[ TabA(), TabB(), ], ● タブのページを作成する サンプルコード: タブのページ? 33?
34.
class TabPage extends
StatelessWidget { @override Widget build(BuildContext context) { return DefaultTabController( ... child: Scaffold( appBar: AppBar( title: Text('Tab Sample'), bottom: TabBar(tabs: <Widget>[ const Tab(child: Text('Tab A')), const Tab(child: Text('Tab B')) ])), body: TabBarView( children: <Widget>[ TabA(), TabB(), ], ● 2つのタブ ● TabA() ● TabB() サンプルコード: タブのページ? 34?
35.
class TabA extends
StatelessWidget { @override Widget build(BuildContext context) { return Center( child: Text('Tab A'), ); } } ● TabA()は画面の中心に “Tab A”を表示するだけ サンプルコード: TabA? 35?
36.
class TabB extends
StatelessWidget { @override Widget build(BuildContext context) { return ListView.builder( key: PageStorageKey('TabB'), itemCount: 1000, itemBuilder: (BuildContext context, int index) { return ListTile( title: Text('Item $index'), ); ... ● TabB()はリストを保持 ● 1000個のアイテムを表示 サンプルコード: TabB? 36?
37.
37? タブ切り替え? TabA? TabB?
38.
38? TabB? ? Tab Bのリストを下までスクロールする
39.
39? TabB? TabA? TabAに? 切り替え? TabBに? 切り替え? TabB?
40.
40? TabB? TabA? TabAに? 切り替え? TabBに? 切り替え? TabB? ● タブ切り替えのアニメーションが非 常に重くなる ●
まれにクラッシュする?
41.
なぜアニメーション重くなる?? ● タブを切り替えた際、表示? されないタブはWidget? ツリーから除外される? 41? TabA表示時? 罢补产叠表示时?
42.
● 再度タブを表示する際には、表示するタブの? レイアウトを再計算する必要がある? ? なぜアニメーション重くなる?? ● タブを切り替えた際、表示? されないタブはWidget? ツリーから除外される? 42? TabA表示時?
罢补产叠表示时?
43.
タブが保持するリストのアイテムの高さが可変の場合? ● 1つ目のアイテムから順にレイアウト? を計算して、高さを決定しないと? ? なぜアニメーション重くなる?? 43? …?
44.
タブが保持するリストのアイテムの高さが可変の場合? ● 1つ目のアイテムから順にレイアウト? を計算して、高さを決定しないと? ? ● 前回のスクロール位置の? アイテムを表示できない? ? ? なぜアニメーション重くなる?? 44? …? 前回の? スクロール位置?
45.
タブが保持するリストのアイテムの高さが可変の場合? ● 1つ目のアイテムから順にレイアウト? を計算して、高さを決定しないと? ? ● 前回のスクロール位置の? アイテムを表示できない? ? ●
この演算のために? パフォーマンスが低下? なぜアニメーション重くなる?? 45? …? 前回の? スクロール位置?
46.
なぜまれにクラッシュする?? ● リストのレイアウトを決定する演算を行うことで、? 一時的にメモリを圧迫する? ● この一時的な圧迫で許容値を超えてしまった場合に? クラッシュが発生していた? 46? タブ切り替え時?
47.
どう回避したか? ● タブのWidgetにAutomaticKeepAliveClientMixin? を適用する? ● 非表示になったタブもWidgetツリーから除外されなくなるた め、再度レイアウトの演算が不要? 47?TabA表示時にTabBが除外されない
?
48.
class TabB extends
StatefulWidget { ... class _TabBState extends State<TabB> with AutomaticKeepAliveClientMixin { @override Widget build(BuildContext context) { super.build(context); return ListView.builder( key: PageStorageKey('TabB'), itemCount: 1000, itemBuilder: (BuildContext context, int index) { return ListTile( title: Text('Item $index'), ... @override bool get wantKeepAlive => true; } ● TabのWidgetを StatefulWidgetに変更する タブにAutomaticKeepAliveClientMixinを適用? 48?
49.
class TabB extends
StatefulWidget { ... class _TabBState extends State<TabB> with AutomaticKeepAliveClientMixin { @override Widget build(BuildContext context) { super.build(context); return ListView.builder( key: PageStorageKey('TabB'), itemCount: 1000, itemBuilder: (BuildContext context, int index) { return ListTile( title: Text('Item $index'), ... @override bool get wantKeepAlive => true; } ● Stateに AutomaticKeepAlive ClientMixin を適用する タブにAutomaticKeepAliveClientMixinを適用? 49?
50.
class TabB extends
StatefulWidget { ... class _TabBState extends State<TabB> with AutomaticKeepAliveClientMixin { @override Widget build(BuildContext context) { super.build(context); return ListView.builder( key: PageStorageKey('TabB'), itemCount: 1000, itemBuilder: (BuildContext context, int index) { return ListTile( title: Text('Item $index'), ... @override bool get wantKeepAlive => true; } タブにAutomaticKeepAliveClientMixinを適用? 50? ● super.buildの呼び出し ● wantKeepAliveのgetterで trueを返す
51.
class TabB extends
StatefulWidget { ... class _TabBState extends State<TabB> with AutomaticKeepAliveClientMixin { @override Widget build(BuildContext context) { super.build(context); return ListView.builder( key: PageStorageKey('TabB'), itemCount: 1000, itemBuilder: (BuildContext context, int index) { return ListTile( title: Text('Item $index'), ... @override bool get wantKeepAlive => true; } タブにAutomaticKeepAliveClientMixinを適用? 51? ● 一時的なメモリ圧迫も 起こらなくなる
52.
直面した課題? 52? 1. タブ切り替えのパフォーマンス? 2. Flutterの画面が初期化されない? 3.
ネットワーク通信がproxyサーバーを経由しない? 4. Google Mapのクラッシュ?
53.
53? Nativeの画面? Flutterの画面?
54.
発生した問題? ● Flutterの画面を閉じて再度開く? ? ? ● 前回開いた画面の状態が残ってしまっている? 54?
55.
55? Nativeの画面? Flutterの画面?
56.
56? Nativeの画面? ? Flutterの画面に遷移する
57.
57? Flutterの画面? ? +のFABをタップ ? 画面の中心にタップした回数が表示
58.
58? Nativeの画面?Flutterの画面? dismiss?
59.
59? Nativeの画面?Flutterの画面? present?dismiss? Flutterの画面?
60.
60? Nativeの画面?Flutterの画面? present?dismiss? Flutterの画面? ● 前回のFlutterの画面の 状態が残ってしまっている ● 画面を破棄して再生成したら、初 期状態になるのでは??
61.
61? じゃらんTOP? ? present?dismiss? 遊び?体験(Flutter)?遊び?体験(Flutter)? 検索条件指定? じゃらん遊び?体験予約? を再度開く? 前回の検索条件のまま?
62.
62? じゃらんTOP? ? present?dismiss? 遊び?体験(Flutter)?遊び?体験(Flutter)? 検索条件指定? 遊び?体験を再度開く? 前回の検索条件のまま? ●
Add-to-appでFlutterの画面を表示する方法の説明 ↓ ● この問題の原因の説明
63.
おさらい: Add-to-app? ● 既存のネイティブプロジェクトにFluterプロジェクトを部分的 に組み込む仕組み? ? 63? じゃらん? アプリ? Swift? Objective-c? じゃらん遊び?体験?? Flutterプロジェクト? Flutterモジュール?
64.
Flutterの画面を表示するために重要なクラス? 64? FlutterEngine FlutterViewController FlutterView
65.
Flutter? View? Controller? ? ? View? Controller? ? ? Flutterの画面を表示するために重要なクラス? 65? FlutterEngine FlutterViewController FlutterView ● ViewControllerの派生クラス? ● FlutterViewControllerに遷移する? ことでFlutterの画面を表示? 画面遷移?
66.
Flutterの画面を表示するために重要なクラス? 66? FlutterEngine FlutterViewController FlutterView ● FlutterViewControllerに? 乗っているView? ● FlutterモジュールのUIが描画される? FlutterViewController? FlutterView?
67.
Flutterの画面を表示するために重要なクラス? 67? FlutterEngine FlutterViewController FlutterView ● Dartを実行して、FlutterViewに? FlutterモジュールのUIを描画する? FlutterViewController? FlutterView? FlutterEngine?
68.
Flutterの画面を表示するために重要なクラス? 68? FlutterEngine FlutterViewController FlutterView ● Dartを実行して、FlutterViewに? FlutterモジュールのUIを描画する? FlutterViewController? FlutterView? FlutterEngine? ? Flutterの画面を描画するためには、 FlutterEngineの初期化が必要
69.
class AppDelegate: FlutterAppDelegate
{ lazy var flutterEngine = FlutterEngine(name: "my flutter engine") override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { flutterEngine.run(); GeneratedPluginRegistrant.register(with: self.flutterEngine); return super.application(application, didFinishLaunchingWithOptions: launchOptions); } } FlutterEngineの初期化? 69? ? AppDelegateにおいてFlutterEngineインスタンスの生成
70.
class AppDelegate: FlutterAppDelegate
{ lazy var flutterEngine = FlutterEngine(name: "my flutter engine") override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { flutterEngine.run(); GeneratedPluginRegistrant.register(with: self.flutterEngine); return super.application(application, didFinishLaunchingWithOptions: launchOptions); } } FlutterEngineの初期化? 70? ? Dartのmainを実行し、FlutterEngineの初期化を行う ? FlutterEngineの初期化は時間がかかるため、予め呼ぶ必要がある
71.
class ViewController: UIViewController
{ override func viewDidLoad() { super.viewDidLoad() } @IBAction func showFlutter(_ sender: Any) { let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil) present(flutterViewController, animated: true, completion: nil) } } FlutterViewControllerへ遷移? 71? ? FlutterEngineを指定して、FlutterViewControllerをインスタンス化 ? FlutterViewControllerに画面遷移することでFlutterの画面を表示
72.
何故画面を再生成しても初期状態にならない?? 72? FlutterEngineFlutterViewController ● Flutterの画面を閉じた段階で FlutterViewControlerは破棄される?
73.
● FlutterEngineはAppDelegateで初期化し、? 参照を保持しておくので、破棄されない? 何故画面を再生成しても初期状態にならない?? 73? FlutterEngine class AppDelegate:
FlutterAppDelegate { lazy var flutterEngine = FlutterEngine(name: "my flutter engine") FlutterViewController
74.
● FlutterEngineはAppDelegateで初期化し、? 参照を保持しておくので、破棄されない? 何故画面を再生成しても初期状態にならない?? 74? FlutterEngine ● Dartを実行しているのはFlutterEngine? ●
Flutterの画面を閉じても、Dart内で破棄? していないStateは残ってしまう? FlutterViewController
75.
● FlutterEngineはAppDelegateで初期化し、? 参照を保持しておくので、破棄されない? 何故画面を再生成しても初期状態にならない?? 75? FlutterEngine ● Dartを実行しているのはFlutterEngine? ●
Flutterの画面を閉じても、Dart内で破棄? していないStateは残ってしまう? ● 時間がかかるためFlutterEngineを毎回初期化 するわけにもいかない? FlutterViewController
76.
● Flutterモジュールの最初に空の画面を挿入(InitialPage)? ? ● FlutterViewController遷移時に? ○
InitialPage以外のページを全て破棄? ○ 本来最初に表示したいページを生成して、? 即座に遷移する? ? どう回避したか? 76?
77.
FlutterViewControllerに遷移するコード? class ViewController: UIViewController
{ ... @IBAction func showFlutter(_ sender: Any) { let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil) let channel = FlutterMethodChannel(name: "channel", binaryMessenger: flutterViewController.binaryMessenger) channel.invokeMethod("setup", arguments: nil); flutterViewController.modalPresentationStyle = .fullScreen present(flutterViewController, animated: true, completion: nil) } } 77? ? Method Channelを使用して、setupのDartコードを呼び出す
78.
FlutterViewControllerに遷移するコード? class ViewController: UIViewController
{ ... @IBAction func showFlutter(_ sender: Any) { let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil) let channel = FlutterMethodChannel(name: "channel", binaryMessenger: flutterViewController.binaryMessenger) channel.invokeMethod("setup", arguments: nil); flutterViewController.modalPresentationStyle = .fullScreen present(flutterViewController, animated: true, completion: nil) } } 78? ? その後FlutterViewControllerへ画面遷移する
79.
Initial Pageのコード? class InitialPage
extends StatelessWidget { static const MethodChannel channel = MethodChannel('channel'); @override Widget build(BuildContext context) { channel.setMethodCallHandler((MethodCall call) async { switch (call.method) { case 'setup': return Navigator.pushNamedAndRemoveUntil<void>( context, TopPage.routeName, (Route<dynamic> route) => route.isFirst, ); } 79? ? 空のInitial pageを作成 ? Flutterモジュールの先頭の 画面に設定
80.
Initial Pageのコード? class InitialPage
extends StatelessWidget { static const MethodChannel channel = MethodChannel('channel'); @override Widget build(BuildContext context) { channel.setMethodCallHandler((MethodCall call) async { switch (call.method) { case 'setup': return Navigator.pushNamedAndRemoveUntil<void>( context, TopPage.routeName, (Route<dynamic> route) => route.isFirst, ); } 80? ? setupのMethod Channelが 呼び出された際に 実行されるコード
81.
? pushNamedAndRemoveUntil 条件が満たされるまで、 画面を破棄する。 その後新たな画面をpush Initial Pageのコード? class
InitialPage extends StatelessWidget { static const MethodChannel channel = MethodChannel('channel'); @override Widget build(BuildContext context) { channel.setMethodCallHandler((MethodCall call) async { switch (call.method) { case 'setup': return Navigator.pushNamedAndRemoveUntil<void>( context, TopPage.routeName, (Route<dynamic> route) => route.isFirst, ); } 81?
82.
● 条件 一番最初の画面であること。 すなわちInitial Pageに到達す るまで画面が破棄される Initial
Pageのコード? class InitialPage extends StatelessWidget { static const MethodChannel channel = MethodChannel('channel'); @override Widget build(BuildContext context) { channel.setMethodCallHandler((MethodCall call) async { switch (call.method) { case 'setup': return Navigator.pushNamedAndRemoveUntil<void>( context, TopPage.routeName, (Route<dynamic> route) => route.isFirst, ); } 82?
83.
● 条件を満たしたタイミングで本 来表示したかった 最初の画面がpushされる Initial Pageのコード? class
InitialPage extends StatelessWidget { static const MethodChannel channel = MethodChannel('channel'); @override Widget build(BuildContext context) { channel.setMethodCallHandler((MethodCall call) async { switch (call.method) { case 'setup': return Navigator.pushNamedAndRemoveUntil<void>( context, TopPage.routeName, (Route<dynamic> route) => route.isFirst, ); } 83?
84.
84? Nativeの画面?Flutterの画面? present?dismiss? Flutterの画面? ● 再度表示した際に、初期化される
85.
直面した課題? 85? 1. タブ切り替えのパフォーマンス? 2. Flutterの画面が初期化されない? 3.
ネットワーク通信がproxyサーバーを経由しない? 4. Google Mapのクラッシュ?
86.
● 詳細なデバッグや試験を行う際に? パケットモニタリングを使用したい? ● iOSプロジェクトにおいては? Wi-Fi設定からproxyサーバーの? IPアドレスとポート番号を入力する? ことでパケットモニタリング可能? (例:
Charles)? iOSプロジェクトでパケットモニタリング? 86?
87.
● 同様のWi-Fi設定をFlutterプロジェクトに行っても、通信が Proxyサーバーを経由せず? パケットモニタリングを使用できない? ? 発生した問題? 87?
88.
proxyサーバーを経由するためには? 88? ● HttpClientクラスに? プロキシ自動設定(PAC)を? 明示的に指定する必要がある?
89.
final httpClient =
HttpClient(); httpClient.findProxy = (url) { return 'PROXY localhost:8888; DIRECT'; }; final request = await httpClient .getUrl(Uri.https('jsonplaceholder.typicode.com', '/posts')); final response = await request.close(); ● HttpClientのfindProxyに PACを指定する PACを指定する方法(HttpClient)? 89?
90.
Future<void> main() async
{ HttpOverrides.global = _HttpOverrides(); runApp(Application()); } class _HttpOverrides extends HttpOverrides { @override HttpClient createHttpClient(SecurityContext context) { return super.createHttpClient(context) ..findProxy = (uri) { return 'PROXY localhost:8888; DIRECT'; }; } ● HttpOverridesを継承した クラスを定義 PACを指定する方法(httpパッケージ)? 90?
91.
Future<void> main() async
{ HttpOverrides.global = _HttpOverrides(); runApp(Application()); } class _HttpOverrides extends HttpOverrides { @override HttpClient createHttpClient(SecurityContext context) { return super.createHttpClient(context) ..findProxy = (uri) { return 'PROXY localhost:8888; DIRECT'; }; } ● HttpOverridesの createHttpClientメソッドを overrideする ● 作成されるHttpClientクラス にfindProxyを指定する PACを指定する方法(httpパッケージ)? 91?
92.
Future<void> main() async
{ HttpOverrides.global = _HttpOverrides(); runApp(Application()); } class _HttpOverrides extends HttpOverrides { @override HttpClient createHttpClient(SecurityContext context) { return super.createHttpClient(context) ..findProxy = (uri) { return 'PROXY localhost:8888; DIRECT'; }; } ● HttpOverridesの派生クラス のインスタンスを HttpOverrides.globalに 指定する PACを指定する方法(httpパッケージ)? 92?
93.
Future<void> main() async
{ HttpOverrides.global = _HttpOverrides(); runApp(Application()); } class _HttpOverrides extends HttpOverrides { @override HttpClient createHttpClient(SecurityContext context) { return super.createHttpClient(context) ..findProxy = (uri) { return 'PROXY localhost:8888; DIRECT'; }; } ● PACをベタ書きしている PACを指定する方法(httpパッケージ)? 93?
94.
システムProxyからPACを指定する? ● じゃらんではsystem_proxyパッケージを使用? 94? pub system_proxy:https://pub.dev/packages/system_proxy,
(参照2020-08-10)
95.
void main() async
{ WidgetsFlutterBinding.ensureInitialized(); Map<String, String> proxy = await SystemProxy.getProxySettings(); ... HttpOverrides.global = _HttpOverrides(proxy['host'], proxy['port']); runApp(Application()); } class _HttpOverrides extends HttpOverrides { _HttpOverrides(this._host, this._port); final String _host; final String _port; @override HttpClient createHttpClient(SecurityContext context) { return super.createHttpClient(context) ..findProxy = (uri) { return _host != null ? "PROXY $_host:$_port;" : 'DIRECT'; }; ● システムのProxy設定を 取得 ● その情報を使用してPACを findProxyに指定 システムProxyからPACを指定する? 95?
96.
直面した課題? 96? 1. タブ切り替えのパフォーマンス? 2. Flutterの画面が初期化されない? 3.
ネットワーク通信がproxyサーバーを経由しない? 4. Google Mapのクラッシュ?
97.
Google Mapの使用? ● レジャー施設の場所や集合場所を示 すために、地図(Google
Map)を表示す る? ● google_maps_flutterパッケージを使用? 97? pub google_maps_flutter:https://pub.dev/packages/google_maps_flutter, (参照2020-08-10)
98.
● google_maps_flutterはDevelopers Preview? ●
実際に使用すると、? Google Mapを何度も表示した際に? アプリがクラッシュする問題が発覚? ? ● Google Mapを閉じてもメモリが解放? されない? ● 地図を開くたびにメモリを圧迫してしまい、? 最終的にクラッシュしてしまっていた? 発生した問題? 98? Memory Usage?
99.
原因と問題の回避? ● GoogleMapが内部で使用しているPlatformViewに? おいて循環参照があり、それによってGoogleMapが? 解放されなくなっていた? ? ● 当時使用していたFlutter
ver1.12.13+hotfix.7から? Flutter ver1.15.17にアップデートしたことで、? メモリが解放される様になり、回避することができた? ? ? 99?
100.
ライブラリのステータス? ● developers preview等のライブラリや機能に関する? 既知の問題には、issueにタグが付与されている? ●
その様なライブラリや機能を使用する際には、? タグでフィルタリングして、関連issueを確認することで? 事前に問題を把握すると吉? 100? pub google_maps_flutter:https://pub.dev/packages/google_maps_flutter, (参照2020-08-10)
101.
直面した課題まとめ? ● Flutterを採用してみると、いくつかの課題に直面した? ● GoogleMapがdevelopers
previewである等、? プラットフォームの未成熟な部分は若干ある?? ● しかし、いずれの直面した課題も回避することは? できていて、プロダクション採用不可能となる様な? 事態には直面しなかった? 101?
102.
これらの課題を乗り越えた結果? どんなメリットを得られたのか? 102?
103.
工数の 削減 開発効率 の向上 最も大きく得られたメリット? 103?
104.
● 開発効率は著しく向上した? ? 1. 既成部品の充実? ? 2.
hot reload/restart? ? 3. IDE(Android Studio)の機能の充実? ? 得られたメリット:開発効率の向上? 104? 開発効率 の向上
105.
1. 既成部品の充実? 105? ● Widgetの種類がとても充実? している? ? ●
じゃらんにおいては、これら既成 部品でほぼ事足りた? ● 既成部品を積極的に使用できた ことが、開発効率向上に? 寄与した? ? Widget catalog:https://flutter.dev/docs/development/ui/widgets, (参照2020-08-10)
106.
● コードを修正した際に、ビルドし直さなくても? その修正が即座にアプリに反映される仕組み? ○ hot
reload: 約0.5s? ○ hot restart: 約3s ? ? ● じゃらんはビルド時間が大分増加してしまって? いたので、この仕組みの開発効率向上への? 寄与は大きかった? 2. hot reload/ restart? 106?
107.
● Widgetの上でoption+Enterを押すことで、包む Widget等の候補を表示。? Widgetツリーの構築をサクサクできる? ? ● “stless”や”stful”と打つことで? Stateless
WidgetやStateful Widget? を自動生成? 3. IDE(Android Studio)の機能の充実? 107? ● XCodeで開発する場合に比べて開発スピードが向上した?
108.
? ? 1. iOS/Androidの開発工数削減? ? 2. 開発以外の工数削減? ? 3.
移行工数の削減? ? 得られたメリット:工数の削減? 108? 工数の 削減
109.
● じゃらんはメディアであり、? プラットフォーム固有の機能が少ない? ● 完全移行が完了すれば、iOS/Androidの開発工数を? ほぼ半分にすることができそう? 1.
iOS/Androidの開発工数削減? 109?
110.
● iOSとAndroidの仕様差分をなるべく減らす? ● デザインをマテリアルデザインに統一? ? ●
開発以外の工数も削減することができている? ? 2. 開発以外の工数削減? 110? 開発工数? 要件検討工数? デザイン作成工数? 5割減? 5割減? 3割減?
111.
● 段階的移行を行っていることにより、各プラットフォームの 実装が多少必要になっている? ○ 例えば、ネイティブ側が保持するアプリの設定情報を? Flutterモジュールに伝播する処理? ? ●
しかし、大部分は共通化できていて、その点? 移行コストも大きく削減することができている? 3. 移行工数の削減? 111?
112.
開発効率向上、工数削減以外にも多くのメリット? ● 宣言的UI構築が素晴らしい? ○ 参考:
宣言的UI そな太さん https://speakerdeck.com/sonatard/xuan-yan-de-ui? ? ● FlutterがOSSであることで、内部処理を確認できる? ? ● パフォーマンスモニタリングが充実している? ? ● 全てDartで記述するため、コードレビューや? コンフリクトの解消がしやすい? などなど...? 112?
113.
まとめ? 113?
114.
● じゃらんでは現在Flutterへの段階的移行を行っている? ● 複数課題に直面したものの、回避することはできた? ●
直面した課題を乗り越えたことで、開発効率向上や開発? 工数削減など多くのメリットを得ることができた? ● 完全移行に向けて引き続きFlutter頑張ります?? まとめ? 114?
115.
ありがとうございました!? 115?
Download