[Flutter] ここが違う!Flutter vs ReactNative!JS vs Dart [RN]

ReactNative developerの皆さんこんにちは(^ ^)

そして、Flutter と ReactNative に興味を持つみさなん、ようこそ!

ReactNativeは2018年6月あたりのAirbnb撤退とfacebookが自社のアプリをReactNativeからネイティブへ移行するニュースがありましたねぇ〜😰

AirbnbがReact Nativeを諦めてネイティブアプリに方向転換したわけ – Roy S. Kim – Medium
一部の企業でReact Native離れが始まる – CIOニュース:CIO Magazine

しかし、個人のアプリ制作ならiosとandroid両方のネイティブアプリを作ることはしんどい😫 おそらく。。。

と言っても、どちらかのプラットフォームを選ぶのもなんか違う気がする。。。。

そんな時にリリースされたのが Flutter でした!

では、FlutterがReactNativeとここが違うと言っているところを見ていきましょー

(このページは流し見用として扱うことがおすすめ。めちゃ長いので。。。)

目次

React Native の開発者のためのFlutter

このページはここの翻訳です。

JavaScript開発者のためのDartの紹介

React Native(RN)と同様に、Flutterはリアクティブなスタイルビューを使用します。しかし、RNはネイティブウィジェットに移行しますが、Flutterはネイティブコードまでコンパイルされます。たとえば、RNのbuttonは、androidではandroid.widget.ButtonとiOSではUIButtonをレンダリングします。Flutterは画面上の各ピクセルを制御し、JavaScriptブリッジの必要性に起因するパフォーマンス上の問題を回避します。

Dartは学習するのが簡単な言語で、次の機能を提供します。

  • Web、サーバー、およびモバイルアプリを構築するための、オープンソースのスケーラブルなプログラミング言語を提供します。
  • ネイティブにAOTコンパイルされたC言語スタイルの構文を使用するオブジェクト指向の単一継承言語を提供します。
  • TranscompilesオプションでJavaScriptに変換します。
  • インタフェースと抽象クラスをサポートします。

JavaScriptとDartの違いのいくつかの例を以下に説明します。

エントリーポイント

JavaScriptにはあらかじめ定義されたエントリ機能がありません。エントリポイントを定義します。

// JavaScript
function startHere() {
  // Can be used as entry point
}

Dartでは、すべてのアプリに、アプリのエントリポイントとして機能するトップレベルのmain()関数が必要です。

// Dart
main() {
}

DartPadで試してみてください。

コンソールへの出力

Dartのコンソールに出力するには、printを使います。

// JavaScript
console.log("Hello world!");
// Dart
print('Hello world!');

DartPadで試してみてください。

変数

Dartは型システムです。これは、静的型検査とランタイム検査の組み合わせを使用して、変数の値が常に変数の静的型と一致することを保証します。型は必須ですが、Dartは型推論を実行するため、型の注釈はオプションです。

変数の作成と割り当て

JavaScriptでは、変数は型定義できません。

// JavaScript
var name = "JavaScript";

Dartでは、変数を明示的に型指定するか、型システムが適切な型を自動的に推論する必要があります。

// Dart
String name = 'dart'; //明示的に文字列として入力します。
var otherName = 'Dart'; // I文字列に推測される。
// どちらもOK

DartPadで試してみてください。

詳細については、Dart’s Type Systemを参照してください。

Default value

JavaScriptでは、未初期化変数はundefinedです。

// JavaScript
var name; // == undefined

Dartでは、初期化されていない変数の初期値はnullです。数値はDartのオブジェクトなので、数値型の初期化されていない変数でも値はnullです。

// Dart
var name; // == null
int x; // == null

DartPadで試してみてください。

詳細は、変数のドキュメントを参照してください。

null と 0 のチェック

JavaScriptでは、1またはnull以外のオブジェクトの値はtrueとして扱われます。

// JavaScript
var myNull = null;
if (!myNull) {
  console.log("null is treated as false");
}
var zero = 0;
if (!zero) {
  console.log("0 is treated as false");
}

Dartでは、boolean値trueだけがtrueとして扱われます。If文はbooleanのみで条件分岐します。

// Dart
var myNull = null;
if (myNull == null) {
  print('use "== null" to check null');
}
var zero = 0;
if (zero == 0) {
  print('use "== 0" to check zero');
}

DartPadで試してみてください。

Functions

Dart関数とJavaScript関数は一般的に似ています。主な違いは宣言です。

// JavaScript
function fn() {
  return true;
}
// Dart
fn() {
  return true;
}
// 次のように書くこともできます。
bool fn() {
  return true;
}

DartPadで試してください。

詳細については、関数のドキュメントを参照してください。

非同期プログラミング

非同期制御に関してはこの辺りの理解が必要になります。

JavaScriptの非同期処理を並列処理と勘違いしていませんか? – Qiita

Dartでの非同期処理(then vs async/await) – Qiita

Futures 

JavaScriptのように、Dartはシングルスレッドです。 JavaScriptでは、Promiseオブジェクトで、非同期完了(または失敗)の結果の値を表します。

// JavaScript
_getIPAddress = () => {
  const url="https://httpbin.org/ip";
  return fetch(url)
    .then(response => response.json())
    .then(responseJson => {
      console.log(responseJson.origin);
    })
    .catch(error => {
      console.error(error);
    });
};

Dartはこれを処理するためにFutureオブジェクトを使用します。

// Dart
_getIPAddress() {
  final url = 'https://httpbin.org/ip';
  HttpRequest.request(url).then((value) {
      print(json.decode(value.responseText)['origin']);
  }).catchError((error) => print(error));
}

DartPadで試してください。

詳細については、Futuresに関する文書を参照してください。

非同期と待機 async and await

async関数宣言は、非同期関数を定義します。

JavaScriptでは、async関数はPromiseを返します。 await演算子は、Promiseを待つために使用されます。

// JavaScript
async _getIPAddress() {
  const url="https://httpbin.org/ip";
  const response = await fetch(url);
  const json = await response.json();
  const data = await json.origin;
  console.log(data);
}

Dartでは、async関数はFutureを返し、関数の本体は後で実行するようにスケジュールされています。 await演算子は、Futureを待つために使用されます。

// Dart
_getIPAddress() async {
  final url = 'https://httpbin.org/ip';
  var request = await HttpRequest.request(url);
  String ip = json.decode(request.responseText)['origin'];
  print(ip);
}

DartPadで試してください。

詳細については、asyncとawaitのドキュメントを参照してください。

アプリ制作の基礎

プロジェクトを作成するには?

RN

create-react-native-app <projectname>

Flutter

flutter create <projectname>
  • コマンドラインからflutter createコマンドを使用します。 Flutter SDKがPATHにあることを確認してください。
  • FlutterとDartプラグインがインストールされたIDEを使用します。

詳細については、[Flutter] ずぼらアプリ開発者の味方!Flutter導入方法! [mac]を参照してください。 Flutterプロジェクトを作成すると、AndroidとiOSの両方のデバイスでサンプルアプリケーションを実行するために必要なすべてのファイルが作成されます。

アプリの実行は?

React Nativeでは、npm runまたはyarn runをプロジェクトディレクトリから実行します。

Flutterアプリケーションは、いくつかの方法で実行することができます。

  • プロジェクトのルートディレクトリから flutter run を実行します。
  • FlutterとDartプラグインを持つIDEで ‘run’オプションを使用します。

接続されたデバイス、iOSエミュレータ、またはAndroidシミュレータで動作します。

詳細については、[Flutter] ずぼらアプリ開発者の味方!Flutter導入方法! [mac]のドキュメントを参照してください。

ウィジェットをインポートするには?

React Nativeでは、必要なコンポーネントをそれぞれインポートする必要があります。

//React Native
import React from "react";
import { StyleSheet, Text, View } from "react-native";

Flutterで、Material Designライブラリのウィジェットを使用するには、material.dartパッケージをインポートします。 iOSスタイルのウィジェットを使用するには、Cupertinoライブラリをインポートします。より基本的なウィジェットセットを使用するには、ウィジェットライブラリをインポートします。または、独自のウィジェットライブラリを作成してインポートすることもできます。

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/my_widgets.dart';

どんなウィジェットパッケージをインポートしても、Dartはあなたのアプリで使われているウィジェットだけを取り込みます。

詳細については、Flutterウィジェットカタログを参照してください。

「Hello world!」アプリのソースコード比較

