SlideShare une entreprise Scribd logo
1  sur  138
Télécharger pour lire hors ligne
• Recruit Lifestyle Co., Ltd.

• iOS / Flutter 

• じゃらんアプリ開発T

Keisuke Kiriyama

• 「宿」だけでなく「旅行」という

優れた体験を提供する











じゃらんアプリ

じゃらんアプリでは
Flutterを導入しています!
• Google製のクロスプラットフォームSDK

• 一つのソースコードで複数のプラットフォームの

アプリケーションを構築することが可能



• フレームワーク:Flutter

• 開発言語:Dart

Flutterとは

本日は
FlutterのUI構築の内部の仕組み
について紹介します。
void main() => runApp(
Center(
child: RichText(
text: TextSpan(
text: 'Hello world',
style: TextStyle(fontSize: 40),
),
textDirection: TextDirection.ltr,
),
),
);
Center
Rich
Text
• Widgetをツリー状に構成する
ことによって、UIを構築
“A widget is an immutable description of
part of a user interface.”
Widget class reference: https://api.flutter.dev/flutter/widgets/Widget-class.html, (参照2020-05-20)
構築されたWidgetツリーは
どの様に画面に描画される?
• 拡張可能な階層化システム

– 複数のライブラリが存在



• 以下のレイヤーを使用してWidgetツ
リーを構築している

– Materialレイヤー

– Cupertinoレイヤー

– Widgetsレイヤー





Material Cupertino
Widgets
Rendering
dart:ui
Flutterのシステムアーキテクチャ

• 各レイヤーは

下位のレイヤーに依存

Material Cupertino
Widgets
Rendering
dart:ui
Flutterのシステムアーキテクチャ

Widgetのツリーを構築
RenderingレイヤーのAPIが呼び出される
dart:uiレイヤーのAPIが呼び出される
画面に描画が行われる
• レイヤーごとに責務が分離され、
上位レイヤーは下位レイヤーの
課題を解決する



• 各レイヤーの責務とレイヤー間で
どの様に連携しているかを理解
することで、Widgetツリーがスク
リーンに描画されるまでの流れを
理解できる

Material Cupertino
Widgets
Rendering
dart:ui
Flutterのシステムアーキテクチャ

• Widgetの種類や使い方



• Widgetツリーの構築の仕方



• Flutter Engineの詳細

話さないこと

• 各レイヤーが担う責務



• 各レイヤー間でどの様に

連携しているか

– WidgetsレイヤーとRendering
レイヤー間で行われている

パフォーマンスの最適化

話すこと

説明の流れ

• 下位のレイヤーから順に、そのレイヤーを使用して

アプリケーションを構築したサンプルを確認し、

そのレイヤーにおける責務を理解する



• 解説に使用するサンプル

– flutterリポジトリのexamples/layersディレクトリ

– 各レイヤーを使用してアプリケーションを構築したサンプルコード

– flutter repository:https://github.com/flutter/flutter/tree/1.17.1/examples/layers(参照2020-5-20)

dart:ui
Material Cupertino
Widgets
Rendering
dart:ui
• Flutter Engineによって公開されているレイヤー

• アプリケーションを構築するのに使用する

最も低レベルのサービスを提供する

• Canvasクラスの描画メソッドを呼ぶことでUIを描画

dart:uiレイヤー

Material Cupertino
Widgets
Rendering
dart:ui
import 'dart:ui' as ui;
void beginFrame(Duration timeStamp) {
...
}
void main() {
ui.window.onBeginFrame = beginFrame;
ui.window.scheduleFrame();
}
• examples/layers/raw/
hello_world.dart
• dart:uiレイヤーを使用して
画面の中心にhello worldを
表示する
• ui.window.onBeginFrameに
渡されたコールバックが、毎フ
レーム呼び出される
import 'dart:ui' as ui;
void beginFrame(Duration timeStamp) {
...
}
void main() {
ui.window.onBeginFrame = beginFrame;
ui.window.scheduleFrame();
}
• コールバック内に
描画処理を記述
• 毎フレーム呼び出され
UIを描画
import 'dart:ui' as ui;
void beginFrame(Duration timeStamp) {
...
}
void main() {
ui.window.onBeginFrame = beginFrame;
ui.window.scheduleFrame();
}
• ParagraphBuilderを
インスタンス化し、
addTextメソッドを呼び出す
• ParagraphBuilderはbuild
メソッドで、フォントサイズ等のス
タイルを適用したParagraphのイ
ンスタンスを
返す
• layoutメソッドで幅の制約を渡す
(この幅を超える場合に改行が
行われる)
void beginFrame(Duration timeStamp) {
...
final ui.ParagraphBuilder paragraphBuilder = ui.ParagraphBuilder(
ui.ParagraphStyle(textDirection: ui.TextDirection.ltr),
)
..addText('Hello, world.');
final ui.Paragraph paragraph = paragraphBuilder.build()
..layout(ui.ParagraphConstraints(width: logicalSize.width));
...
final ui.Canvas canvas = ui.Canvas(recorder, physicalBounds);
...
canvas.drawParagraph(paragraph, ui.Offset(
(logicalSize.width - paragraph.maxIntrinsicWidth) / 2.0,
(logicalSize.height - paragraph.height) / 2.0,
));
}
...
void beginFrame(Duration timeStamp) {
...
final ui.ParagraphBuilder paragraphBuilder = ui.ParagraphBuilder(
ui.ParagraphStyle(textDirection: ui.TextDirection.ltr),
)
..addText('Hello, world.');
final ui.Paragraph paragraph = paragraphBuilder.build()
..layout(ui.ParagraphConstraints(width: logicalSize.width));
...
final ui.Canvas canvas = ui.Canvas(recorder, physicalBounds);
...
canvas.drawParagraph(paragraph, ui.Offset(
(logicalSize.width - paragraph.maxIntrinsicWidth) / 2.0,
(logicalSize.height - paragraph.height) / 2.0,
));
}
...
• Canvasクラスを
インスタンス化
• Paragraphのインスタンス
を、オフセットを指定して
drawParagraphに渡すことで
文字列が描画
• Hello worldを表示するだけでも、こ
れだけの描画処理が必要になる
void beginFrame(Duration timeStamp) {
final double devicePixelRatio = ui.window.devicePixelRatio;
final ui.Size logicalSize = ui.window.physicalSize / devicePixelRatio;
final ui.ParagraphBuilder paragraphBuilder = ui.ParagraphBuilder(
ui.ParagraphStyle(textDirection: ui.TextDirection.ltr),
)
..addText('Hello, world.');
final ui.Paragraph paragraph = paragraphBuilder.build()
..layout(ui.ParagraphConstraints(width: logicalSize.width));
final ui.Rect physicalBounds = ui.Offset.zero & (logicalSize * devicePixelRatio);
final ui.PictureRecorder recorder = ui.PictureRecorder();
final ui.Canvas canvas = ui.Canvas(recorder, physicalBounds);
canvas.scale(devicePixelRatio, devicePixelRatio);
canvas.drawParagraph(paragraph, ui.Offset(
(logicalSize.width - paragraph.maxIntrinsicWidth) / 2.0,
(logicalSize.height - paragraph.height) / 2.0,
));
final ui.Picture picture = recorder.endRecording();
final ui.SceneBuilder sceneBuilder = ui.SceneBuilder()
..pushClipRect(physicalBounds)
..addPicture(ui.Offset.zero, picture)
..pop();
ui.window.render(sceneBuilder.build());
}
• 全ての座標計算を自分で行わなくてはならない

