狠狠撸

狠狠撸Share a Scribd company logo
Flutter移行の苦労と、?
乗り越えた先に得られたもの?
Recruit Co., Ltd. Keisuke Kiriyama?
1?
? Recruit Co., Ltd.?
? iOS / Flutter ?
? じゃらんアプリ開発T?
Keisuke Kiriyama?
3?
旅を、もっと豊かに?
宿?ホテル予約アプリ?
現在じゃらんは?
Flutterへの移行に挑戦しています!?
4?
Flutterとは?
● Google製のクロスプラットフォームSDK?
?
● 単一のソースコードで、複数のプラットフォームの?
アプリケーションを構築可能?
?
● 開発言語: Dart?
5?
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)
国内におけるFlutterのプロダクション採用?
● しかし、国内においてFlutterを?
プロダクションに採用している例はそれほど多くない?
● 弊社においてもFlutterを採用したのはじゃらんが初?
?
7?
Flutterどうなの??
8?
実際メリット
得られるの?
課題はないの?
Flutterを採用して実際どうだったのかをお伝えします?
話すこと?
9?
Flutterを採用して実際どうだったのかをお伝えします?
1. どんな技術的課題に直面したのか?
?
話すこと?
10?
Flutterを採用して実際どうだったのかをお伝えします?
1. どんな技術的課題に直面したのか?
2. 課題を乗り越えた結果、どんなメリットを得られたのか?
?
話すこと?
11?
発表のゴール?
● Flutter開発経験者?
→直面した課題と得られたメリットを知り?
 技術選定の際の判断材料になる?