React Nativeでは、HelloWorldAppクラスはReact.Componentを拡張し、ビューコンポーネントを返すことによってrenderメソッドを実装します。

// React Native
import React from "react";
import { StyleSheet, Text, View } from "react-native";

export default class App extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Text>Hello world!</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center"
  }
});

Flutterでは、コアウィジェットライブラリのCenterウィジェットとTextウィジェットを使用して、同じ「Hello world!」アプリを作成できます。 Centerウィジェットは、ウィジェットツリーのルートになり、1つの子Text Widgetを持ちます。

// Flutter
import 'package:flutter/material.dart';

void main() {
  runApp(
    Center(
      child: Text(
        'Hello, world!',
        textDirection: TextDirection.ltr,
      ),
    ),
  );
}

ウィジェットツリーを形成するためにネストするには?

Flutterではほとんどすべてがウィジェットです。

ウィジェットは、アプリケーションのユーザーインターフェースの基本的なビルディングブロックです。ウィジェットをウィジェットツリーと呼ばれる階層に構成します。各ウィジェットは親ウィジェット内にネストし、その親からプロパティを継承します。

アプリケーションオブジェクト自体もウィジェットです。別の「アプリケーション」オブジェクトはありません。代わりに、ルートウィジェットがこの役割を果たします。

ウィジェットは以下を定義できます:

  • ボタンやメニューのような構造的要素
  • フォントや色のスタイルのような文体要素
  • レイアウトのようなパディングやアライメントの様相

次の例は、Materialライブラリのウィジェットを使用する「Hello world!」アプリを示しています。この例では、ウィジェットツリーはMaterialAppルートウィジェット内にネストされています。

// Flutter
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Welcome to Flutter',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Welcome to Flutter'),
        ),
        body: Center(
          child: Text('Hello world'),
        ),
      ),
    );
  }
}

以下の画像は、Material Designウィジェットから構築された ‘Hello world!’を示しています。

アプリケーションを書くときは、StatelessWidgetまたはStatefulWidgetという2種類のウィジェットを使います。StatelessWidgetは、状態のないウィジェットです。一度作成され、外観を変更しません。 StatefulWidgetは、受信したデータまたはユーザー入力に基づいて状態を動的に変更します。

StatelessWidgetとStatefulWidgetの重要な違いは、StatefulWidgetsにはステートデータが格納、保持され、ツリー再構築全体に渡ってStateオブジェクトがあることです。

単純なまたは基本的なアプリケーションでは、ウィジェットを入れ子にするのは簡単ですが、コードが大きくなりアプリケーションが複雑になります。入れ子になったウィジェットを深くネストせずに、ウィジェットまたはそれより小さなクラスを返す関数を別で用意することが望ましいです。機能とウィジェットを別々に作成することで、アプリ内のコンポーネントを再利用することもできます。

再利用可能なコンポーネントを作成するには?

React Nativeでは、再利用可能なコンポーネントを作成するクラスを定義し、次に、選択された要素のプロパティと値を設定または返すためにpropsメソッドを使用します。以下の例では、CustomCardクラスが定義され、親クラス内で使用されています。

// React Native
class CustomCard extends React.Component {
  render() {
    return (
      <View>
        <Text > Card {this.props.index} </Text>
        <Button
          title="Press"
          onPress={() => this.props.onPress(this.props.index)}
        />
      </View>
    );
  }
}

// Usage
<CustomCard onPress={this.onPress} index={item.key} />

Flutterで、クラスを定義してカスタムウィジェットを作成し、ウィジェットを再利用します。次の例のbuild関数に示すように、再利用可能なウィジェットを返す関数を定義して呼び出すこともできます。

// Flutter
class CustomCard extends StatelessWidget {
  CustomCard({@required this.index, @required 
     this.onPress});

  final index;
  final Function onPress;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Column(
        children: <Widget>[
          Text('Card $index'),
          FlatButton(
            child: const Text('Press'),
            onPressed: this.onPress,
          ),
        ],
      )
    );
  }
}
    ...
// Usage
CustomCard(
  index: index,
  onPress: () { 
    print('Card $index');
  },
)
    ...

CustomCardクラスのコンストラクタは、Dartの中括弧構文{}を使用して、名前付きオプションパラメータを示しています。これらのフィールドを必要とするには、コンストラクタから中括弧を削除するか、requiredをコンストラクタに追加します。

次のスクリーンショットは、再利用可能なCustomCardクラスの例を示しています。

プロジェクトの構造とリソース

どこでコードを書く?

main.dartファイルから始めます。 Flutterアプリを作成すると自動生成されます。

// Dart
void main(){
 print("Hello, this is the main function.");
}

Flutterでは、エントリポイントファイルは’projectname’/lib/main.dartで、main関数から実行が開始されます。

Flutterアプリケーションのファイルが構造化は?

新しいFlutterプロジェクトを作成すると、次のディレクトリ構造が構築されます。後でカスタマイズすることもできます。

┬
└ projectname
  ┬
  ├ android      -Android固有のファイルが含まれています。
  ├ build        -iOSとAndroidのビルドファイルを保存します。
  ├ ios          -iOS固有のファイルが含まれています。
  ├ lib          -外部からアクセス可能なDartソースファイルが含まれています。
    ┬
    └ src        -追加のソースファイルが含まれています。
    └ main.dart  -Flutterエントリポイントと新しいアプリケーションの開始。 これはFlutter プロジェクトを作成すると自        動的に生成されます。 これでダーツコードの作成が始まります。Dartコードを書く場所です。
  ├ test         -自動テストファイルが含まれています。
  └ pubspec.yaml -Flutterアプリケーションのメタデータを含みます。 これは、React Nativeのpackage.jsonファイルと同じです。

resourcesとassetsは、どのように使用?

Flutter resourcesまたはassetsは、アプリケーションにバンドルされて配備され、実行時にアクセス可能なファイルです。フラッターアプリには、以下のアセットタイプを含めることができます:

  • JSONファイルなどの静的データ
  • 設定ファイル
  • アイコンと画像(JPEG, PNG, GIF, Animated GIF, WebP, Animated WebP, BMP, and WBMP)

Flutterは、プロジェクトのルートにあるpubspec.yamlファイルを使用して、アプリケーションに必要なアセットを識別します。

flutter:
  assets:
    - assets/my_icon.png
    - assets/background.png

アセットサブセクションには、アプリケーションに含めるファイルを指定します。各アセットは、アセットファイルが配置されているpubspec.yamlファイルに対する明示的なパスによって識別されます。アセットが宣言される順序は関係ありません。

使用される実際のディレクトリ(この場合はアセット)は関係ありません。ただし、アセットは任意のアプリディレクトリに配置できますが、アセットディレクトリに配置することをお勧めします。

ビルド中、Flutterはアセットを、アセットバンドルと呼ばれる特別なアーカイブに入れます。アセットバンドルは、アプリケーションが実行時に読み込むものです。アセットのパスがpubspec.yamlのassetsセクションで指定されると、ビルドプロセスは隣接するサブディレクトリに同じ名前のファイルを探します。これらのファイルは、指定されたアセットと共にアセットバンドルにも含まれます。 Flutterは、アプリの解像度に適した画像を選択する際にアセットの種類を使用します。

React Nativeでは、イメージファイルをソースコードディレクトリに置いて参照することで、静的イメージを追加します。

<Image source={require("./my-icon.png")} />

Flutterでは、ウィジェットのビルドメソッドでAssetImageクラスを使用して静的イメージをアプリケーションに追加します。

image: AssetImage('assets/background.png'),

詳細については、Adding Assets and Images in Flutterを参照してください。

ネットワーク経由でイメージをロードするには?

React Nativeでは、Imageコンポーネントのソース・プロップにuriを指定し、必要に応じてサイズを指定します。
Flutterでは、Image.networkコンストラクタを使用して、URLの画像をインクルードします。