– 複雑で膨大な算術演算を記述しなくてはならない

– レイアウトの修正をする際には、あらゆる箇所の計算の整合性を保つ必要

– デバッグが困難

dart:uiレイヤーで構築する際の問題

• 全ての座標計算を自分で行わなくてはならない

– 複雑で膨大な算術演算を記述しなくてはならない

– レイアウトの修正をする際には、あらゆる箇所の計算の整合性を保つ必要

– デバッグが困難



• 毎フレーム計算を行う必要がある

– 座標計算の結果はキャッシュしておきたい

– しかし抽象化のレイヤーがないと不可能なほど複雑になってしまう

dart:uiレイヤーで構築する際の問題

Rendering
Material Cupertino
Widgets
Rendering
dart:ui
Renderingレイヤー

• dart:uiレイヤーにおける2つの問題点を解決する

– 全ての座標計算を自分で行わなくてはならない

– 毎フレーム計算を行う必要がある



• そのためにRenderObjectを使用する

– レイアウトやペイントのためのプロトコルを保持しているクラス



• 行うことはRenderObjectをノードとする

Renderツリーの構築

import 'package:flutter/rendering.dart';
void main() {
RenderingFlutterBinding(
root: RenderPositionedBox(
alignment: Alignment.center,
child: RenderParagraph(
const TextSpan(text: 'Hello, world.'),
textDirection: TextDirection.ltr,
),
),
);
}
• examples/layers/rendering/
hello_world.dart
• Renderingレイヤーを使用して
画面の中心にhello worldを
表示する
• RenderObjectをノードとするツ
リーを構築
import 'package:flutter/rendering.dart';
void main() {
RenderingFlutterBinding(
root: RenderPositionedBox(
alignment: Alignment.center,
child: RenderParagraph(
const TextSpan(text: 'Hello, world.'),
textDirection: TextDirection.ltr,
),
),
);
}
• レイアウトが抽象化され、座標
計算は不要になった
import 'package:flutter/rendering.dart';
void main() {
RenderingFlutterBinding(
root: RenderPositionedBox(
alignment: Alignment.center,
child: RenderParagraph(
const TextSpan(text: 'Hello, world.'),
textDirection: TextDirection.ltr,
),
),
);
}
• 計算結果はRenderingレイ
ヤーによってキャッシュされ、
毎フレーム計算する必要がな
くなった
import 'package:flutter/rendering.dart';
void main() {
RenderingFlutterBinding(
root: RenderPositionedBox(
alignment: Alignment.center,
child: RenderParagraph(
const TextSpan(text: 'Hello, world.'),
textDirection: TextDirection.ltr,
),
),
);
}
• Renderingレイヤーによって、
dart:uiレイヤーの以下の
問題点が解決された
1. 全ての座標計算を自分で

行わなくてはならない

2. 毎フレーム計算を行う必要がある

import 'package:flutter/rendering.dart';
void main() {
RenderingFlutterBinding(
root: RenderPositionedBox(
alignment: Alignment.center,
child: RenderParagraph(
const TextSpan(text: 'Hello, world.'),
textDirection: TextDirection.ltr,
),
),
);
}
• Layoutフェーズ

• Paintフェーズ

• Compositeフェーズ

• Rasteraizeフェーズ

構築されたRenderツリーが行うこと

• 各ノード(RenderObject)の

サイズと位置を決定



• プロセスは2つのパスから構成

1. 親からそれぞれの子に

constraints(制約)が渡される

2. 子から親へサイズが渡される



• Constraints(制約)

– レイアウトを決定する際に

守らなくてはいけないルール

Layoutフェーズ

Constraints
Size
Constraints
Size
Constraints
Size
• 許容される最大と最小の幅、および最大と最小の高さ

• 子に特定のサイズを指定するときには最大と最小を同じ値にする

• シンプルなため、高速にレイアウトを決定することができる

BoxConstraints

Min Width
Max Width
MinHeight
MaxHeight
1. ツリーのルートから

それぞれの子にConstraintsが渡される



2. 子は新しいConstraintsを生成し

自身の子に渡す



3. リーフノードに到達したら、

リーフノードは自身のサイズを

決定して、親に伝播する



4. 子のサイズを受け取ったら、その情報を元に、
子のオフセットと自身のサイズを決定し、自身
のサイズを親に伝播する



5. ルートに到達するまで続く

全てのノードのサイズと位置が決定



Constraints
Size
Constraints
Size
Constraints
Size
Constraints
Size
Constraints
Size
Constraints
Size
1. ツリーのルートから

それぞれの子にConstraintsが渡される



2. 子は新しいConstraintsを生成し

自身の子に渡す



3. リーフノードに到達したら、

リーフノードは自身のサイズを

決定して、親に伝播する



4. 子のサイズを受け取ったら、その情報を元に、
子のオフセットと自身のサイズを決定し、自身
のサイズを親に伝播する



5. ルートに到達するまで続く

全てのノードのサイズと位置が決定



Constraints
Size
Constraints
Size
Constraints
Size
1. ツリーのルートから

それぞれの子にConstraintsが渡される



2. 子は新しいConstraintsを生成し

自身の子に渡す



3. リーフノードに到達したら、

リーフノードは自身のサイズを

決定して、親に伝播する



4. 子のサイズを受け取ったら、その情報を元に、
子のオフセットと自身のサイズを決定し、自身
のサイズを親に伝播する



5. ルートに到達するまで続く

全てのノードのサイズと位置が決定



Constraints
Size
Constraints
Size
Constraints
Size
1. ツリーのルートから

それぞれの子にConstraintsが渡される



2. 子は新しいConstraintsを生成し

自身の子に渡す



3. リーフノードに到達したら、

リーフノードは自身のサイズを

決定して、親に伝播する



4. 子のサイズを受け取ったら、その情報を元に、
子のオフセットと自身のサイズを決定し、自身
のサイズを親に伝播する



5. ルートに到達するまで続く

全てのノードのサイズと位置が決定



Constraints
Size
Constraints
Size
Constraints
Size
1. ツリーのルートから

それぞれの子にConstraintsが渡される



2. 子は新しいConstraintsを生成し

自身の子に渡す



3. リーフノードに到達したら、

リーフノードは自身のサイズを

決定して、親に伝播する



4. 子のサイズを受け取ったら、その情報を元に、
子のオフセットと自身のサイズを決定し、自身
のサイズを親に伝播する



5. ルートに到達するまで続く

全てのノードのサイズと位置が決定



• Paintフェーズの前に実行される
Layoutフェーズで各ノードのサ
イズとオフセットは決定している



• 子ノードのオフセット

を指定して、dart:uiレイヤーの
Canvasに描画を行う

Paintフェーズ

offset
offset
offset
• RenderParagraphの
paintフェーズで実行さ
れる描画処理
• Offsetが与えられ、
dart:uiレイヤーの
Canvasに描画を行う
void main() {
RenderingFlutterBinding(
root: RenderPositionedBox(
alignment: Alignment.center,
child: RenderParagraph(
const TextSpan(text: 'Hello, world.'),
textDirection: TextDirection.ltr,
),
),
);
}
void paint(Canvas canvas, Offset offset) {
...
canvas.drawParagraph(_paragraph, offset);
}
Renderingレイヤー
どんな課題を抱えている?
• Viewの初期化処理の記述と更新処理の記述が必要



Renderingレイヤーは命令的なUI構築