?
● Flutter開発未経験者?
→まずはFlutter触ってみたいと思ってもらう?
12?
1. 前提の共有?
○ じゃらんのFlutter移行?
○ Flutterのレイアウト構築?
?
2. 直面した課題?
?
3. 得られたメリット?
?
4. まとめ?
説明の流れ?
13?
じゃらんのFlutter移行?
14?
Flutter採用の背景?
● じゃらんアプリはiOS/Android共にリリースから?
10年を迎え、長年に渡る開発が行われてきた?
?
?
?
● 上記課題を解決するために、リプレースを検討?
15?
プロジェクトの大規模化によ
るビルド時間の増加
プロジェクト全体の
コードが古くなっている
じゃらんアプリのリプレース検討?
● クロスプラットフォーム技術の検討?
○ iOS/Android開発工数?
○ リプレースコスト?
?
● クロスプラットフォーム技術の中でも?
Flutterの開発生産性が最も高いと実感し?
Flutterの採用を決断?
16?
半減?
17?
18?
19?
ここから先の画面は?
全てFlutterで実装?
Flutterへの段階的移行?
● Add-to-app(Add Flutter to existing app)を使用?
● 既存のネイティブプロジェクトにFluterプロジェクトを部分的
に組み込む仕組み?
?
20?
じゃらん?
アプリ?
Swift?
Objective-c?
じゃらん遊び?体験??
Flutterプロジェクト?
Flutterへの段階的移行?
● Add-to-app(Add Flutter to existing app)を使用?
● 既存のネイティブプロジェクトにFluterプロジェクトを部分的
に組み込む仕組み?
?
21?
じゃらん?
アプリ?
Swift?
Objective-c?
じゃらん遊び?体験??
Flutterプロジェクト?
Flutterモジュール?
Flutterのレイアウト構築?
22?
Flutterのレイアウト構築?
● Widget?
○ UIの構成情報を保持するクラス?
?
● Widgetをツリー上に構成することによって?
UIの構築を行う?
?
23?
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?
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?
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?
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?
直面した課題?
29?
1. タブ切り替えのパフォーマンス?
2. Flutterの画面が初期化されない?
3. ネットワーク通信がproxyサーバーを経由しない?
4. Google Mapのクラッシュ?
直面した課題?
30?
1. タブ切り替えのパフォーマンス?
2. Flutterの画面が初期化されない?
3. ネットワーク通信がproxyサーバーを経由しない?
4. Google Mapのクラッシュ?
● タブを使用して表示する情報を?
切り替えるページが存在する?
?
● このタブのページでは、?
口コミの一覧や、プランの一覧を?
リストで表示する?
タブの切り替えをする画面?
31?
発生した問題?
● リストのアイテムを大量に読み込んでタブを切り替える?
?
?
● タブ切り替えのアニメーションが重くなってしまう?
● まれにタブ切り替えのタイミングで?
アプリがクラッシュすることがある?
32?
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?
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?
class TabA extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Text('Tab A'),
);
}
}
● TabA()は画面の中心に
“Tab A”を表示するだけ
サンプルコード: TabA?
35?
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?
タブ切り替え?
TabA? TabB?
38?
TabB?
? Tab Bのリストを下までスクロールする
39?
TabB? TabA?
TabAに?
切り替え?
TabBに?
切り替え?
TabB?
40?
TabB? TabA?
TabAに?
切り替え?
TabBに?
切り替え?
TabB?
● タブ切り替えのアニメーションが非
常に重くなる
● まれにクラッシュする?
なぜアニメーション重くなる??
● タブを切り替えた際、表示?
されないタブはWidget?
ツリーから除外される?
41?
TabA表示時? 罢补产叠表示时?
● 再度タブを表示する際には、表示するタブの?
レイアウトを再計算する必要がある?
?
なぜアニメーション重くなる??
● タブを切り替えた際、表示?
されないタブはWidget?
ツリーから除外される?
42?
TabA表示時? 罢补产叠表示时?
タブが保持するリストのアイテムの高さが可変の場合?
● 1つ目のアイテムから順にレイアウト?
を計算して、高さを決定しないと?
?
なぜアニメーション重くなる??
43?
…?
タブが保持するリストのアイテムの高さが可変の場合?
● 1つ目のアイテムから順にレイアウト?
を計算して、高さを決定しないと?
?
● 前回のスクロール位置の?
アイテムを表示できない?
?
?
なぜアニメーション重くなる??
44?
…?
前回の?
スクロール位置?
タブが保持するリストのアイテムの高さが可変の場合?
● 1つ目のアイテムから順にレイアウト?
を計算して、高さを決定しないと?
?
● 前回のスクロール位置の?
アイテムを表示できない?
?
● この演算のために?
パフォーマンスが低下?
なぜアニメーション重くなる??
45?
…?
前回の?
スクロール位置?
なぜまれにクラッシュする??
● リストのレイアウトを決定する演算を行うことで、?
一時的にメモリを圧迫する?
● この一時的な圧迫で許容値を超えてしまった場合に?
クラッシュが発生していた?
46?
タブ切り替え時?
どう回避したか?
● タブのWidgetにAutomaticKeepAliveClientMixin?
を適用する?
● 非表示になったタブもWidgetツリーから除外されなくなるた
め、再度レイアウトの演算が不要?
47?TabA表示時にTabBが除外されない ?
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?
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?
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を返す
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?
1. タブ切り替えのパフォーマンス?
2. Flutterの画面が初期化されない?
3. ネットワーク通信がproxyサーバーを経由しない?
4. Google Mapのクラッシュ?
53?
Nativeの画面? Flutterの画面?
発生した問題?
● Flutterの画面を閉じて再度開く?
?
?
● 前回開いた画面の状態が残ってしまっている?
54?
55?
Nativeの画面? Flutterの画面?
56?
Nativeの画面?
? Flutterの画面に遷移する
57?
Flutterの画面?
? +のFABをタップ
? 画面の中心にタップした回数が表示
58?
Nativeの画面?Flutterの画面?
dismiss?
59?
Nativeの画面?Flutterの画面?
present?dismiss?
Flutterの画面?
60?
Nativeの画面?Flutterの画面?
present?dismiss?
Flutterの画面?
● 前回のFlutterの画面の
状態が残ってしまっている
● 画面を破棄して再生成したら、初
期状態になるのでは??
61?
じゃらんTOP?
?
present?dismiss?
遊び?体験(Flutter)?遊び?体験(Flutter)?
検索条件指定? じゃらん遊び?体験予約?
を再度開く?
前回の検索条件のまま?
62?
じゃらんTOP?
?
present?dismiss?
遊び?体験(Flutter)?遊び?体験(Flutter)?
検索条件指定? 遊び?体験を再度開く? 前回の検索条件のまま?
● Add-to-appでFlutterの画面を表示する方法の説明
↓
● この問題の原因の説明
おさらい: Add-to-app?
● 既存のネイティブプロジェクトにFluterプロジェクトを部分的
に組み込む仕組み?
?
63?
じゃらん?
アプリ?
Swift?
Objective-c?
じゃらん遊び?体験??
Flutterプロジェクト?
Flutterモジュール?
Flutterの画面を表示するために重要なクラス?
64?
FlutterEngine
FlutterViewController
FlutterView
Flutter?
View?
Controller?
?
?
View?
Controller?
?
?
Flutterの画面を表示するために重要なクラス?
65?
FlutterEngine
FlutterViewController
FlutterView
● ViewControllerの派生クラス?
● FlutterViewControllerに遷移する?
ことでFlutterの画面を表示?
画面遷移?
Flutterの画面を表示するために重要なクラス?
66?
FlutterEngine
FlutterViewController
FlutterView
● FlutterViewControllerに?
乗っているView?
● FlutterモジュールのUIが描画される?
FlutterViewController?
FlutterView?
Flutterの画面を表示するために重要なクラス?
67?
FlutterEngine
FlutterViewController
FlutterView
● Dartを実行して、FlutterViewに?
FlutterモジュールのUIを描画する?
FlutterViewController?
FlutterView?
FlutterEngine?
Flutterの画面を表示するために重要なクラス?
68?
FlutterEngine
FlutterViewController
FlutterView
● Dartを実行して、FlutterViewに?
FlutterモジュールのUIを描画する?
FlutterViewController?
FlutterView?
FlutterEngine?
? Flutterの画面を描画するためには、
FlutterEngineの初期化が必要
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インスタンスの生成
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の初期化は時間がかかるため、予め呼ぶ必要がある
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?
FlutterEngineFlutterViewController
● Flutterの画面を閉じた段階で
FlutterViewControlerは破棄される?
● FlutterEngineはAppDelegateで初期化し、?
参照を保持しておくので、破棄されない?
何故画面を再生成しても初期状態にならない??
73?
FlutterEngine
class AppDelegate: FlutterAppDelegate {
lazy var flutterEngine = FlutterEngine(name: "my flutter engine")
FlutterViewController
● FlutterEngineはAppDelegateで初期化し、?
参照を保持しておくので、破棄されない?
何故画面を再生成しても初期状態にならない??
74?
FlutterEngine
● Dartを実行しているのはFlutterEngine?
● Flutterの画面を閉じても、Dart内で破棄?
していないStateは残ってしまう?
FlutterViewController
● FlutterEngineはAppDelegateで初期化し、?
参照を保持しておくので、破棄されない?
何故画面を再生成しても初期状態にならない??
75?
FlutterEngine
● Dartを実行しているのはFlutterEngine?
● Flutterの画面を閉じても、Dart内で破棄?
していないStateは残ってしまう?
● 時間がかかるためFlutterEngineを毎回初期化
するわけにもいかない?
FlutterViewController
● Flutterモジュールの最初に空の画面を挿入(InitialPage)?
?
● FlutterViewController遷移時に?
○ InitialPage以外のページを全て破棄?
○ 本来最初に表示したいページを生成して、?
即座に遷移する?
?
どう回避したか?
76?
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コードを呼び出す
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へ画面遷移する
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モジュールの先頭の
画面に設定
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が
呼び出された際に
実行されるコード
? 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?
● 条件
一番最初の画面であること。
すなわち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?
● 条件を満たしたタイミングで本
来表示したかった
最初の画面が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?
Nativeの画面?Flutterの画面?
present?dismiss?
Flutterの画面?
● 再度表示した際に、初期化される
直面した課題?
85?
1. タブ切り替えのパフォーマンス?
2. Flutterの画面が初期化されない?
3. ネットワーク通信がproxyサーバーを経由しない?
4. Google Mapのクラッシュ?
● 詳細なデバッグや試験を行う際に?
パケットモニタリングを使用したい?
● iOSプロジェクトにおいては?
Wi-Fi設定からproxyサーバーの?
IPアドレスとポート番号を入力する?
ことでパケットモニタリング可能?
(例: Charles)?
iOSプロジェクトでパケットモニタリング?
86?
● 同様のWi-Fi設定をFlutterプロジェクトに行っても、通信が
Proxyサーバーを経由せず?
パケットモニタリングを使用できない?
?
発生した問題?
87?
proxyサーバーを経由するためには?
88?
● HttpClientクラスに?
プロキシ自動設定(PAC)を?
明示的に指定する必要がある?
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?
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?
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?
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?
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?
システムProxyからPACを指定する?
● じゃらんではsystem_proxyパッケージを使用?
94?
pub system_proxy:https://pub.dev/packages/system_proxy, (参照2020-08-10)
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?
1. タブ切り替えのパフォーマンス?
2. Flutterの画面が初期化されない?
3. ネットワーク通信がproxyサーバーを経由しない?
4. Google Mapのクラッシュ?
Google Mapの使用?
● レジャー施設の場所や集合場所を示
すために、地図(Google Map)を表示す
る?
● google_maps_flutterパッケージを使用?
97?
pub google_maps_flutter:https://pub.dev/packages/google_maps_flutter, (参照2020-08-10)
● google_maps_flutterはDevelopers Preview?
● 実際に使用すると、?
Google Mapを何度も表示した際に?
アプリがクラッシュする問題が発覚?
?
● Google Mapを閉じてもメモリが解放?
されない?
● 地図を開くたびにメモリを圧迫してしまい、?
最終的にクラッシュしてしまっていた?
発生した問題?
98?
Memory Usage?
原因と問題の回避?
● GoogleMapが内部で使用しているPlatformViewに?
おいて循環参照があり、それによってGoogleMapが?
解放されなくなっていた?
?
● 当時使用していたFlutter ver1.12.13+hotfix.7から?
Flutter ver1.15.17にアップデートしたことで、?
メモリが解放される様になり、回避することができた?
?
?
99?
ライブラリのステータス?
● developers preview等のライブラリや機能に関する?
既知の問題には、issueにタグが付与されている?
● その様なライブラリや機能を使用する際には、?
タグでフィルタリングして、関連issueを確認することで?
事前に問題を把握すると吉?
100?
pub google_maps_flutter:https://pub.dev/packages/google_maps_flutter, (参照2020-08-10)
直面した課題まとめ?
● Flutterを採用してみると、いくつかの課題に直面した?
● GoogleMapがdevelopers previewである等、?
プラットフォームの未成熟な部分は若干ある??
● しかし、いずれの直面した課題も回避することは?
できていて、プロダクション採用不可能となる様な?
事態には直面しなかった?
101?
これらの課題を乗り越えた結果?
どんなメリットを得られたのか?
102?
工数の
削減
開発効率
の向上
最も大きく得られたメリット?
103?
● 開発効率は著しく向上した?
?
1. 既成部品の充実?
?
2. hot reload/restart?
?
3. IDE(Android Studio)の機能の充実?
?
得られたメリット:開発効率の向上?
104?
開発効率
の向上
1. 既成部品の充実?
105?
● Widgetの種類がとても充実?
している?
?
● じゃらんにおいては、これら既成
部品でほぼ事足りた?
● 既成部品を積極的に使用できた
ことが、開発効率向上に?
寄与した?
?
Widget catalog:https://flutter.dev/docs/development/ui/widgets, (参照2020-08-10)
● コードを修正した際に、ビルドし直さなくても?
その修正が即座にアプリに反映される仕組み?
○ hot reload: 約0.5s?
○ hot restart: 約3s ?
?
● じゃらんはビルド時間が大分増加してしまって?
いたので、この仕組みの開発効率向上への?
寄与は大きかった?
2. hot reload/ restart?
106?
● Widgetの上でoption+Enterを押すことで、包む
Widget等の候補を表示。?
Widgetツリーの構築をサクサクできる?
?
● “stless”や”stful”と打つことで?
Stateless WidgetやStateful Widget?
を自動生成?
3. IDE(Android Studio)の機能の充実?
107?
● XCodeで開発する場合に比べて開発スピードが向上した?
?
?
1. iOS/Androidの開発工数削減?
?
2. 開発以外の工数削減?
?
3. 移行工数の削減?
?
得られたメリット:工数の削減?
108?
工数の
削減
● じゃらんはメディアであり、?
プラットフォーム固有の機能が少ない?
● 完全移行が完了すれば、iOS/Androidの開発工数を?
ほぼ半分にすることができそう?
1. iOS/Androidの開発工数削減?
109?
● iOSとAndroidの仕様差分をなるべく減らす?
● デザインをマテリアルデザインに統一?
?
● 開発以外の工数も削減することができている?
?
2. 開発以外の工数削減?
110?
開発工数? 要件検討工数? デザイン作成工数?
5割減? 5割減? 3割減?
● 段階的移行を行っていることにより、各プラットフォームの
実装が多少必要になっている?
○ 例えば、ネイティブ側が保持するアプリの設定情報を?
Flutterモジュールに伝播する処理?
?
● しかし、大部分は共通化できていて、その点?
移行コストも大きく削減することができている?
3. 移行工数の削減?
111?
開発効率向上、工数削減以外にも多くのメリット?
● 宣言的UI構築が素晴らしい?
○ 参考: 宣言的UI そな太さん
https://speakerdeck.com/sonatard/xuan-yan-de-ui?
?
● FlutterがOSSであることで、内部処理を確認できる?
?
● パフォーマンスモニタリングが充実している?
?
● 全てDartで記述するため、コードレビューや?
コンフリクトの解消がしやすい?
などなど...? 112?
まとめ?
113?
● じゃらんでは現在Flutterへの段階的移行を行っている?
● 複数課題に直面したものの、回避することはできた?
● 直面した課題を乗り越えたことで、開発効率向上や開発?
工数削減など多くのメリットを得ることができた?
● 完全移行に向けて引き続きFlutter頑張ります??
まとめ?
114?
ありがとうございました!?
115?

More Related Content

贵濒耻迟迟别谤移行の苦労と、乗り越えた先に得られたもの