// Flutter
body: Image.network(
          'https://flutter.io/images/owl.jpg',

パッケージとパッケージプラグインをインストールするには?

他の開発者がFlutter and Dartエコシステムに寄贈した共有パッケージを使用したFlutterサポートがあります。これにより、最初からすべてを開発することなく、アプリケーションを素早く構築することができます。プラットフォーム固有のコードを含むパッケージは、パッケージプラグインと呼ばれます。

React Nativeでは、コマンドラインからパッケージをインストールするために、yarn add {package-name} or npm install --save {package-name}を使用します。

Flutterでは、次の手順を使用してパッケージをインストールします。

  1. パッケージ名とバージョンをpubspec.yamlのdependenciesセクションに追加します。次の例は、google_sign_in Dartパッケージをpubspec.yamlファイルに追加する方法を示しています。スペースが問題になることが多いため、YAMLファイルで作業するときにスペースに注意してください!
  2. dependencies:
      flutter:
        sdk: flutter
      google_sign_in: ^3.0.3
  3. flutter packages getを実行して、コマンドラインからパッケージをインストールします。 IDEを使用している場合は、flutter packages getが実行されることがよくあります。
  4. 以下に示すように、あなたのアプリケーションコードにパッケージをインポートしてください:
import 'package:flutter/cupertino.dart';

詳細については、パッケージの使用およびパッケージとプラグインの開発を参照してください。

Flutter開発者が共有する多くのパッケージは、パッケージサイトFlutter Packagesセクションにあります。

Flutter widgets

Flutterでは、ウィジェットのUIを構築し、現在の設定と状態を考慮して、そのビューをどのように表示するかを記述します。

ウィジェットは、しばしば強力な効果を生み出すためにネストされた、多くの小さな、単一目的のウィジェットで構成されています。たとえば、Container ウィジェットは、レイアウト、ペイント、位置付け、およびサイズ変更を担当するいくつかのウィジェットで構成されています。具体的には、ContainerウィジェットにはLimitedBox、ConstrainedBox、Align、Padding、DecoratedBox、およびTransformウィジェットが含まれています。カスタマイズされたエフェクトを生成するためにコンテナをサブクラス化するのではなく、これらのウィジェットや他のシンプルなウィジェットを新しくユニークな方法で作成できます。

センターウィジェットは、レイアウトを制御する方法のもう1つの例です。ウィジェットを中央揃えするには、それをCenterウィジェットにラップしてから、配置、行、列、およびグリッドにレイアウトウィジェットを使用します。これらのレイアウトウィジェットには独自のビジュアル表現がありません。代わりに、その唯一の目的は別のウィジェットのレイアウトのいくつかの側面を制御することです。ウィジェットが特定の方法でレンダリングする理由を理解するには、隣接するウィジェットを調べると便利です。

詳細については、Flutterの技術概要を参照してください。

Widgetsパッケージのコアウィジェットの詳細については、Flutter Basic WidgetsFlutter Widget Catalog、またはFlutter Widget Indexを参照してください。

Views

View containerと同等のものは?

React Nativeでは、ViewはFlexboxによるレイアウト、スタイル、タッチ操作、アクセシビリティコントロールをサポートするコンテナです。

Flutterでは、コンテナ、および中心などのWidgetsライブラリのコアレイアウトウィジェットを使用できます。

詳細はLayout Widgetsを参照してください。

FlatListまたはSectionListに相当するものは?

リストは、垂直に配置されたコンポーネントのスクロール可能なリストです。
React Nativeでは、FlatListまたはSectionListを使用して、シンプルリストまたはセクションリストを表示します。

// React Native
<FlatList
  data={[ ... ]}
  renderItem={({ item }) => <Text>{item.key}</Text>}
/>

ListViewはFlutterの最も一般的に使用されるスクロールウィジェットです。 ListViewは、少数のウィジェットに最適です。大規模なリストまたは無限のリストの場合は、ListView.builderを使用します。ListView.builderは、必要に応じて子を構築し、可視の子のみを構築します。

// Flutter
var data = [ ... ];
ListView.builder(
  itemCount: data.length,
  itemBuilder: (context, int index) {
    return Text(
      data[index],
    );
  },
)

無限のスクロールリストを実装する方法については、Write Your First Flutter App, Part 1を参照してください。

Canvasを使用して描画またはペイントするには?

React Nativeでは、キャンバスコンポーネントが存在しないため、react-native-canvasなどのサードパーティライブラリが使用されます。

// React Native
handleCanvas = canvas => {
  const ctx = canvas.getContext("2d");
  ctx.fillStyle = "skyblue";
  ctx.beginPath();
  ctx.arc(75, 75, 50, 0, 2 * Math.PI);
  ctx.fillRect(150, 100, 300, 300);
  ctx.stroke();
};

render() {
  return (
    <View>
      <Canvas ref={this.handleCanvas} />
    </View>
  );
}

Flutterでは、CustomPaintクラスとCustomPainterクラスを使用してキャンバスに描画できます。

次の例は、CustomPaintウィジェットを使用してペイントフェーズで描画する方法を示しています。これは、CustomPainterを実装し、それをCustomPaintのペインタプロパティに渡します。 CustomPaintサブクラスでは、paintメソッドとshouldRepaintメソッドを実装する必要があります。

// Flutter
class MyCanvasPainter extends CustomPainter {

  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint();
    paint.color = Colors.amber;
    canvas.drawCircle(Offset(100.0, 200.0), 40.0, paint);
    Paint paintRect = Paint();
    paintRect.color = Colors.lightBlue;
    Rect rect = Rect.fromPoints(Offset(150.0, 300.0), Offset(300.0, 400.0));
    canvas.drawRect(rect, paintRect);
  }

  bool shouldRepaint(MyCanvasPainter oldDelegate) => false;
  bool shouldRebuildSemantics(MyCanvasPainter oldDelegate) => false;
}
class _MyCanvasState extends State<MyCanvas> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomPaint(
        painter: MyCanvasPainter(),
      ),
    );
  }
}

Layouts

ウィジェットを使用してレイアウトプロパティを定義するには?

React Nativeでは、ほとんどのレイアウトは特定のコンポーネントに渡されるpropsで行うことができます。たとえば、Flexコンポーネントを指定するには、Viewコンポーネントのstyle propを使用します。コンポーネントを列に配置するには、flexDirection: ‘column’などの小道具を指定します。

// React Native
<View
  style={{
    flex: 1,
    flexDirection: "column",
    justifyContent: "space-between",
    alignItems: "center"
  }}
>

Flutterでは、レイアウトは主にレイアウトを提供するように設計されたウィジェットとコントロールウィジェットおよびそのスタイルプロパティによって定義されます。例えば、ColumnウィジェットとRowウィジェットは子の配列をとり、それぞれ縦と横に並べます。コンテナウィジェットはレイアウトとスタイルプロパティの組み合わせをとり、センターウィジェットはその子ウィジェットを中央に配置します。

// Flutter
Center(
  child: Column(
    children: <Widget>[
      Container(
        color: Colors.red,
        width: 100.0,
        height: 100.0,
      ),
      Container(
        color: Colors.blue,
        width: 100.0,
        height: 100.0,
      ),
      Container(
        color: Colors.green,
        width: 100.0,
        height: 100.0,
      ),
    ],
  ),
)

Flutterは、コアウィジェットライブラリにさまざまなレイアウトウィジェットを提供します。たとえば、PaddingAlignStackなどです。

完全なリストについては、レイアウトウィジェットを参照してください。

どのようにウィジェットをレイヤーするの?

React Nativeでは、absoluteを使用してコンポーネントを重ねることができます。
Flutterはスタックウィジェットを使用して子ウィジェットをレイヤーに配置します。ウィジェットは、ベースウィジェット全体または一部をオーバーラップさせることができます。

Stackウィジェットは、ボックスの端に相対的に子を配置します。このクラスは、複数の子ウィジェットを重複させたい場合に便利です。

// Flutter
Stack(
  alignment: const Alignment(0.6, 0.6),
  children: <Widget>[
    CircleAvatar(
      backgroundImage: NetworkImage(
        "https://avatars3.githubusercontent.com/u/14101776?v=4"),
    ),
    Container(
      decoration: BoxDecoration(
          color: Colors.black45,
      ),
      child: Text('Flutter'),
    ),
  ],
)

CircleAvatarの上にStackを使用してコンテナ(半透明の黒い背景にテキストを表示)をオーバーレイしました。 Stackは、alignmentプロパティとAlignment座標を使用してテキストをオフセットします。

詳細については、Stackクラスのドキュメントを参照してください。

Styling

コンポーネントのスタイルを設定するには?

React Nativeでは、インライン・スタイリングとstylesheets.createを使用してコンポーネントをスタイルします。

// React Native
<View style={styles.container}>
  <Text style={{ fontSize: 32, color: "cyan", fontWeight: "600" }}>
    This is a sample text
  </Text>
</View>

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center"
  }
});

Flutterでは、TextウィジェットはstyleプロパティのTextStyleクラスを取ります。複数の場所で同じテキストスタイルを使用する場合は、TextStyleクラスを作成して複数のテキストウィジェットに使用できます。