• サンプル(カウンター)
1. スクリーンの中心に0を表示
2. 数字がタップされたら
インクリメントする
import 'package:flutter/rendering.dart';
final RenderParagraph paragraph = RenderParagraph(
TextSpan(text: '0'),
textDirection: TextDirection.ltr,
);
void incrementCounter() {
final TextSpan textSpan = paragraph.text;
int count = int.parse(textSpan.text);
paragraph.text = TextSpan(text: (++count).toString());
}
void main() {
RenderingFlutterBinding(
root: RenderPointerListener(
onPointerDown: (_) => incrementCounter(),
child: RenderPositionedBox(
alignment: Alignment.center,
child: paragraph,
),
),
);
}
import 'package:flutter/rendering.dart';
final RenderParagraph paragraph = RenderParagraph(
TextSpan(text: '0'),
textDirection: TextDirection.ltr,
);
void incrementCounter() {
final TextSpan textSpan = paragraph.text;
int count = int.parse(textSpan.text);
paragraph.text = TextSpan(text: (++count).toString());
}
void main() {
RenderingFlutterBinding(
root: RenderPointerListener(
onPointerDown: (_) => incrementCounter(),
child: RenderPositionedBox(
alignment: Alignment.center,
child: paragraph,
),
),
);
}
• RenderParagraphのインスタ
ンスを変数に格納し、
参照を保持する
import 'package:flutter/rendering.dart';
final RenderParagraph paragraph = RenderParagraph(
TextSpan(text: '0'),
textDirection: TextDirection.ltr,
);
void incrementCounter() {
final TextSpan textSpan = paragraph.text;
int count = int.parse(textSpan.text);
paragraph.text = TextSpan(text: (++count).toString());
}
void main() {
RenderingFlutterBinding(
root: RenderPointerListener(
onPointerDown: (_) => incrementCounter(),
child: RenderPositionedBox(
alignment: Alignment.center,
child: paragraph,
),
),
);
}
• Renderツリーの初期化
• RenderPositionedBoxの子
ノードに、変数に格納した
RenderParagraphを挿入
import 'package:flutter/rendering.dart';
final RenderParagraph paragraph = RenderParagraph(
TextSpan(text: '0'),
textDirection: TextDirection.ltr,
);
void incrementCounter() {
final TextSpan textSpan = paragraph.text;
int count = int.parse(textSpan.text);
paragraph.text = TextSpan(text: (++count).toString());
}
void main() {
RenderingFlutterBinding(
root: RenderPointerListener(
onPointerDown: (_) => incrementCounter(),
child: RenderPositionedBox(
alignment: Alignment.center,
child: paragraph,
),
),
);
}
• タップされたら、表示している
数字をインクリメントするメソッ
ドを呼び出す
• RenderParagraphが保持
している数字を取得、
インクリメントして
RenderParagraphを更新する
import 'package:flutter/rendering.dart';
final RenderParagraph paragraph = RenderParagraph(
TextSpan(text: '0'),
textDirection: TextDirection.ltr,
);
void incrementCounter() {
final TextSpan textSpan = paragraph.text;
int count = int.parse(textSpan.text);
paragraph.text = TextSpan(text: (++count).toString());
}
void main() {
RenderingFlutterBinding(
root: RenderPointerListener(
onPointerDown: (_) => incrementCounter(),
child: RenderPositionedBox(
alignment: Alignment.center,
child: paragraph,
),
),
);
}
• Renderingレイヤーは命令的
なUI構築であるため、
Viewの初期化処理の記述と更
新処理の記述が必要
Viewの初期化処理
import 'package:flutter/rendering.dart';
final RenderParagraph paragraph = RenderParagraph(
TextSpan(text: '0'),
textDirection: TextDirection.ltr,
);
void incrementCounter() {
final TextSpan textSpan = paragraph.text;
int count = int.parse(textSpan.text);
paragraph.text = TextSpan(text: (++count).toString());
}
void main() {
RenderingFlutterBinding(
root: RenderPointerListener(
onPointerDown: (_) => incrementCounter(),
child: RenderPositionedBox(
alignment: Alignment.center,
child: paragraph,
),
),
);
}
Viewの更新処理
• Renderツリーを見ただけで
は、Viewの状態が何に依存
しているかわからない
• Viewの最終的な状態が
想像しにくい
import 'package:flutter/rendering.dart';
final RenderParagraph paragraph = RenderParagraph(
TextSpan(text: '0'),
textDirection: TextDirection.ltr,
);
void incrementCounter() {
final TextSpan textSpan = paragraph.text;
int count = int.parse(textSpan.text);
paragraph.text = TextSpan(text: (++count).toString());
}
void main() {
RenderingFlutterBinding(
root: RenderPointerListener(
onPointerDown: (_) => incrementCounter(),
child: RenderPositionedBox(
alignment: Alignment.center,
child: paragraph,
),
),
);
}
import 'package:flutter/rendering.dart';
final RenderParagraph paragraph = RenderParagraph(
TextSpan(text: '0'),
textDirection: TextDirection.ltr,
);
void incrementCounter() {
final TextSpan textSpan = paragraph.text;
int count = int.parse(textSpan.text);
paragraph.text = TextSpan(text: (++count).toString());
}
void main() {
RenderingFlutterBinding(
root: RenderPointerListener(
onPointerDown: (_) => incrementCounter(),
child: RenderPositionedBox(
alignment: Alignment.center,
child: paragraph,
),
),
);
}
• Viewの記述があらゆる箇所に
存在する
• コードのシンプルさが
損なわれてしまう
• Viewの初期化処理の記述と更新処理の記述が必要



• Renderツリーを見ただけでは、Viewの状態が何に依存してるかわ
からず、最終的なViewの状態を想像しにくい

• コードのあらゆる箇所にViewの記述が存在し、

コードのシンプルさが損なわれる



Renderingレイヤーは命令的なUI構築

Widgets
Material Cupertino
Widgets
Rendering
dart:ui
= F (
• Viewの状態は現在のStateのみに依存する

Widgetsレイヤーは宣言的なUI構築

View State )
• Viewの最終的な状態のみを記述する

import 'package:flutter/widgets.dart';
...
class _MyAppState extends State<MyApp> {
int _count = 0;
void incrementCounter() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: incrementCounter,
child: Center(
child: Text(
_count.toString(),
textDirection: TextDirection.ltr,
),
),
);
}
}
• サンプル(カウンター)
1. スクリーンの中心に0を表示
2. 数字がタップされたら
インクリメントする
import 'package:flutter/widgets.dart';
...
class _MyAppState extends State<MyApp> {
int _count = 0;
void incrementCounter() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: incrementCounter,
child: Center(
child: Text(
_count.toString(),
textDirection: TextDirection.ltr,
),
),
);
}
}
• この例におけるstate
• stateの初期化
import 'package:flutter/widgets.dart';
...
class _MyAppState extends State<MyApp> {
int _count = 0;
void incrementCounter() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: incrementCounter,
child: Center(
child: Text(
_count.toString(),
textDirection: TextDirection.ltr,
),
),
);
}
}
• buildメソッドで
Widgetツリーを構築
import 'package:flutter/widgets.dart';
...
class _MyAppState extends State<MyApp> {
int _count = 0;
void incrementCounter() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: incrementCounter,
child: Center(
child: Text(
_count.toString(),
textDirection: TextDirection.ltr,
),
),
);
}
}
• タップされたら、表示している
数字をインクリメントするメソッ
ドを呼び出す
• countをインクリメントし、state
の更新を行う
import 'package:flutter/widgets.dart';
...
class _MyAppState extends State<MyApp> {
int _count = 0;
void incrementCounter() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: incrementCounter,
child: Center(
child: Text(
_count.toString(),
textDirection: TextDirection.ltr,
),
),
);
}
}
import 'package:flutter/widgets.dart';
...
class _MyAppState extends State<MyApp> {
int _count = 0;
void incrementCounter() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: incrementCounter,
child: Center(
child: Text(
_count.toString(),
textDirection: TextDirection.ltr,
),
),
);
}
}
• Viewの状態は現在の
stateのみに依存する
• Viewの状態が現在のstate
のみに依存することに
よって、
Viewの記述は最終的な状態
のみを記述すれば良くなる
import 'package:flutter/widgets.dart';
...
class _MyAppState extends State<MyApp> {
int _count = 0;
void incrementCounter() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: incrementCounter,
child: Center(
child: Text(
_count.toString(),
textDirection: TextDirection.ltr,
),
),
);
}
}
• コードがUIをそのまま表現して
いて、Viewの最終的な状態を
想像しやすい
• スクリーンの中心に_countの
数字が表示されることが
Widgetツリーをパッと見ただけ
でわかる
import 'package:flutter/widgets.dart';
...
class _MyAppState extends State<MyApp> {
int _count = 0;
void incrementCounter() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: incrementCounter,
child: Center(
child: Text(
_count.toString(),
textDirection: TextDirection.ltr,
),
),
);
}
}
import 'package:flutter/widgets.dart';
...
class _MyAppState extends State<MyApp> {
int _count = 0;
void incrementCounter() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: incrementCounter,
child: Center(
child: Text(
_count.toString(),
textDirection: TextDirection.ltr,
),
),
);
}
}
• 表示する数字を更新
する際にはstateの更新
を行い、Viewを直接更新する
ことはしない
• Viewの記述はbuildメソッドの
中だけで完結できる
import 'package:flutter/widgets.dart';
...
class _MyAppState extends State<MyApp> {
int _count = 0;
void incrementCounter() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: incrementCounter,
child: Center(
child: Text(
_count.toString(),
textDirection: TextDirection.ltr,
),
),
);
}
}
• Stateを管理するコードにView
の記述が入らない
• コードをシンプルに保つことが
できる
= F (
• Viewの状態は現在のStateのみに依存する







• 最終的なViewの状態を想像しやすくなる

• コードをシンプルに保つことができる







Widgetsレイヤーは宣言的なUI構築

View State )
• Viewの最終的な状態のみを記述する

Widgetsレイヤーは
どうやって宣言的UIを
実現しているのか?
Renderingレイヤー
RenderSquare
color:
Render
Circle
color:
Widgetsレイヤー
Square
color:
Circle
color:
Square
color: blue
Circle
color: red
Widget Tree
runApp()
Render
Circle
color: red
RenderSquare
color: blue
Element
Square
color: blue
Circle
color: red
Widget Tree
Element
Render Tree仲介役(後述)
Elementを仲介役として対応するRenderツリーを構築(Widgetの情報を伝播)
Render
Circle
color: red
RenderSquare
color: blue
Element
Square
color: blue
Circle
color: red
Widget Tree
Element
Render Tree
RenderツリーはRenderingレイヤーで構築するツリーに対応
Render
Circle
color: red
RenderSquare
color: blue
Element
Square
color: blue
Circle
color: red
Widget Tree
Element
Render Tree
UIを更新したい
● Square
○ blue→green
● Circle
○ red→orange
Render
Circle
color: red
RenderSquare
color: blue
Element
Square
color: blue
Circle
color: red
Widget Tree
Element
Render Tree
Square
color: green
Circle
color:
orange
既存のWidgetを更新することはせず、新たなWidgetのサブツリーを構築
Element
Square
color: green
Circle
color:
orange
Widget Tree
Element
Render
Circle
color: red
RenderSquare
color: blue
Render Tree
新たなWidgetのサブツリーがWidgetツリーに挿入される
Render
Circle
color:
orange
Element
Square
color: green
Circle
color:
orange
Widget Tree
Element
RenderSquare
color: green
Render Tree
update
update
ElementがRenderObjectの更新を行う
• Viewの生成と更新が必要だと、宣言的UIを実現不可能



• UIの更新の度に、Widgetを更新するのではなく、

新たなWidgetのサブツリーを生成する

• 新たなWidgetのサブツリーが生成されると、Elementの

働きによって、RenderObjectが更新される



Renderingレイヤーにおける

Viewの生成と更新が隠蔽された

Widgetsレイヤーの宣言的UIの実現

• UIの構成情報と子Widgetを保持しているオブジェクト

• immutable

• 軽量で生成と破棄にコストがかからない





Widget

• UIの更新や変更を管理するオブジェクト

• mutable

• WidgetとRenderObjectへの参照を保持し、

それらのライフサイクルを管理する



Element

• Widgetツリー

• Elementツリー

• Renderツリー



• 3つのツリーの存在によって、宣言的UIの実現に加え、

UIを更新する際にパフォーマンスの最適化が行われている

3つのツリー