// Flutter
var textStyle = TextStyle(fontSize: 32.0, color: Colors.cyan, fontWeight:
   FontWeight.w600);
  ...
Center(
  child: Column(
    children: <Widget>[
      Text(
        'Sample text',
        style: textStyle,
      ),
      Padding(
        padding: EdgeInsets.all(20.0),
        child: Icon(Icons.lightbulb_outline,
          size: 48.0, color: Colors.redAccent)
      ),
    ],
  ),
)

どのようにアイコンや色を使用しますか?

React Nativeにはアイコンのサポートが含まれていないため、サードパーティライブラリが使用されます。

Flutterでは、マテリアルライブラリをインポートすると、マテリアルアイコンの豊富なセットが取り込まれます。

Icon(Icons.lightbulb_outline, color: Colors.redAccent)

Iconsクラスを使用する場合は、プロジェクトのpubspec.yamlファイルでuses-material-design:trueを設定してください。これにより、アイコンを表示するMaterialIconsフォントがアプリに含まれていることが保証されます。

name: my_awesome_application
flutter: uses-material-design: true

FlutterのCupertino(iOSスタイル)パッケージは、現行のiOS用の高品質ウィジェットを提供します。 CupertinoIconsフォントを使用するには、プロジェクトのpubspec.yamlファイルにcupertino_iconsの依存関係を追加します。

name: my_awesome_application
dependencies:
  cupertino_icons: ^0.1.0

コンポーネントの色とスタイルをグローバルにカスタマイズするには、ThemeDataを使用してテーマのさまざまな側面のデフォルトの色を指定します。 MaterialAppのテーマプロパティをThemeDataオブジェクトに設定します。 Colorsクラスは、Material Designカラーパレットの色を提供します。

次の例では、主スウォッチを青に設定し、テキスト選択を赤に設定しています。

class SampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        textSelectionColor: Colors.red
      ),
      home: SampleAppPage(),
    );
  }
}

スタイルテーマを追加するには?

React Nativeでは、共通のテーマがスタイルシートのコンポーネントに対して定義され、コンポーネントで使用されます。

Flutterでは、ThemeDataクラスのスタイリングを定義し、それをMaterialAppウィジェットのテーマプロパティに渡すことで、ほぼすべてのスタイルの均一なスタイルを作成できます。

@override
Widget build(BuildContext context) {
  return MaterialApp(
    theme: ThemeData(
      primaryColor: Colors.cyan,
      brightness: Brightness.dark,
    ),
    home: StylingPage(),
  );
}

ThemeはMaterialAppウィジェットを使わなくても適用できます。 Themeウィジェットは、そのdataパラメータでThemeDataを受け取り、すべての子ウィジェットにThemeDataを適用します。

@override
Widget build(BuildContext context) {
  return Theme(
    data: ThemeData(
      primaryColor: Colors.cyan,
      brightness: brightness,
    ),
    child: Scaffold(
       backgroundColor: Theme.of(context).primaryColor,
            ...
            ...
    ),
  );
}

状態管理

状態とは、ウィジェットが構築されたときに同期して読み取られる情報、またはウィジェットの存続期間中に変更される可能性のある情報のことです。

Flutterでアプリの状態を管理するには、StateオブジェクトとペアになっているStatefulWidgetを使用します。

StatelessWidget

FlutterのStatelessWidgetは、状態の変更を必要としないウィジェットです。管理する内部状態はありません。

StatelessWidgetは、記述しているユーザーインターフェースの一部が、オブジェクト自体の構成情報やウィジェットが拡張されたBuildContext以外のものに依存していない場合に便利です。

AboutDialogCircleAvatar、およびTextStatelessWidgetをサブクラス化するStatelessWidgetの例です。

// Flutter
import 'package:flutter/material.dart';

void main() => runApp(MyStatelessWidget(text: "StatelessWidget Example to show immutable data"));

class MyStatelessWidget extends StatelessWidget {
  final String text;
  MyStatelessWidget({Key key, this.text}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(
        text,
        textDirection: TextDirection.ltr,
      ),
    );
  }
}

MyStatelessWidgetクラスのコンストラクタを使用して、finalとして宣言されているtextを渡しました。このクラスはStatelessWidgetを拡張します。これは不変のデータを含みます。

ステートレスウィジェットのビルドメソッドは、通常、次の3つの状況で呼び出されます:

  • ウィジェットがツリーに挿入されるとき
  • ウィジェットの親がその設定を変更するとき
  • それが依存するInheritedWidgetを変更するとき

StatefulWidget

StatefulWidgetは状態を変更するウィジェットです。 StatefulWidgetの状態変更を管理するには、setStateメソッドを使用します。 setStateを呼び出すとFlutterフレームワークに状態が変化したことが通知され、アプリケーションが変更メソッドを再実行してアプリケーションがその変更を反映できるようになります。

状態とは、ウィジェットが構築されたときに同期して読み取られる情報であり、ウィジェットの存続期間中に変更される可能性がある情報です。ウィジェットの実装者は、状態が変化したときに状態が即座に通知されるようにする責任があります。ウィジェットが動的に変更できる場合は、StatefulWidgetを使用してください。たとえば、フォームに入力するか、スライダを動かすことによって、ウィジェットの状態が変わります。または、時間とともに変化する可能性があります。データフィードによってUIが更新される場合があります。

CheckboxRadioSliderInkWellForm,およびTextFieldStatefulWidgetのサブクラスであるstateful widgetsの例です。

次の例では、createState() メソッドを必要とするStatefulWidgetを宣言しています。このメソッドは、ウィジェットの状態_MyStatefulWidgetStateを管理する状態オブジェクトを作成します。

class MyStatefulWidget extends StatefulWidget {
  MyStatefulWidget({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}

状態クラス_MyStatefulWidgetStateは、ウィジェットのbuild() メソッドを実装します。状態が変化すると、例えば、ユーザがボタンをトグルすると、新しいトグル値でsetStateが呼び出されます。これにより、フレームワークはUI内でこのウィジェットを再構築します。

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  bool showtext=true;
  bool toggleState=true;
  Timer t2;

  void toggleBlinkState(){
    setState((){
      toggleState=!toggleState;
    });
    var twenty = const Duration(milliseconds: 1000);
    if(toggleState==false) {
      t2 = Timer.periodic(twenty, (Timer t) {
        toggleShowText();
      });
    } else {
      t2.cancel();
    }
  }

  void toggleShowText(){
    setState((){
      showtext=!showtext;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: <Widget>[
            (showtext
              ?(Text('This execution will be done before you can blink.'))
              :(Container())
            ),
            Padding(
              padding: EdgeInsets.only(top: 70.0),
              child: RaisedButton(
                onPressed: toggleBlinkState,
                child: (toggleState
                  ?( Text('Blink'))
                  :(Text('Stop Blinking'))
                )
              )
            )
          ],
        ),
      ),
    );
  }
}

StatefulWidgetとStatelessWidgetのベストプラクティスは?

ウィジェットのデザイン時に考慮すべき点をいくつか挙げておきます。

  1. ウィジェットがStatefulWidgetかStatelessWidgetかを決定する
    • Flutterでは、状態変化に依存するかどうかによって、ウィジェットはステートフルかステートレスのいずれかになります。
    • ウィジェットが変更された場合(ユーザーがそのウィジェットとやりとりしたり、データフィードがUIを中断した場合)、ステートフルです。
    • ウィジェットが最後か不変の場合、それはステートレスです。
  2. どのオブジェクトがウィジェットの状態を管理しているか(StatefulWidgetの場合)
    • Flutterには、状態を管理する主な3つの方法があります:
      • ウィジェットは独自の状態を管理します
      • 親ウィジェットはウィジェットの状態を管理します
      • ミックス・アンド・マッチのアプローチ
    • どのアプローチを使用するかを決定する際には、以下の原則を考慮してください:
      • 問題の状態がチェックボックスのチェックまたはチェックされていないモードやスライダの位置などのユーザデータである場合、状態は親によって最良に管理されますウィジェット。
      • 問題の状態がデザイン的なもの、例えばアニメーションである場合、ウィジェット自体は状態を最もよく管理する。
      • 疑問がある場合は、親ウィジェットが子ウィジェットの状態を管理するようにします。
  3. StatefulWidgetとStateをサブクラス化する
    • MyStatefulWidgetクラスは独自の状態を管理します。StatefulWidgetを拡張し、createState() メソッドをオーバーライドしてStateオブジェクトを作成し、フレームワークがcreateState() を呼び出してウィジェットを構築します。この例では、createState() は、次のベストプラクティスで実装される_MyStatefulWidgetStateのインスタンスを作成します。
    • class MyStatefulWidget extends StatefulWidget {
        MyStatefulWidget({Key key, this.title}) : super(key: key);
        final String title;
      
        @override
        _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
      }
      
      class _MyStatefulWidgetState extends State<MyStatefulWidget> {
      
        @override
        Widget build(BuildContext context) {
          ...
        }
      }
      
  4. StatefulWidgetをウィジェットツリーに追加する
    • カスタムのStatefulWidgetを、アプリケーションのビルドメソッドのウィジェットツリーに追加します。
    • class MyStatelessWidget extends StatelessWidget {
        // This widget is the root of your application.
      
        @override
        Widget build(BuildContext context) {
          return MaterialApp(
            title: 'Flutter Demo',
            theme: ThemeData(
              primarySwatch: Colors.blue,
            ),
            home: MyStatefulWidget(title: 'State Change Demo'),
          );
        }
      }