3つのツリーによって行われる
パフォーマンスの最適化
参考 How flutter render widgets: https://www.youtube.com/watch?v=996ZgFRENMs(参照2020-05-20)
void main() => runApp(
RichText(
text: TextSpan(text: 'HogeHoge'),
textDirection: TextDirection.ltr,
),
);
• WidgetツリーからRenderツ
リーが構築されるまでの流れ
をコードレベルで確認
• 解説に使用するサンプル
• RichTextのみ
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}
• runApp関数の中で渡された
WidgetサブツリーをWidget
ツリーに挿入する
Widget Tree Render Tree
RichText
HogeHoge
Element Tree
RichTextのみのWidgetツリーが完成
Widget Tree Render Tree
RichText
HogeHoge
Element Tree
Widgetツリーに挿入されたRichTextからElementを生成する
• Elementを生成するメソッド
はRichTextの親クラスが保持
abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {
...
@override
MultiChildRenderObjectElement createElement() => MultiChildRenderObjectElement(this);
}
• Elementを生成するメソッド
• MultiChildRenderObjectElementというElementを返す
abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {
...
@override
MultiChildRenderObjectElement createElement() => MultiChildRenderObjectElement(this);
}
MultiChild
RenderObject
Element
Widget Tree Render Tree
RichText
HogeHoge
Element Tree
createElement()
生成されたElementはElementツリーに挿入され、Widgetへの参照を持つ
MultiChild
RenderObject
Element
Widget Tree Render Tree
RichText
HogeHoge
Element Tree
次にFlutterはElementにRenderObjectの生成を依頼
• RenderObjectの生成を依頼
されたElementは、mountメ
ソッドにおいて
WidgetからRenderObjectを
取得する
@override
void mount(Element parent, dynamic newSlot) {
...
_renderObject = widget.createRenderObject(this);
...
attachRenderObject(newSlot);
_dirty = false;
}
• Elementは受け取った
RenderObjectをRenderツ
リーに挿入
@override
void mount(Element parent, dynamic newSlot) {
...
_renderObject = widget.createRenderObject(this);
...
attachRenderObject(newSlot);
_dirty = false;
}
• WidgetがRenderObject
を生成するメソッド
• RenderParagraphの
インスタンスを返す
@override
RenderParagraph createRenderObject(BuildContext context) {
return RenderParagraph(
text,
textAlign: textAlign,
textDirection: textDirection ?? Directionality.of(context),
...
textWidthBasis: textWidthBasis,
locale: locale ?? Localizations.localeOf(context, nullOk: true),
);
}
• コンストラクタで
Widgetが保持して
いるUIの構成情報を
RenderObjectに伝播
@override
RenderParagraph createRenderObject(BuildContext context) {
return RenderParagraph(
text,
textAlign: textAlign,
textDirection: textDirection ?? Directionality.of(context),
...
textWidthBasis: textWidthBasis,
locale: locale ?? Localizations.localeOf(context, nullOk: true),
);
}
RenderParagraph
HogeHoge
MultiChild
RenderObject
Element
Widget Tree Render Tree
RichText
HogeHoge
Element Tree
createRenderObject()
生成されたRenderObjectはRenderツリーに挿入され、
ElementはそのRenderObjectへの参照を保持する
RenderParagraph
HogeHoge
MultiChild
RenderObject
Element
Widget Tree Render Tree
RichText
HogeHoge
Element Tree
WidgetツリーからRenderツリーの構築が完了
UIを更新する
void main() {
runApp(
RichText(
text: TextSpan(
text: 'HogeHoge',
textDirection: TextDirection.ltr,
),
),
);
runApp(
RichText(
text: TextSpan(
text: 'FugaFuga',
textDirection: TextDirection.ltr,
),
),
);
}
• 解説に使用するサンプル
• runAppを2回呼び出す
• Widgetのサブツリーを
2回Widgetツリーに
挿入する
• ‘HogeHoge’のRichTextから
‘FugaFuga’のRichTextへ
void main() {
runApp(
RichText(
text: TextSpan(
text: 'HogeHoge',
textDirection: TextDirection.ltr,
),
),
);
runApp(
RichText(
text: TextSpan(
text: 'FugaFuga',
textDirection: TextDirection.ltr,
),
),
);
}
RenderParagraph
HogeHoge
MultiChild
RenderObject
Element
Widget Tree Render Tree
RichText
HogeHoge
Element Tree
1回目のrunAppが完了した状態
Widget Tree Render TreeElement Tree
2回目のrunAppが呼ばれると、新たなWidgetのサブツリーが生成され、
Widgetツリーに挿入される。
RichText
FugaFuga
RenderParagraph
HogeHoge
MultiChild
RenderObject
Element
RichText
HogeHoge
このときElementは自分自身を再利用できるかを確かめにいく
Widget Tree Render TreeElement Tree
RichText
FugaFuga
再利用可能?
RenderParagraph
HogeHoge
MultiChild
RenderObject
Element
RichText
HogeHoge
• Widetが保持しているcanUpdateメソッ
ドが呼び出される
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
• 引数でWidgetツリーに挿入されていた古いWidget
と、置き換える新しいWidgetを受け取る
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
• WidgetのruntimeTypeとKeyが一致しているかを確認
• Widgetを置き換える際、canUpdateがtrueの場合は
ElementとRenderObjectが再利用される
runtimeTypeは共にRichText。keyは共にnull。
canUpdateメソッドはtrueを返す。
Widget Tree Render TreeElement Tree
RichText
FugaFuga
canUpdate() => true
RenderParagraph
HogeHoge
MultiChild
RenderObject
Element
RichText
HogeHoge
古いRichTextは破棄され、Elementは新しいRichTextへの参照を保持する。
そしてElementとRenderObjectは再利用される。
RenderParagraph
HogeHoge
MultiChild
RenderObject
Element
Widget Tree Render TreeElement Tree
RichText
FugaFuga
Widgetが置き換えられた
RenderParagraph
HogeHoge
MultiChild
RenderObject
Element
Widget Tree Render TreeElement Tree
RichText
FugaFuga
この段階ではまだ、RenderParagraphは古いWidgetの情報を保持したまま
この時ElementはRenderObjectの更新を行う
RenderParagraph
HogeHoge
MultiChild
RenderObject
Element
Widget Tree Render TreeElement Tree
RichText
FugaFuga
@override
void updateRenderObject(BuildContext context, RenderParagraph renderObject) {
renderObject
..text = text
..textAlign = textAlign
..textDirection = textDirection ?? Directionality.of(context)
..softWrap = softWrap
...
..textWidthBasis = textWidthBasis
..locale = locale ?? Localizations.localeOf(context, nullOk: true);
}
• Widgetが保持している
RenderObjectの
更新のメソッドが
呼び出される
@override
void updateRenderObject(BuildContext context, RenderParagraph renderObject) {
renderObject
..text = text
..textAlign = textAlign
..textDirection = textDirection ?? Directionality.of(context)
..softWrap = softWrap
...
..textWidthBasis = textWidthBasis
..locale = locale ?? Localizations.localeOf(context, nullOk: true);
}
• Renderツリーに挿入されて
いるRenderObjectを
引数に受け取る
@override
void updateRenderObject(BuildContext context, RenderParagraph renderObject) {
renderObject
..text = text
..textAlign = textAlign
..textDirection = textDirection ?? Directionality.of(context)
..softWrap = softWrap
...
..textWidthBasis = textWidthBasis
..locale = locale ?? Localizations.localeOf(context, nullOk: true);
}
• Widgetが保持する情報で
RenderObjectの
プロパティを更新する
RenderObjectに新しいWidgetの保持する情報が伝播され、
RenderParagraph
FugaFuga
MultiChild
RenderObject
Element
Widget Tree Render Tree
RichText
FugaFuga
Element Tree
updateRenderObject()
UIの更新が完了
RenderParagraph
FugaFuga
MultiChild
RenderObject
Element
Widget Tree Render Tree
RichText
FugaFuga
Element Tree
• WidgetにはUIの構成情報だけを保持させて

できる限り軽量なオブジェクトにしている

• Widgetが頻繁に生成や破棄されてもパフォーマンスに

影響しないように設計されている



• 生成コストの大きいElementとRenderObjectは

できる限り再利用する

– RenderObjectはレイアウトや描画のための全てのロジックを

保持しているため、生成コストが特に大きい

3つのツリーによるパフォーマンスの最適化

層が増えた時の
WidgetツリーからRenderツリー
の構築
RenderSquare
color:
Renderingレイヤー
Render
Circle
color:
Widgetsレイヤー
Square
color:
Circle
color:
Triangle
Render
Triangle
Square
color: blue
Circle
color: red
Widget Tree
2層のWidgetサブツリーがWidgetツリーに挿入された
Square
color: blue
Circle
color: red
Widget Tree
1層目は先ほどと同じ流れでElementツリーとRenderツリーを構築
Element
RenderSquare
color: blue
Element Tree Render Tree
Square
color: blue
Circle
color: red
Widget Tree
rootのElementは参照しているWidgetが
子Widgetを保持している場合、そのWidgetのElementを生成する
Element
RenderSquare
color: blue
Element Tree Render Tree
Circle WidgetのcreateElementが呼ばれElementが生成
Square
color: blue
Circle
color: red
Widget Tree
Element
RenderSquare
color: blue
Element Tree Render Tree
Element
Square
color: blue
Circle
color: red
Widget Tree
Element
RenderSquare
color: blue
Element Tree Render Tree
Element
Render
Circle
color: red
2層目のElementはCircle Widgetから
RenderObjectを受け取りRenderSquareに接続する
2層の場合の、3つのツリーを構築完了
Square
color: blue
Circle
color: red
Widget Tree
Element
RenderSquare
color: blue
Element Tree Render Tree
Element
Render
Circle
color: red
UIを更新する。
新たなWidgetのサブツリーが構築される
Square
Circle
Widget Tree
Element
RenderSquare
color: blue
Element Tree Render Tree
Element
Render
Circle
color: redSquare
Circle
rootのElementがSquare Widgetが保持する
canUpdateメソッドを呼び出す
Square
Circle
Widget Tree
Element
RenderSquare
color: blue
Element Tree Render Tree
Element
Render
Circle
color: redSquare
Circle
canUpdate()
canUpdateメソッドの結果はtrue。
古いWidgetへの参照はドロップされ、1層目の
ElementとRenderSquareは再利用される
Square
Circle
Widget Tree
Element
RenderSquare
color: blue
Element Tree Render Tree
Element
Render
Circle
color: redSquare
Circle
canUpdate():
true
updateRenderObjectを呼び出し、
RenderObjectを更新する
Square
Circle
Widget Tree
Element
RenderSquare
color: green
Element Tree Render Tree
Element
Render
Circle
color: redSquare
Circle
updateRenderObject()
1層目の更新が完了
Square
Circle
Widget Tree
Element
RenderSquare
color: green
Element Tree Render Tree
Element
Render
Circle
color: redSquare
Circle
子のElementがCircle Widgetが保持する
canUpdateメソッドを呼び出す
Square
Circle
Widget Tree
Element
RenderSquare
color: green
Element Tree Render Tree
Element
Render
Circle
color: redSquare
Circle
canUpdate()
Square
Circle
Widget Tree
Element
RenderSquare
color: green
Element Tree Render Tree
Element
Render
Circle
color: redSquare
Circle
canUpdate():
true
canUpdateメソッドの結果はtrue。
古いWidgetへの参照はドロップされ、2層目の
ElementとRenderCircleは再利用される
updateRenderObjectを呼び出し、
RenderCircleを更新
Square
Circle
Widget Tree
Element
RenderSquare
color: green
Element Tree Render Tree
Element
Render
Circle
color:
orange
Square
Circle
updateRenderObject()
ツリーの更新が完了
Square
color: green
Circle
color:
orange
Widget Tree
Element
RenderSquare
color: green
Element Tree Render Tree
Element
Render
Circle
color:
orange
Widgetのタイプが異なる
サブツリーが構築された
Square
Circle
Widget Tree
Element
RenderSquare
color: green
Element Tree Render Tree
Element
Render
Circle
color:
orange
Square
Triangle
1層目は先ほどと同じなので割愛
Square
Circle
Widget Tree
Element
RenderSquare
color: green
Element Tree Render Tree
Element
Render
Circle
color:
orange
Square
Triangle
子のElementがcanUpdateを呼び出す。
今回はWidgetのタイプが異なるためfalse
Square
Circle
Widget Tree
Element
RenderSquare
color: green
Element Tree Render Tree
Element
Render
Circle
color:
orange
Square
Triangle
canUpdate():
false
ElementとRenderObjectを再利用できない
Square
Circle
Widget Tree
Element
RenderSquare
color: green
Element Tree Render Tree
Element
Render
Circle
color:
orange
Square
Triangle
canUpdate():
false
Elementが破棄される。
このタイミングで対応するRenderObjectも破棄
Square
Circle
Widget Tree
Element
RenderSquare
color: green
Element Tree Render Tree
Element
Render
Circle
color:
orange
Square
Triangle
canUpdate():
false
Triangle WidgetのElementとRenderObjectは存在しない
Square
color: green
Widget Tree
Element
RenderSquare
color: green
Element Tree Render Tree
Triangle
最初に構築した時と同じ流れで
ElementとRenderObjectを生成し、更新が完了
Square
color: green
Widget Tree
Element
RenderSquare
color: green
Element Tree Render Tree
Triangle
Element Render
Triangle
• 多層の場合も可能な部分はできるだけ

ツリーの再利用が行われる

• 実際には、相当な数の層でWidgetツリーが構築されるが、この
様な最適化が行われることによって、Flutterは

パフォーマンスの高いUIの構築を実現している



WidgetツリーからRenderツリーが構築されるまで

まとめ
• Flutterのシステムアーキテクチャの全体像

– 階層化システムとして設計され、責務が分離されている

– 上位のレイヤーは下位のレイヤーの課題を解決するために存在



• Widgetsレイヤー

– UIの更新の際にはWidgetを新しく生成することによって

宣言的UIを実現している

– Widgetは軽量なオブジェクトにし、頻繁に生成や破棄が

行われてもパフォーマンスに影響しない様に設計されている

– 生成コストの大きいElementとRenderObjectはなるべく再利用する

まとめ


Contenu connexe

Tendances

REST API のコツ
REST API のコツREST API のコツ
REST API のコツpospome
 
Reactive Extensionsで非同期処理を簡単に
Reactive Extensionsで非同期処理を簡単にReactive Extensionsで非同期処理を簡単に
Reactive Extensionsで非同期処理を簡単にYoshifumi Kawai
 
ドメイン駆動設計入門
ドメイン駆動設計入門ドメイン駆動設計入門
ドメイン駆動設計入門Takuya Kitamura
 
【Unite Tokyo 2019】Understanding C# Struct All Things
【Unite Tokyo 2019】Understanding C# Struct All Things【Unite Tokyo 2019】Understanding C# Struct All Things
【Unite Tokyo 2019】Understanding C# Struct All ThingsUnityTechnologiesJapan002
 
SolrとElasticsearchを比べてみよう
SolrとElasticsearchを比べてみようSolrとElasticsearchを比べてみよう
SolrとElasticsearchを比べてみようShinsuke Sugaya
 
ドメイン駆動設計サンプルコードの徹底解説
ドメイン駆動設計サンプルコードの徹底解説ドメイン駆動設計サンプルコードの徹底解説
ドメイン駆動設計サンプルコードの徹底解説増田 亨
 
Java ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsugJava ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsugMasatoshi Tada
 
Flutter移行の苦労と、乗り越えた先に得られたもの
Flutter移行の苦労と、乗り越えた先に得られたものFlutter移行の苦労と、乗り越えた先に得られたもの
Flutter移行の苦労と、乗り越えた先に得られたものRecruit Lifestyle Co., Ltd.
 
全文検索サーバ Fess 〜 全文検索システム構築時の悩みどころ
全文検索サーバ Fess 〜 全文検索システム構築時の悩みどころ全文検索サーバ Fess 〜 全文検索システム構築時の悩みどころ
全文検索サーバ Fess 〜 全文検索システム構築時の悩みどころShinsuke Sugaya
 
技術的負債を生み出す構造とその対処について
技術的負債を生み出す構造とその対処について技術的負債を生み出す構造とその対処について
技術的負債を生み出す構造とその対処についてMasahiro Nishimi
 
reduxのstate設計の話
reduxのstate設計の話reduxのstate設計の話
reduxのstate設計の話ayatas0623
 
第8回勉強会 開発プロセス 「計画ゲーム~ふりかえり」
第8回勉強会 開発プロセス 「計画ゲーム~ふりかえり」第8回勉強会 開発プロセス 「計画ゲーム~ふりかえり」
第8回勉強会 開発プロセス 「計画ゲーム~ふりかえり」hakoika-itwg
 
イミュータブルデータモデルの極意
イミュータブルデータモデルの極意イミュータブルデータモデルの極意
イミュータブルデータモデルの極意Yoshitaka Kawashima
 