Props

React Nativeでは、ほとんどのコンポーネントを、異なるパラメータまたはプロパティ(propsと呼ばれます)で作成するときにカスタマイズすることができます。これらのパラメータは、this.propsを使用して子コンポーネントで使用できます。

// React Native
class CustomCard extends React.Component {
  render() {
    return (
      <View>
        <Text> Card {this.props.index} </Text>
        <Button
          title="Press"
          onPress={() => this.props.onPress(this.props.index)}
        />
      </View>
    );
  }
}
class App extends React.Component {

  onPress = index => {
    console.log("Card ", index);
  };

  render() {
    return (
      <View>
        <FlatList
          data={[ ... ]}
          renderItem={({ item }) => (
            <CustomCard onPress={this.onPress} index={item.key} />
          )}
        />
      </View>
    );
  }
}

Flutterでは、finalと宣言されたローカル変数または関数に、パラメータ化されたコンストラクタで受け取ったプロパティを割り当てます。

// Flutter
class CustomCard extends StatelessWidget {

  CustomCard({@required this.index, @required this.onPress});
  final index;
  final Function onPress;

  @override
  Widget build(BuildContext context) {
  return Card(
    child: Column(
      children: <Widget>[
        Text('Card $index'),
        FlatButton(
          child: const Text('Press'),
          onPressed: this.onPress,
        ),
      ],
    ));
  }
}
    ...
//Usage
CustomCard(
  index: index,
  onPress: () {
    print('Card $index');
  },
)

ローカルストレージ

大量のデータを格納する必要がなく、構造体を必要としない場合は、shared_preferencesを使用して、boolean、float、ints、longs、およびprimitiveデータ型の永続的なキーと値のペアを読み書きできます。

アプリにグローバルな永続的なKey-Valueペアを保存するには?

React Nativeでは、AsyncStorageコンポーネントのsetItem関数とgetItem関数を使用して、アプリケーションに永続的かつグローバルなデータを格納および取得します。

// React Native
await AsyncStorage.setItem( "counterkey", json.stringify(++this.state.counter));
AsyncStorage.getItem("counterkey").then(value => {
  if (value != null) {
    this.setState({ counter: value });
  }
});

Flutterでは、shared_preferencesプラグインを使用して、アプリケーションに永続的でグローバルなKey-Valueデータを格納し、取得できます。 shared_preferencesプラグインは、iOS上のNSUserDefaultsとAndroid上のSharedPreferencesをラップし、単純なデータ用の永続ストアを提供します。プラグインを使用するには、pubspec.yamlファイルにshared_preferencesを依存関係として追加し、Dartファイルにパッケージをインポートします。

dependencies:
  flutter:
    sdk: flutter
  shared_preferences: ^0.3.0
// Dart
import 'package:shared_preferences/shared_preferences.dart';

永続データを実装するには、SharedPreferencesクラスによって提供されるsetterメソッドを使用します。 setterメソッドは、setInt、setBool、setStringなどのさまざまなプリミティブ型で使用できます。データを読み取るには、SharedPreferencesクラスが提供する適切なゲッターメソッドを使用します。各セッターには、対応するgetterメソッド(getInt、getBool、getStringなど)があります。

SharedPreferences prefs = await SharedPreferences.getInstance();
_counter = prefs.getInt('counter');
prefs.setInt('counter', ++_counter);
setState(() {
  _counter = _counter;
});

Routing

ほとんどのアプリには、さまざまな種類の情報を表示するための複数の画面が含まれています。たとえば、ユーザーが製品イメージをタップして新しい画面で製品に関する詳細情報を入手できるようにしたイメージを表示する製品画面があるとします。

Androidでは、新しい画面が新しいアクティビティです。 iOSでは、新しい画面は新しいViewControllerです。 Flutterでは、スクリーンはウィジェットです! Flutterの新しい画面に移動するには、Navigatorウィジェットを使用します。

画面間をナビゲートするには?

React Nativeには、StackNavigator、TabNavigator、DrawerNavigatorという3つのメインナビゲータがあります。それぞれは、画面の構成と定義方法を提供します。

// React Native
const MyApp = TabNavigator(
  { Home: { screen: HomeScreen }, Notifications: { screen: tabNavScreen } },
  { tabBarOptions: { activeTintColor: "#e91e63" } }
);
const SimpleApp = StackNavigator({
  Home: { screen: MyApp },
  stackScreen: { screen: StackScreen }
});
export default (MyApp1 = DrawerNavigator({
  Home: {
    screen: SimpleApp
  },
  Screen2: {
    screen: drawerScreen
  }
}));

Flutterには、画面間を移動するために使用される2つの主要なウィジェットがあります。

  • ルートは、アプリの画面やページの抽象です。
  • Navigatorは経路を管理するウィジェットです。

ナビゲーターは、スタックで子ウィジェットのセットを管理するウィジェットとして定義されています。ナビゲータはRouteオブジェクトのスタックを管理し、Navigator.pushNavigator.popなどのスタックを管理するためのメソッドを提供します。ルートのリストは、MaterialAppウィジェットで指定することも、ヒーローアニメーションのようにオンザフライで構築することもできます。次の例では、MaterialAppウィジェットで名前付きルートを指定します。

// Flutter
class NavigationApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
            ...
      routes: <String, WidgetBuilder>{
        '/a': (BuildContext context) => usualNavscreen(),
        '/b': (BuildContext context) => drawerNavscreen(),
      }
            ...
  );
  }
}

名前付きルートにナビゲートするには、Navigatorウィジェットのofメソッドを使用してBuildContext(ウィジェットツリー内のウィジェットの位置へのハンドル)を指定します。ルートの名前は、指定されたルートに移動するためにpushNamed関数に渡されます。

Navigator.of(context).pushNamed('/a');

また、Navigatorのpushメソッドを使用して、指定されたコンテキストを最も密接に囲むナビゲータの履歴に与えられたルートを追加し、そのコンテキストに遷移することもできます。次の例では、MaterialPageRouteウィジェットは、画面全体をプラットフォーム適応遷移に置き換えるモーダルルートです。必須パラメータとしてWidgetBuilderが必要です。

Navigator.push(context, MaterialPageRoute(builder: (BuildContext context)
 => UsualNavscreen()));

タブナビゲーションとドロワーナビゲーションを使用するには?

マテリアルデザインアプリには、tabsとdrawersの2つの主要なフラッターナビゲーションオプションがあります。タブをサポートするスペースが不十分な場合は、drawersが適しています。

タブナビゲーション

React Nativeでは、createBottomTabNavigatorとTabNavigationは、タブの表示とタブのナビゲーションに使用されます。

// React Native
import { createBottomTabNavigator } from 'react-navigation';

const MyApp = TabNavigator(
  { Home: { screen: HomeScreen }, Notifications: { screen: tabNavScreen } },
  { tabBarOptions: { activeTintColor: "#e91e63" } }
);

Flutterは、drawersやタブナビゲーション用のいくつかの特殊なウィジェットを提供します。

  • TabController – TabBarとTabBarViewの間のタブ選択を調整します。
  • TabBar – タブの水平行を表示します。
  • Tab:マテリアルデザインのTabBarタブを作成します。
  • TabBarView – 現在選択されているタブに対応するウィジェットを表示します。
// Flutter
TabController controller=TabController(length: 2, vsync: this);