Tier Ⅳ Tech Meetup #2 - 自動運転を作るのはCloudシステムの集合体?? 活用技術を大解剖 -
Tier Ⅳ Tech Meetup #2 - 自動運転を作るのはCloudシステムの集合体?? 活用技術を大解剖 -Tier Ⅳ Tech Meetup #2 - 自動運転を作るのはCloudシステムの集合体?? 活用技術を大解剖 -
Tier Ⅳ Tech Meetup #2 - 自動運転を作るのはCloudシステムの集合体?? 活用技術を大解剖 -Tier_IV
 
実践!Elasticsearch + Sudachi を用いた全文検索エンジン
実践!Elasticsearch + Sudachi を用いた全文検索エンジン実践!Elasticsearch + Sudachi を用いた全文検索エンジン
実践!Elasticsearch + Sudachi を用いた全文検索エンジンS. T.
 
MVPパターンによる設計アプローチ「あなたのアプリ報連相できてますか」
MVPパターンによる設計アプローチ「あなたのアプリ報連相できてますか」MVPパターンによる設計アプローチ「あなたのアプリ報連相できてますか」
MVPパターンによる設計アプローチ「あなたのアプリ報連相できてますか」U-dai Yokoyama
 
継承辺りのもしかしたらマイナーかもしれない C#
継承辺りのもしかしたらマイナーかもしれない C#継承辺りのもしかしたらマイナーかもしれない C#
継承辺りのもしかしたらマイナーかもしれない C#m ishizaki
 
ドメイン駆動設計のためのオブジェクト指向入門
ドメイン駆動設計のためのオブジェクト指向入門ドメイン駆動設計のためのオブジェクト指向入門
ドメイン駆動設計のためのオブジェクト指向入門増田 亨
 
AirLab導入でテストコストの大幅削減と品質向上! 数十台の端末を一斉に全自動テストできる社内DeviceFarmについてご紹介
AirLab導入でテストコストの大幅削減と品質向上! 数十台の端末を一斉に全自動テストできる社内DeviceFarmについてご紹介AirLab導入でテストコストの大幅削減と品質向上! 数十台の端末を一斉に全自動テストできる社内DeviceFarmについてご紹介
AirLab導入でテストコストの大幅削減と品質向上! 数十台の端末を一斉に全自動テストできる社内DeviceFarmについてご紹介KLab Inc. / Tech
 

Tendances (20)

REST API のコツ
REST API のコツREST API のコツ
REST API のコツ
 
Reactive Extensionsで非同期処理を簡単に
Reactive Extensionsで非同期処理を簡単にReactive Extensionsで非同期処理を簡単に
Reactive Extensionsで非同期処理を簡単に
 
PHP版レガシーコード改善に役立つ新パターン #wewlc_jp
PHP版レガシーコード改善に役立つ新パターン #wewlc_jp PHP版レガシーコード改善に役立つ新パターン #wewlc_jp
PHP版レガシーコード改善に役立つ新パターン #wewlc_jp
 
ドメイン駆動設計入門
ドメイン駆動設計入門ドメイン駆動設計入門
ドメイン駆動設計入門
 
【Unite Tokyo 2019】Understanding C# Struct All Things
【Unite Tokyo 2019】Understanding C# Struct All Things【Unite Tokyo 2019】Understanding C# Struct All Things
【Unite Tokyo 2019】Understanding C# Struct All Things
 
SolrとElasticsearchを比べてみよう
SolrとElasticsearchを比べてみようSolrとElasticsearchを比べてみよう
SolrとElasticsearchを比べてみよう
 
ドメイン駆動設計サンプルコードの徹底解説
ドメイン駆動設計サンプルコードの徹底解説ドメイン駆動設計サンプルコードの徹底解説
ドメイン駆動設計サンプルコードの徹底解説
 
Java ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsugJava ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsug
 
Flutter移行の苦労と、乗り越えた先に得られたもの
Flutter移行の苦労と、乗り越えた先に得られたものFlutter移行の苦労と、乗り越えた先に得られたもの
Flutter移行の苦労と、乗り越えた先に得られたもの
 
全文検索サーバ Fess 〜 全文検索システム構築時の悩みどころ
全文検索サーバ Fess 〜 全文検索システム構築時の悩みどころ全文検索サーバ Fess 〜 全文検索システム構築時の悩みどころ
全文検索サーバ Fess 〜 全文検索システム構築時の悩みどころ
 
技術的負債を生み出す構造とその対処について
技術的負債を生み出す構造とその対処について技術的負債を生み出す構造とその対処について
技術的負債を生み出す構造とその対処について
 
reduxのstate設計の話
reduxのstate設計の話reduxのstate設計の話
reduxのstate設計の話
 
第8回勉強会 開発プロセス 「計画ゲーム~ふりかえり」
第8回勉強会 開発プロセス 「計画ゲーム~ふりかえり」第8回勉強会 開発プロセス 「計画ゲーム~ふりかえり」
第8回勉強会 開発プロセス 「計画ゲーム~ふりかえり」
 
イミュータブルデータモデルの極意
イミュータブルデータモデルの極意イミュータブルデータモデルの極意
イミュータブルデータモデルの極意
 
Tier Ⅳ Tech Meetup #2 - 自動運転を作るのはCloudシステムの集合体?? 活用技術を大解剖 -
Tier Ⅳ Tech Meetup #2 - 自動運転を作るのはCloudシステムの集合体?? 活用技術を大解剖 -Tier Ⅳ Tech Meetup #2 - 自動運転を作るのはCloudシステムの集合体?? 活用技術を大解剖 -
Tier Ⅳ Tech Meetup #2 - 自動運転を作るのはCloudシステムの集合体?? 活用技術を大解剖 -
 
実践!Elasticsearch + Sudachi を用いた全文検索エンジン
実践!Elasticsearch + Sudachi を用いた全文検索エンジン実践!Elasticsearch + Sudachi を用いた全文検索エンジン
実践!Elasticsearch + Sudachi を用いた全文検索エンジン
 
MVPパターンによる設計アプローチ「あなたのアプリ報連相できてますか」
MVPパターンによる設計アプローチ「あなたのアプリ報連相できてますか」MVPパターンによる設計アプローチ「あなたのアプリ報連相できてますか」
MVPパターンによる設計アプローチ「あなたのアプリ報連相できてますか」
 
継承辺りのもしかしたらマイナーかもしれない C#
継承辺りのもしかしたらマイナーかもしれない C#継承辺りのもしかしたらマイナーかもしれない C#
継承辺りのもしかしたらマイナーかもしれない C#
 
ドメイン駆動設計のためのオブジェクト指向入門
ドメイン駆動設計のためのオブジェクト指向入門ドメイン駆動設計のためのオブジェクト指向入門
ドメイン駆動設計のためのオブジェクト指向入門
 
AirLab導入でテストコストの大幅削減と品質向上! 数十台の端末を一斉に全自動テストできる社内DeviceFarmについてご紹介
AirLab導入でテストコストの大幅削減と品質向上! 数十台の端末を一斉に全自動テストできる社内DeviceFarmについてご紹介AirLab導入でテストコストの大幅削減と品質向上! 数十台の端末を一斉に全自動テストできる社内DeviceFarmについてご紹介
AirLab導入でテストコストの大幅削減と品質向上! 数十台の端末を一斉に全自動テストできる社内DeviceFarmについてご紹介
 

Similaire à FlutterをRenderObjectまで理解する

Unityの夕べ in Fukuoka
Unityの夕べ in FukuokaUnityの夕べ in Fukuoka
Unityの夕べ in FukuokaShinobu Izumi
 
iOSプログラマへ。HTML5 Canvasがおもしろい!
iOSプログラマへ。HTML5 Canvasがおもしろい!iOSプログラマへ。HTML5 Canvasがおもしろい!
iOSプログラマへ。HTML5 Canvasがおもしろい!cocopon
 
Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力
Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力
Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力ThinReports
 
2012 05-19第44回cocoa勉強会発表資料
2012 05-19第44回cocoa勉強会発表資料2012 05-19第44回cocoa勉強会発表資料
2012 05-19第44回cocoa勉強会発表資料OCHI Shuji
 
Android Lecture #01 @PRO&BSC Inc.
Android Lecture #01 @PRO&BSC Inc.Android Lecture #01 @PRO&BSC Inc.
Android Lecture #01 @PRO&BSC Inc.Yuki Higuchi
 
Titanium Mobile
Titanium MobileTitanium Mobile
Titanium MobileNaoya Ito
 
cocos2d-xにおけるBox2Dの利用方法および便利なツール
cocos2d-xにおけるBox2Dの利用方法および便利なツールcocos2d-xにおけるBox2Dの利用方法および便利なツール
cocos2d-xにおけるBox2Dの利用方法および便利なツールTomoaki Shimizu
 
cocos2d-xハンズオン勉強会 in 名古屋
cocos2d-xハンズオン勉強会 in 名古屋cocos2d-xハンズオン勉強会 in 名古屋
cocos2d-xハンズオン勉強会 in 名古屋Tomoaki Shimizu
 
Sencha touchのはじめかた
Sencha touchのはじめかたSencha touchのはじめかた
Sencha touchのはじめかたYuki Naotori
 
ボット開発でも DevOps! BotBuilder のテスト手法
ボット開発でも DevOps! BotBuilder のテスト手法ボット開発でも DevOps! BotBuilder のテスト手法
ボット開発でも DevOps! BotBuilder のテスト手法Kenichiro Nakamura
 
Inside of 聖徳玉子 by O2
Inside of 聖徳玉子 by O2Inside of 聖徳玉子 by O2
Inside of 聖徳玉子 by O2mganeko
 
jQuery/Html5/ASP.NET MVC 対応コンポーネントを用いたデバイス対応業務アプリケーション開発
jQuery/Html5/ASP.NET MVC 対応コンポーネントを用いたデバイス対応業務アプリケーション開発jQuery/Html5/ASP.NET MVC 対応コンポーネントを用いたデバイス対応業務アプリケーション開発
jQuery/Html5/ASP.NET MVC 対応コンポーネントを用いたデバイス対応業務アプリケーション開発Daizen Ikehara
 
レスポンシブWebデザイン【発展編】
レスポンシブWebデザイン【発展編】レスポンシブWebデザイン【発展編】
レスポンシブWebデザイン【発展編】Yasuhito Yabe
 
Cocos2d-x公開講座 in 鹿児島
Cocos2d-x公開講座 in 鹿児島Cocos2d-x公開講座 in 鹿児島
Cocos2d-x公開講座 in 鹿児島Tomoaki Shimizu
 
Xamarin 概要 2014年08月版
Xamarin 概要 2014年08月版Xamarin 概要 2014年08月版
Xamarin 概要 2014年08月版Yoshito Tabuchi
 

Similaire à FlutterをRenderObjectまで理解する (20)

Unityの夕べ in Fukuoka
Unityの夕べ in FukuokaUnityの夕べ in Fukuoka
Unityの夕べ in Fukuoka
 
Android gameprogramming
Android gameprogrammingAndroid gameprogramming
Android gameprogramming
 
iOSプログラマへ。HTML5 Canvasがおもしろい!
iOSプログラマへ。HTML5 Canvasがおもしろい!iOSプログラマへ。HTML5 Canvasがおもしろい!
iOSプログラマへ。HTML5 Canvasがおもしろい!
 
Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力
Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力
Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力
 
2013 Ignite UI 最新情報 in 岡山
2013 Ignite UI 最新情報 in 岡山2013 Ignite UI 最新情報 in 岡山
2013 Ignite UI 最新情報 in 岡山
 
2012 05-19第44回cocoa勉強会発表資料
2012 05-19第44回cocoa勉強会発表資料2012 05-19第44回cocoa勉強会発表資料
2012 05-19第44回cocoa勉強会発表資料
 
Android Lecture #01 @PRO&BSC Inc.
Android Lecture #01 @PRO&BSC Inc.Android Lecture #01 @PRO&BSC Inc.
Android Lecture #01 @PRO&BSC Inc.
 
Titanium Mobile
Titanium MobileTitanium Mobile
Titanium Mobile
 
実践 NestJS
実践 NestJS実践 NestJS
実践 NestJS
 
Titanium勉強会
Titanium勉強会Titanium勉強会
Titanium勉強会
 
cocos2d-xにおけるBox2Dの利用方法および便利なツール
cocos2d-xにおけるBox2Dの利用方法および便利なツールcocos2d-xにおけるBox2Dの利用方法および便利なツール
cocos2d-xにおけるBox2Dの利用方法および便利なツール
 
cocos2d-xハンズオン勉強会 in 名古屋
cocos2d-xハンズオン勉強会 in 名古屋cocos2d-xハンズオン勉強会 in 名古屋
cocos2d-xハンズオン勉強会 in 名古屋
 
Sencha touchのはじめかた
Sencha touchのはじめかたSencha touchのはじめかた
Sencha touchのはじめかた
 
ボット開発でも DevOps! BotBuilder のテスト手法
ボット開発でも DevOps! BotBuilder のテスト手法ボット開発でも DevOps! BotBuilder のテスト手法
ボット開発でも DevOps! BotBuilder のテスト手法
 
Inside of 聖徳玉子 by O2
Inside of 聖徳玉子 by O2Inside of 聖徳玉子 by O2
Inside of 聖徳玉子 by O2
 
jQuery/Html5/ASP.NET MVC 対応コンポーネントを用いたデバイス対応業務アプリケーション開発
jQuery/Html5/ASP.NET MVC 対応コンポーネントを用いたデバイス対応業務アプリケーション開発jQuery/Html5/ASP.NET MVC 対応コンポーネントを用いたデバイス対応業務アプリケーション開発
jQuery/Html5/ASP.NET MVC 対応コンポーネントを用いたデバイス対応業務アプリケーション開発
 
レスポンシブWebデザイン【発展編】
レスポンシブWebデザイン【発展編】レスポンシブWebデザイン【発展編】
レスポンシブWebデザイン【発展編】
 
MRTK-Unreal(UX Tools) を利用した HoloLens 2 アプリ開発 | UNREAL FEST EXTREME 2020 WINTER
MRTK-Unreal(UX Tools) を利用した HoloLens 2 アプリ開発 | UNREAL FEST EXTREME 2020 WINTERMRTK-Unreal(UX Tools) を利用した HoloLens 2 アプリ開発 | UNREAL FEST EXTREME 2020 WINTER
MRTK-Unreal(UX Tools) を利用した HoloLens 2 アプリ開発 | UNREAL FEST EXTREME 2020 WINTER
 
Cocos2d-x公開講座 in 鹿児島
Cocos2d-x公開講座 in 鹿児島Cocos2d-x公開講座 in 鹿児島
Cocos2d-x公開講座 in 鹿児島
 
Xamarin 概要 2014年08月版
Xamarin 概要 2014年08月版Xamarin 概要 2014年08月版
Xamarin 概要 2014年08月版
 

FlutterをRenderObjectまで理解する