TabBar(
  tabs: <Tab>[
    Tab(icon: Icon(Icons.person),),
    Tab(icon: Icon(Icons.email),),
  ],
  controller: controller,
),

TabBarとTabBarViewの間のタブ選択を調整するには、TabControllerが必要です。 TabControllerコンストラクタのlength引数は、タブの総数です。 TickerProviderは、フレームが状態変更をトリガするたびに通知をトリガする必要があります。 TickerProviderはvsyncです。新しいTabControllerを作成するたびに、vsync:この引数をTabControllerコンストラクタに渡します。

TickerProviderは、Tickerオブジェクトを提供できるクラスによって実装されたインターフェイスです。Tickerは、フレームがトリガされるたびに通知される必要があるオブジェクトであれば使用できますが、AnimationControllerを介して間接的に使用されるのが最も一般的です。 AnimationControllerにはTickerを取得するためにTickerProviderが必要です。 StateからAnimationControllerを作成する場合は、TickerProviderStateMixinまたはSingleTickerProviderStateMixinクラスを使用して適切なTickerProviderを取得できます。

Scaffoldウィジェットは、新しいTabBarウィジェットをラップし、2つのタブを作成します。 TabBarViewウィジェットはScaffoldウィジェットのbodyパラメーターとして渡されます。 TabBarウィジェットのタブに対応するすべての画面は、同じTabControllerとともにTabBarViewウィジェットの子です。

// Flutter

class _NavigationHomePageState extends State<NavigationHomePage> with SingleTickerProviderStateMixin {
  TabController controller=TabController(length: 2, vsync: this);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: Material (
        child: TabBar(
          tabs: <Tab> [
            Tab(icon: Icon(Icons.person),)
            Tab(icon: Icon(Icons.email),),
          ],
          controller: controller,
        ),
        color: Colors.blue,
      ),
      body: TabBarView(
        children: <Widget> [
          home.homeScreen(),
          tabScreen.tabScreen()
        ],
        controller: controller,
      )
    );
  }
}
drawersのナビゲーション

React Nativeでは、必要なreact-navigationパッケージをインポートしてから、createDrawerNavigatorとDrawerNavigationを使用します。

// React Native
export default (MyApp1 = DrawerNavigator({
  Home: {
    screen: SimpleApp
  },
  Screen2: {
    screen: drawerScreen
  }
}));

Flutterでは、DrawerウィジェットをScaffoldと組み合わせて使用​​して、Material Designドロワーを使用してレイアウトを作成することができます。 Drawerをアプリに追加するには、Scaffoldウィジェットでラップします。足場ウィジェットは、Material Designガイドラインに従ったアプリケーションに一貫したビジュアル構造を提供します。 Drawer、AppBars、SnackBarsなどの特殊なMaterial Designコンポーネントもサポートしています。

Drawerウィジェットは、端から水平にスライドしてアプリケーションのナビゲーションリンクを表示するMaterial Designパネルです。あなたは、ボタンテキストウィジェット、またはDrawerウィジェットの子として表示する項目のリストを提供することができます。次の例では、ListTileウィジェットはナビゲーションをタップします。

// Flutter
Drawer(
  child:ListTile(
    leading: Icon(Icons.change_history),
    title: Text('Screen2'),
    onTap: () {
      Navigator.of(context).pushNamed("/b");
    },
  ),
  elevation: 20.0,
),

ScaffoldウィジェットにはAppBarウィジェットも含まれています。このAppBarウィジェットは、ScaffoldでDrawerが利用可能になったときにDrawerを表示するための適切なIconButtonを自動的に表示します。Scaffoldは自動的にエッジスワイプジェスチャーを処理して、Drawerを表示します。

// Flutter
@override
Widget build(BuildContext context) {
  return Scaffold(
    drawer: Drawer(
      child: ListTile(
        leading: Icon(Icons.change_history),
        title: Text('Screen2'),
        onTap: () {
          Navigator.of(context).pushNamed("/b");
        },
      ),
      elevation: 20.0,
    ),
    appBar: AppBar(
      title: Text("Home"),
    ),
    body: Container(),
  );
}

ジェスチャ検出とタッチイベント処理

ジェスチャーをリッスンし、任意の処理を行うことができます。Flutterはタップ、ドラッグ、スケーリングをサポートしています。 Flutterのジェスチャーシステムには2つのレイヤーがあります。最初のレイヤーには、ポインター(タッチ、マウス、およびスタイラスの動きなど)の位置と移動を認識する画面上のポインタのイベントが含まれています。第2の層は、1つ以上のポインタ移動からなるアクションを記述するジェスチャを含みます。

クリックやタップをウィジェットに追加するには?

React Nativeでは、リスナーはPanResponderまたはTouchableコンポーネントを使用してコンポーネントに追加されます。

// React Native
<TouchableOpacity
  onPress={() => {
    console.log("Press");
  }}
  onLongPress={() => {
    console.log("Long Press");
  }}
>
  <Text>Tap or Long Press</Text>
</TouchableOpacity>

より複雑なジェスチャーと複数のタッチを組み合わせて単一のジェスチャーにするには、PanResponderが使用されます。

// React Native
class App extends Component {

  componentWillMount() {
    this._panResponder = PanResponder.create({
      onMoveShouldSetPanResponder: (event, gestureState) =>
        !!getDirection(gestureState),
      onPanResponderMove: (event, gestureState) => true,
      onPanResponderRelease: (event, gestureState) => {
        const drag = getDirection(gestureState);
      },
      onPanResponderTerminationRequest: (event, gestureState) => true
    });
  }

  render() {
    return (
      <View style={styles.container} {...this._panResponder.panHandlers}>
        <View style={styles.center}>
          <Text>Swipe Horizontally or Vertically</Text>
        </View>
      </View>
    );
  }
}

Flutterでは、クリック(または押す)リスナーをウィジェットに追加するには、ボタンまたはonPress:フィールドを持つタッチ可能なウィジェットを使用します。または、GestureDetectorでラップすることで、任意のウィジェットにジェスチャ検出を追加できます。

// Flutter
GestureDetector(
  child: Scaffold(
    appBar: AppBar(
      title: Text("Gestures"),
    ),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('Tap, Long Press, Swipe Horizontally or Vertically '),
        ],
      )
    ),
  ),
  onTap: () {
    print('Tapped');
  },
  onLongPress: () {
    print('Long Pressed');
  },
  onVerticalDragEnd: (DragEndDetails value) {
    print('Swiped Vertically');
  },
  onHorizontalDragEnd: (DragEndDetails value) {
    print('Swiped Horizontally');
  },
);

HTTPネットワークリクエストの作成

ほとんどのアプリでは、インターネットからデータを取得するのが一般的です。 Flutterでは、httpパッケージを使用してインターネットからデータを取得する最も簡単な方法を提供しています。

API呼び出しからデータを取得するには?

React Nativeは、ネットワーキング用のFetch APIを提供します。フェッチ要求を行い、データを取得するための応答を受信します。

// React Native
_getIPAddress = () => {
  fetch("https://httpbin.org/ip")
    .then(response => response.json())
    .then(responseJson => {
      this.setState({ _ipAddress: responseJson.origin });
    })
    .catch(error => {
      console.error(error);
    });
};

Flutterはhttpパッケージを使用します。 httpパッケージをインストールするには、pubspec.yamlの依存関係セクションに追加します。

dependencies:
  flutter:
    sdk: flutter
  http: <latest_version>

Flutterは、dart:ioコアのHTTPサポートクライアントを使用します。 HTTPクライアントを作成するには、dart:ioをインポートします。

import 'dart:io';

クライアントは、GET、POST、PUT、およびDELETEのHTTP操作をサポートしています。

// Flutter
final url = Uri.https('httpbin.org', 'ip');
final httpClient = HttpClient();
_getIPAddress() async {
  var request = await httpClient.getUrl(url);
  var response = await request.close();
  var responseBody = await response.transform(utf8.decoder).join();
  String ip = json.decode(responseBody)['origin'];
  setState(() {
    _ipAddress = ip;
  });
}

フォーム入力

テキストフィールドを使用すると、ユーザーはアプリケーションにテキストを入力できるので、フォーム、メッセージングアプリケーション、検索エクスペリエンスなどの作成に使用できます。 Flutterは、TextFieldTextFormFieldという2つのテキストフィールドウィジェットを提供します。

テキストフィールドウィジェットを使用するには?

React Nativeでは、テキストを入力するためにTextInputコンポーネントを使用してテキスト入力ボックスを表示し、コールバックを使用して値を変数に格納します。

// React Native
<TextInput
  placeholder="Enter your Password"
  onChangeText={password => this.setState({ password })}
 />
<Button title="Submit" onPress={this.validate} />

Flutterでは、TextEditingControllerクラスを使用してTextFieldウィジェットを管理します。テキストフィールドが変更されると、コントローラはリスナに通知します。

リスナーは、テキストと選択プロパティを読み取って、ユーザーがフィールドに入力した内容を確認します。 TextFieldのテキストには、コントローラのtextプロパティでアクセスできます。

// Flutter
final TextEditingController _controller = TextEditingController();
      ...
TextField(
  controller: _controller,
  decoration: InputDecoration(
    hintText: 'Type something', labelText: "Text Field "
  ),
),
RaisedButton(
  child: Text('Submit'),
  onPressed: () {
    showDialog(
      context: context,
        child: AlertDialog(
          title: Text('Alert'),
          content: Text('You typed ${_controller.text}'),
        ),
     );
   },
 ),
)

この例では、ユーザーが送信ボタンをクリックすると、テキストフィールドに入力された現在のテキストがアラートダイアログに表示されます。これは、警告メッセージを表示するalertDialogウィジェットを使用して実現され、TextFieldのテキストはTextEditingControllerのtextプロパティによってアクセスされます。

フォームウィジェットはどのように使用?

Flutterで、TextFormFieldウィジェットと送信ボタンを子として渡すFormウィジェットを使用します。 TextFormFieldウィジェットにはonSavedというパラメータがあり、コールバックを受け取り、フォームが保存されるときに実行されます。 FormStateオブジェクトは、このフォームの子孫である各FormFieldを保存、リセット、または検証するために使用されます。 FormStateを取得するには、親がFormであるコンテキストでForm.ofを使用するか、FormKeyコンストラクターにGlobalKeyを渡してGlobalKey.currentStateを呼び出します。

final formKey = GlobalKey<FormState>();

...

Form(
  key:formKey,
  child: Column(
    children: <Widget>[
      TextFormField(
        validator: (value) => !value.contains('@') ? 'Not a valid email.' : null,
        onSaved: (val) => _email = val,
        decoration: const InputDecoration(
          hintText: 'Enter your email',
          labelText: 'Email',
        ),
      ),
      RaisedButton(
        onPressed: _submit,
        child: Text('Login'),
      ),
    ],
  ),
)

次の例は、Form.save() およびformKey(GlobalKey)がsubmit時にフォームを保存するために使用される方法を示しています。

void _submit() {
  final form = formKey.currentState;
  if (form.validate()) {
    form.save();
    showDialog(
      context: context,
      child: AlertDialog(
        title: Text('Alert'),
        content: Text('Email: $_email, password: $_password'),
      )
    );
  }
}

プラットフォーム固有のコード

クロスプラットフォームアプリケーションを構築するときは、プラットフォーム間でできるだけ多くのコードを再利用したいと考えています。ただし、OSによってコードが異なることが理にかなっているシナリオが発生することがあります。これには、特定のプラットフォームを宣言して別個の実装が必要です。

React Nativeでは、次の実装が使用されます:

// React Native
if (Platform.OS === "ios") {
  return "iOS";
} else if (Platform.OS === "android") {
  return "android";
} else {
  return "not recognised";
}

Flutterでは、次の実装を使用します。

// Flutter
if (Theme.of(context).platform == TargetPlatform.iOS) {
  return "iOS";
} else if (Theme.of(context).platform == TargetPlatform.android) {
  return "android";
} else if (Theme.of(context).platform == TargetPlatform.fuchsia) {
  return "fuchsia";
} else {
  return "not recognised ";
}

Debugging

アプリケーションを実行する前に、flutter analyzeでコードを検証してください。 Flutterアナライザ(dartanalyzerツールのラッパー)は、コードを調べ、問題の可能性を特定するのに役立ちます。 Flutter対応のIDEを使用している場合、これは自動的に発生します。

アプリ内デベロッパーメニューにアクセスするには?

React Nativeでは、デバイスごとに振られるリンクにブラウザでアクセスし、開発者のメニューを見れます.iOS Simulatorの場合は⌘D、Androidエミュレータの場合は⌘Mです。

Flutterでは、IDEを使用している場合は、IDEツールを使用できます。 flutter runを使用してアプリケーションを起動する場合は、ターミナルウィンドウにhと入力するか、次のショートカットを入力してメニューにアクセスすることもできます。

Action Terminal Shortcut デバッグ関数とプロパティ
アプリのウィジェット階層 w debugDumpApp()
アプリのレンダリングツリー t debugDumpRenderTree()
Layers L debugDumpLayerTree()
Accessibility S (traversal order) or
U (inverse hit test order)
debugDumpSemantics()
ウィジェットインスペクタを切替 i WidgetsApp. showWidgetInspectorOverride
構成行の表示を切替 p debugPaintSizeEnabled
異なるオペレーティングシステムをシミュレート o defaultTargetPlatform
パフォーマンスオーバーレイを表示 P WidgetsApp. showPerformanceOverlay
スクリーンショットを保存する(PNG) s
やめる q

ホットリロードを実行するには?

Flutterのホットリロード機能は、迅速かつ簡単に実験、UIの構築、機能の追加、およびバグの修正を支援します。ソースを変更するたびにアプリケーションを再コンパイルし、即座にホットリロードすることができます。アプリはあなたの変更を反映するために更新し、そしてアプリの現在の状態は保存されます。

React Nativeでは、ショートカットはiOSシミュレータでは⌘R、AndroidエミュレータではRを2回タッピングします。

Flutterでは、IntelliJ IDEまたはAndroid Studioを使用している場合は、[すべてを保存](⌘/ ctrl-s)を選択するか、ツールバーの[ホットリロード]ボタンをクリックします。 flutter runを使用してコマンドラインでアプリケーションを実行している場合は、ターミナルウィンドウに「r」と入力します。また、「ターミナル」ウィンドウでRを入力して完全再起動することもできます。

Flutterのデバッグにはどんなツールが使える?

Flutterアプリケーションをデバッグする必要があるときに使用できるオプションとツールがいくつかあります。

Flutterアナライザに加えて、Dart Observatoryは、Dartアプリケーションのプロファイリングとデバッグに使用されるツールです。ターミナルでflutter runを使用してアプリケーションを起動した場合は、ターミナルウィンドウに表示されたObservatory URL(http://127.0.0.1:8100/など)でWebページを開くことができます。

Observatoryには、プロファイリング、ヒープの検査、実行されたコード行の観察、メモリーリークのデバッグ、メモリー断片化のサポートが含まれています。詳細については、Observatoryのドキュメントを参照してください。 Observatoryは、Dart SDKをダウンロードしてインストールするときに無料で含まれています。

IDEを使用している場合は、IDEデバッガを使用してアプリケーションをデバッグできます。

IntelliJとAndroid Studioを使用している場合は、Flutterインスペクタを使用できます。 Flutter Inspectorを使用すると、アプリケーションがそのようにレンダリングされる理由を簡単に理解することができます。

  • アプリのUI構造をウィジェットのツリーとして表示する
  • デバイスまたはシミュレータ上のポイントを選択し、それらのピクセルをレンダリングした対応するウィジェットを見つける
  • 個々のウィジェットのプロパティを表示する
  • レイアウトの問題を素早く特定し、その原因を特定する

Flutter Inspectorビューは、View> Tool Windows> Flutter Inspectorから開くことができます。コンテンツは、アプリの実行中にのみ表示されます。

特定のウィジェットを検査するには、ツールバーのToggle inspect mode アクションを選択し、接続されている実機またはシミュレータで目的のウィジェットをクリックします。アプリのUIでウィジェットが強調表示されます。ウィジェットは、IntelliJのウィジェット階層とそのウィジェットの個々のプロパティで表示されます。

詳細については、Flutter Appsのデバッグを参照してください。

Animation

うまく設計されたアニメーションはUIを直感的に感じさせ、洗練されたアプリのルック・アンド・フィールに貢献し、ユーザーエクスペリエンスを向上させます。 Flutterのアニメーションサポートにより、シンプルで複雑なアニメーションを簡単に実装できます。 Flutter SDKには、標準モーションエフェクトを含む多くのマテリアルデザインウィジェットが含まれています。これらのエフェクトを簡単にカスタマイズして、アプリをパーソナライズすることができます。

Reactネイティブでは、アニメーション化されたAPIがアニメーションの作成に使用されます。

Flutterでは、AnimationクラスとAnimationControllerクラスを使用します。アニメーションは、現在の値とその状態(完了または却下)を理解する抽象クラスです。 AnimationControllerクラスを使用すると、前後にアニメーションを再生したり、アニメーションを停止したり、アニメーションを特定の値に設定してモーションをカスタマイズできます。

単純なフェードインアニメーションを追加するには?

下のReact Nativeの例では、アニメーション化されたコンポーネントFadeInViewがアニメーション化されたAPIを使用して作成されています。最初の不透明状態、最終状態、および遷移が起こる持続時間が定義される。アニメーションコンポーネントはAnimatedコンポーネント内に追加され、不透明状態fadeAnimはアニメーション化するTextコンポーネントの不透明度にマップされ、次にstart()が呼び出されてアニメーションが開始されます。

// React Native
class FadeInView extends React.Component {
  state = {
    fadeAnim: new Animated.Value(0) // Initial value for opacity: 0
  };
  componentDidMount() {
    Animated.timing(this.state.fadeAnim, {
      toValue: 1,
      duration: 10000
    }).start();
  }
  render() {
    return (
      <Animated.View style={{...this.props.style, opacity: this.state.fadeAnim }} >
        {this.props.children}
      </Animated.View>
    );
  }
}
    ...
<FadeInView>
  <Text> Fading in </Text>
</FadeInView>
    ...

Flutterでは同じアニメーションを作成するには、controllerという名前のAnimationControllerオブジェクトを作成し、継続時間を指定します。デフォルトでは、AnimationControllerは、一定の期間、0.0から1.0の範囲の値を線形に生成します。アニメーションコントローラは、アプリケーションを実行しているデバイスが新しいフレームを表示する準備ができたら必ず新しい値を生成します。通常、このレートは毎秒約60です。

AnimationControllerを定義するときは、vsyncオブジェクトを渡す必要があります。 vsyncの存在は、オフスクリーンアニメーションが不要なリソースを消費するのを防ぎます。クラス定義にTickerProviderStateMixinを追加することで、ステートフルオブジェクトをvsyncとして使用できます。 AnimationControllerには、コンストラクタのvsync引数を使用して設定されたTickerProviderが必要です。

Tweenは、開始値と終了値の間の補間、または入力範囲から出力範囲へのマッピングを記述します。アニメーションでTweenオブジェクトを使用するには、Tweenオブジェクトのanimateメソッドを呼び出し、変更するAnimationオブジェクトに渡します。

この例では、FadeTransitionウィジェットが使用され、opacityプロパティがアニメーションオブジェクトにマッピングされています。

アニメーションを開始するには、controller.forward()を使用します。他の操作は、fling()やrepeat()などのコントローラーを使用して実行することもできます。この例では、FlutterLogoウィジェットがFadeTransitionウィジェット内で使用されています。

// Flutter
import 'package:flutter/material.dart';

void main() {
  runApp(Center(child: LogoFade()));
}

class LogoFade extends StatefulWidget {
  _LogoFadeState createState() => _LogoFadeState();
}

class _LogoFadeState extends State<LogoFade> with TickerProviderStateMixin {
  Animation animation;
  AnimationController controller;

  initState() {
    super.initState();
    controller = AnimationController(
        duration: const Duration(milliseconds: 3000), vsync: this);
    final CurvedAnimation curve =
    CurvedAnimation(parent: controller, curve: Curves.easeIn);
    animation = Tween(begin: 0.0, end: 1.0).animate(curve);
    controller.forward();
  }

  Widget build(BuildContext context) {
    return FadeTransition(
      opacity: animation,
      child: Container(
        height: 300.0,
        width: 300.0,
        child: FlutterLogo(),
      ),
    );
  }

  dispose() {
    controller.dispose();
    super.dispose();
  }
}

スワイプアニメーションをカードに追加するには?

React Nativeでは、スワイプアニメーションにPanResponderライブラリまたはサードパーティライブラリが使用されます。

Flutterでは、スワイプアニメーションを追加するには、Dismissibleウィジェットを使用して子ウィジェットをネストします。

child: Dismissible(
  key: key,
  onDismissed: (DismissDirection dir) {
    cards.removeLast();
  },
  child: Container(
    ...
  ),
),

React NativeとFlutterウィジェットの同等のコンポーネント

次の表に、対応するFlutterウィジェットおよび一般的なウィジェットプロパティにマップされた、一般的に使用されるReactネイティブコンポーネントを示します。

React Native Component Flutter Widget Description
Button Raised Button 基本的なボタン
onPressed [required] ボタンがタップ、もしくはアクティブ化されたときのコールバック
Child ボタンのラベル
Button Flat Button 基本的なフラットボタン
onPressed [required] ボタンがタップ、もしくはアクティブ化されたときのコールバック
Child ボタンのラベル
ScrollView ListView 線形に配置されたスクロール可能なウィジェットのリスト
children 表示する子ウィジェットのリスト( <Widget> [ ])
controller Scroll Controller ]スクロール可能なウィジェットを制御するために使用できるオブジェクト
itemExtent [ double ]null以外の場合、子はスクロール方向に所定の範囲を持つように強制されます
scroll Direction Axis ]スクロールビューがスクロールする軸
FlatList ListView. builder() オンデマンドで作成されるウィジェットの線形配列のコンストラクタ
itemBuilder [required] [ Indexed Widget Builder]このコールバックは、ゼロ以上でitemCountより小さいインデックスでのみ呼び出されます。
itemCount [ int ] ListViewが最大スクロール・エクステントを推定する能力を向上させます。
Image Image 画像を表示するウィジェット
image [required] 表示する画像
Image. asset イメージを指定するさまざまな方法のために、いくつかのコンストラクタが用意されています
width, height, color, alignment イメージのスタイルとレイアウト
fit レイアウト中に割り当てられたスペースにイメージを書き込む
Modal ModalRoute 以前のルートとのやりとりをブロックするルート
animation ルートの遷移と前のルートの順方向遷移を駆動するアニメーション
Activity Indicator Circular Progress Indicator サークルに沿って進行状況を示すウィジェット
strokeWidth 円を描くために使用される線の幅
backgroundColor 進捗インジケータの背景色。デフォルトでは、現在のテーマのThemeData.backgroundColorです
Activity Indicator Linear Progress Indicator サークルに沿って進行状況を示すウィジェット
value この進捗インジケータの値
Refresh Control Refresh Indicator マテリアルのswipe to refreshイディオムをサポートするウィジェット
color 進捗インジケータの前景色
onRefresh ユーザーがリフレッシュインジケータをドラッグして、アプリをリフレッシュさせることを示すために十分にドラッグしたときに呼び出される関数
View Container 子ウィジェットを囲むウィジェット
Column 子を垂直配列で表示するウィジェット
Row 子を水平配列で表示するウィジェット
Center 子を中央配列で表示するウィジェット
Padding 指定されたパディングで子をインセットするウィジェット
padding [required] [ EdgeInsets ]子を差込ためのスペースの量
Touchable Opacity Gesture Detector ジェスチャーを検出するウィジェット
onTap タップが発生したときのコールバック
onDoubleTap タップが同じ場所で2回連続して発生した場合のコールバック
Text Input Text Input システムのテキスト入力コントロールへのインターフェース
controller Text Editing Controller ]テキストにアクセスして変更するために使用されます
Text Text 単一のスタイルでテキストの文字列を表示するTextウィジェット
data [ String ]表示するテキスト
textDirection Text Align ]テキストが表示される方向
Switch Switch material designスイッチ
value [required] [ boolean ]このスイッチがオンであるかオフであるか
onChanged [required] [ callback ]ユーザーがスイッチをオンまたはオフに切り替えると呼び出されます
Slider Slider ある範囲の値から選択するために使用されます
value [required] [ double ]スライダの現在の値
onChanged [required] ユーザーがスライダの新しい値を選択したときに呼び出されます

Summary

長々ーーと、ReactNativeでできることはFlutterでもできます!!!

的な話をしてきました。

どうだったでしょうーか!

FlutterはめちゃめちゃReactNativeを意識していますねぇ〜

ただしまだ、ドキュメント整備はまだなってないようですけどねぇ〜

それでは少しでも役になれば👋👋👋👋👋

シェアする

  • このエントリーをはてなブックマークに追加

フォローする