Flutter-quick-guide

提供:Dev Guides
移動先:案内検索

フラッター-はじめに

一般に、モバイルアプリケーションの開発は複雑で困難な作業です。 モバイルアプリケーションを開発するために利用できる多くのフレームワークがあります。 AndroidはJava言語に基づくネイティブフレームワークを提供し、iOSはObjective-C/Swift言語に基づくネイティブフレームワークを提供します。

ただし、両方のOSをサポートするアプリケーションを開発するには、2つの異なるフレームワークを使用して2つの異なる言語でコーディングする必要があります。 この複雑さを克服するために、両方のOSをサポートするモバイルフレームワークが存在します。 これらのフレームワークは、単純なHTMLベースのハイブリッドモバイルアプリケーションフレームワーク(ユーザーインターフェイスにHTML、アプリケーションロジックにJavaScriptを使用)から、複雑な言語固有のフレームワーク(コードをネイティブコードに変換する手間のかかる作業)に及びます。 これらのフレームワークには、単純さや複雑さに関係なく、常に多くの欠点がありますが、主な欠点の1つはパフォーマンスの低下です。

このシナリオでは、Dart言語に基づいたシンプルで高性能なフレームワークであるFlutterは、ネイティブフレームワークではなくオペレーティングシステムのキャンバスで直接UIをレンダリングすることにより、高いパフォーマンスを提供します。

Flutterは、すぐに使用できるウィジェット(UI)を多数提供して、最新のアプリケーションを作成します。 これらのウィジェットはモバイル環境向けに最適化されており、ウィジェットを使用したアプリケーションの設計はHTMLの設計と同じくらい簡単です。

具体的には、Flutterアプリケーション自体がウィジェットです。 Flutterウィジェットは、アニメーションとジェスチャーもサポートしています。 アプリケーションロジックは、リアクティブプログラミングに基づいています。 ウィジェットにはオプションで状態があります。 ウィジェットの状態を変更することにより、Flutterは自動的に(リアクティブプログラミング)ウィジェットの状態(古いものと新しいもの)を比較し、ウィジェット全体を再レンダリングする代わりに、必要な変更のみでウィジェットをレンダリングします。

完全なアーキテクチャについては、今後の章で説明します。

Flutterの機能

Flutterフレームワークは、開発者に次の機能を提供します-

  • モダンでリアクティブなフレームワーク。
  • Dartプログラミング言語を使用し、非常に簡単に習得できます。
  • 迅速な開発
  • 美しく滑らかなユーザーインターフェイス。
  • 巨大なウィジェットカタログ。
  • 複数のプラットフォームで同じUIを実行します。
  • 高性能アプリケーション。

フラッターの利点

Flutterには、高性能で優れたモバイルアプリケーション向けの美しくカスタマイズ可能なウィジェットが付属しています。 すべてのカスタムニーズと要件を満たします。 これらに加えて、Flutterは以下で説明するように多くの利点を提供します-

  • Dartには、アプリケーションの機能を拡張できるソフトウェアパッケージの大きなリポジトリがあります。
  • 開発者は、両方のアプリケーション(AndroidプラットフォームとiOSプラットフォームの両方)に対して1つのコードベースのみを記述する必要があります。 _Flutter_は、将来的に他のプラットフォームにも拡張される可能性があります。
  • Flutterのテストは少なくて済みます。 単一のコードベースのため、両方のプラットフォームに対して自動化されたテストを一度書くだけで十分です。
  • Flutterはシンプルであるため、高速開発に適しています。 そのカスタマイズ機能と拡張性により、さらに強力になります。
  • Flutterを使用すると、開発者はウィジェットとそのレイアウトを完全に制御できます。
  • Flutterは、すばらしいホットリロードを備えた優れた開発者ツールを提供します。

フラッターの欠点

その多くの利点にもかかわらず、フラッターには次のような欠点があります-

  • Dart言語でコーディングされているため、開発者は新しい言語を習得する必要があります(習得は簡単ですが)。
  • 現代のフレームワークは、ロジックとUIを可能な限り分離しようとしますが、Flutterでは、ユーザーインターフェイスとロジックが混在しています。 スマートコーディングを使用し、高レベルモジュールを使用してユーザーインターフェイスとロジックを分離することで、これを克服できます。
  • Flutterは、モバイルアプリケーションを作成するためのもう1つのフレームワークです。 開発者は、人口が非常に多いセグメントで適切な開発ツールを選択するのに苦労しています。

フラッター-インストール

この章では、ローカルコンピューターにFlutterを詳細にインストールする方法を説明します。

Windowsでのインストール

このセクションでは、_Flutter SDK_とその要件をWindowsシステムにインストールする方法を見てみましょう。

  • ステップ1 *-URL [[1]] SDKをダウンロードします。 2019年4月現在、バージョンは1.2.1で、ファイルはflutter_windows_v1.2.1-stable.zipです。
  • ステップ2 *-C:\ flutter \などのフォルダーでzipアーカイブを解凍します
  • ステップ3 *-システムパスを更新して、flutter binディレクトリを含めます。
  • ステップ4 *-Flutterは、フラッター開発のすべての要件が満たされていることを確認するツールであるflutter doctorを提供します。
flutter doctor
  • ステップ5 *-上記のコマンドを実行すると、システムが分析され、以下に示すようにレポートが表示されます-
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, v1.2.1, on Microsoft Windows [Version
10.0.17134.706], locale en-US)
[√] Android toolchain - develop for Android devices (Android SDK version
28.0.3)
[√] Android Studio (version 3.2)
[√] VS Code, 64-bit edition (version 1.29.1)
[!] Connected device
! No devices available
! Doctor found issues in 1 category.

レポートには、すべての開発ツールが利用可能であるが、デバイスが接続されていないことが記載されています。 これを修正するには、USB経由でAndroidデバイスを接続するか、Androidエミュレーターを起動します。

  • ステップ6 *-flutter doctorから報告された場合、最新のAndroid SDKをインストールする
  • ステップ7 *-flutter doctorから報告された場合、最新のAndroid Studioをインストールします
  • ステップ8 *-Androidエミュレーターを起動するか、実際のAndroidデバイスをシステムに接続します。
  • ステップ9 *-Android Studio用のFlutterおよびDartプラグインをインストールします。 新しいFlutterアプリケーションを作成するためのスタートアップテンプレート、Androidスタジオ自体でFlutterアプリケーションを実行およびデバッグするオプションなどを提供します。
  • Android Studioを開きます。
  • [ファイル]→[設定]→[プラグイン]をクリックします。
  • Flutterプラグインを選択し、[インストール]をクリックします。
  • Dartプラグインをインストールするように求められたら、[はい]をクリックします。
  • Androidスタジオを再起動します。

MacOSでのインストール

MacOSにFlutterをインストールするには、次の手順に従う必要があります-

  • ステップ1 *-URL [[2]] SDKをダウンロードします。 2019年4月現在、バージョンは1.2.1で、ファイルはflutter_macos_v1.2.1- stable.zipです。
  • ステップ2 *-フォルダー内のzipアーカイブを解凍します。たとえば、/path/to/flutter
  • ステップ3 *-システムパスを更新して、flutter binディレクトリ(〜/.bashrcファイル内)を含めます。
> export PATH = "$PATH:/path/to/flutter/bin"
  • ステップ4 *-以下のコマンドを使用して、現在のセッションで更新されたパスを有効にし、それも確認します。
source ~/.bashrc
source $HOME/.bash_profile
echo $PATH

フラッターは、フラッター開発のすべての要件が満たされていることを確認するツール、フラッタードクターを提供します。 Windowsの同等物に似ています。

  • ステップ5 *-flutter doctorから報告された場合、最新のXCodeをインストールします
  • ステップ6 *-flutter doctorから報告された場合、最新のAndroid SDKをインストールします
  • ステップ7 *-flutter doctorから報告された場合、最新のAndroid Studioをインストールします
  • ステップ8 *-Androidエミュレーターを起動するか、実際のAndroidデバイスをシステムに接続して、Androidアプリケーションを開発します。
  • ステップ9 *-iOSシミュレーターを開くか、実際のiPhoneデバイスをシステムに接続してiOSアプリケーションを開発します。
  • ステップ10 *-Android Studio用のFlutterおよびDartプラグインをインストールします。 新しいFlutterアプリケーションを作成するためのスタートアップテンプレート、Androidスタジオ自体でFlutterアプリケーションを実行およびデバッグするオプションなどを提供します。
  • Android Studioを開きます
  • [設定]→[プラグイン]をクリックします*
  • Flutterプラグインを選択し、[インストール]をクリックします
  • Dartプラグインをインストールするように求められたら、[はい]をクリックします。
  • Androidスタジオを再起動します。

Android Studioでのシンプルなアプリケーションの作成

この章では、Android Studioでフラッターアプリケーションを作成する基本を理解するために、簡単な_Flutter_アプリケーションを作成します。

  • ステップ1 *-Android Studioを開きます

ステップ2 *-Flutterプロジェクトを作成します。 そのためには、[ファイル]→[新規]→[新しいFlutterプロジェクト]をクリックします

新しいFlutterプロジェクト

ステップ3 *-Flutter Applicationを選択します。 このために、 *Flutter Application を選択し、 Next をクリックします。

Flutter Application Next

  • ステップ4 *-アプリケーションを以下のように構成し、[次へ]をクリックします。
  • プロジェクト名: hello_app
  • Flutter SDKパス: <path_to_flutter_sdk>
  • プロジェクトの場所: <path_to_project_folder>
  • 説明:フラッターベースのHello Worldアプリケーション

プロジェクト名

  • ステップ5 *-プロジェクトを構成します。

会社のドメインを flutterapp.finddevguides.com として設定し、[*完了]をクリックします。

  • ステップ6 *-会社のドメインを入力します。

Android Studioは、最小限の機能で完全に機能するフラッターアプリケーションを作成します。 アプリケーションの構造を確認してから、コードを変更してタスクを実行しましょう。

アプリケーションの構造とその目的は次のとおりです-

構造アプリケーション

アプリケーションの構造のさまざまなコンポーネントはここで説明されています-

  • アンドロイド-アンドロイドアプリケーションを作成するために自動生成されたソースコード
  • ios -iOSアプリケーションを作成するために自動生成されたソースコード
  • lib -flutterフレームワークを使用して記述されたDartコードを含むメインフォルダー
  • ib/main.dart -Flutterアプリケーションのエントリポイント
  • test -フラッターアプリケーションをテストするためのDartコードを含むフォルダー
  • test/widget_test.dart -サンプルコード
  • .gitignore -Gitバージョン管理ファイル
  • .metadata -フラッターツールによって自動生成
  • .packages -フラッターパッケージを追跡するために自動生成
  • .iml -Androidスタジオで使用されるプロジェクトファイル
  • pubspec.yaml -Flutterパッケージマネージャー Pub で使用
  • pubspec.lock -Flutterパッケージマネージャー、 Pub によって自動生成
  • README.md -Markdown形式で記述されたプロジェクト記述ファイル
  • ステップ7 *-_lib/main.dartファイルのdartコードを以下のコードに置き換えます-
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  //This widget is the root of your application.
   @override
   Widget build(BuildContext context) {
      return MaterialApp(
         title: 'Hello World Demo Application',
         theme: ThemeData(
            primarySwatch: Colors.blue,
         ),
         home: MyHomePage(title: 'Home page'),
      );
   }
}
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key);
   final String title;

   @override
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(
            title: Text(this.title),
         ),
         body: Center(
            child:
            Text(
               'Hello World',
            )
         ),
      );
   }
}

dartコードを1行ずつ理解してみましょう。

  • *行1 *-flutterパッケージ_material_をインポートします。 マテリアルは、Androidで指定されたマテリアルデザインガイドラインに従ってユーザーインターフェイスを作成するためのフラッターパッケージです。
  • *行3 *-これはFlutterアプリケーションのエントリポイントです。 _runApp_関数を呼び出して、_MyApp_クラスのオブジェクトを渡します。 _runApp_関数の目的は、指定されたウィジェットを画面に添付することです。
  • *行5-17 *-ウィジェットは、フラッターフレームワークでUIを作成するために使用されます。 StatelessWidget_はウィジェットであり、ウィジェットの状態を保持しません。 _MyApp_は_StatelessWidget_を拡張し、その_buildメソッド_をオーバーライドします。 _build_メソッドの目的は、アプリケーションのUIの一部を作成することです。 ここで、_build_メソッドは、アプリケーションのルートレベルUIを作成するウィジェットである_MaterialApp_を使用します。 _title、theme _、 home_の3つのプロパティがあります。
  • _title_はアプリケーションのタイトルです
  • _theme_はウィジェットのテーマです。 ここでは、_ThemeData_クラスとそのプロパティ_primarySwatch_を使用して、アプリケーションの全体的な色として_blue_を設定します。
  • homeはアプリケーションの内部UIで、別のウィジェット MyHomePage を設定します
  • *行19-38 *-MyHomePage_は、_Scaffold Widgetを返すことを除いて、_MyApp_と同じです。 _Scaffold_は、_MaterialApp_ウィジェットの横にあるトップレベルのウィジェットで、UI準拠のマテリアルデザインを作成するために使用されます。 アプリケーションのヘッダーを表示する_appBar_と、アプリケーションの実際のコンテンツを表示するbodyの2つの重要なプロパティがあります。 _AppBar_は、アプリケーションのヘッダーをレンダリングする別のウィジェットであり、_appBar_プロパティで使用しています。 _body_プロパティでは、子ウィジェットを中央に配置する_Center_ウィジェットを使用しました。 _Text_は、テキストを表示するための最後で最も内側のウィジェットであり、画面の中央に表示されます。

ステップ8 *-次に、 *Run→Run main.dart を使用してアプリケーションを実行します

メインダーツ

  • ステップ9 *-最後に、アプリケーションの出力は次のとおりです-

ホームページ

Flutter-アーキテクチャアプリケーション

この章では、Flutterフレームワークのアーキテクチャについて説明します。

ウィジェット

Flutterフレームワークのコアコンセプトは* Flutterでは、すべてがウィジェットです*。 ウィジェットは基本的に、アプリケーションのユーザーインターフェイスを作成するために使用されるユーザーインターフェイスコンポーネントです。

_Flutter_では、アプリケーション自体がウィジェットです。 アプリケーションはトップレベルのウィジェットであり、そのUIは1つ以上の子(ウィジェット)を使用して構築され、子は再び子ウィジェットを使用して構築されます。 この composability 機能は、複雑なユーザーインターフェイスを作成するのに役立ちます。

たとえば、hello worldアプリケーション(前の章で作成)のウィジェット階層は、次の図で指定されています-

Hello Worldアプリケーション

ここで、次の点は注目に値します-

  • _MyApp_はユーザーが作成したウィジェットで、Flutterネイティブウィジェット_MaterialApp_を使用してビルドされます。
  • _MaterialApp_には、ホームページのユーザーインターフェイスを指定するhomeプロパティがあります。これは、ユーザーが作成したウィジェット_MyHomePage_です。
  • _MyHomePage_は、別のフラッターネイティブウィジェット_Scaffold_を使用して構築されます
  • _Scaffold_には、_body_と_appBar_の2つのプロパティがあります
  • _body_はメインユーザーインターフェイスを指定するために使用され、_appBar_はヘッダーユーザーインターフェイスを指定するために使用されます
  • _Header UI_はフラッターネイティブウィジェットを使用してビルドされ、_AppBar_および_Body UI_は_Center_ウィジェットを使用してビルドされます。
  • _Center_ウィジェットには、実際のコンテンツを参照するプロパティ_Child_があり、_Text_ウィジェットを使用して構築されます

ジェスチャー

Flutterウィジェットは、特別なウィジェット_GestureDetector_を介した対話をサポートします。 _GestureDetector_は、子ウィジェットのタップ、ドラッグなどのユーザーインタラクションをキャプチャする機能を持つ非表示のウィジェットです。 Flutterの多くのネイティブウィジェットは、_GestureDetector_の使用による相互作用をサポートしています。 _GestureDetector_ウィジェットで構成することにより、既存のウィジェットにインタラクティブ機能を組み込むこともできます。 ジェスチャーについては、今後の章で個別に学習します。

状態の概念

Flutterウィジェットは、_StatefulWidget_という特別なウィジェットを提供することにより、_State maintenance_をサポートします。 ウィジェットは、状態の維持をサポートするために_StatefulWidget_ウィジェットから派生する必要があり、他のすべてのウィジェットは_StatefulWidget_から派生する必要があります。 Flutterウィジェットはネイティブでは*リアクティブ*です。 これはreactjsに似ており、_StatefulWidget_は内部状態が変更されるたびに自動的に再レン​​ダリングされます。 再レンダリングは、古いウィジェットと新しいウィジェットのUIの違いを見つけて、必要な変更のみをレンダリングすることにより最適化されます

レイヤー

Flutterフレームワークの最も重要な概念は、フレームワークが複雑さの観点から複数のカテゴリにグループ化され、複雑さが減少する層に明確に配置されることです。 レイヤーは、そのすぐ次のレベルのレイヤーを使用して構築されます。 最上位のレイヤーは、_Android_および_iOS_に固有のウィジェットです。 次のレイヤーには、すべてのフラッターネイティブウィジェットがあります。 次のレイヤーは_Rendering layer_です。これは低レベルのレンダラーコンポーネントであり、フラッターアプリですべてをレンダリングします。 レイヤーはコアプラットフォーム固有のコードになります

Flutterのレイヤーの一般的な概要は、以下の図に指定されています-

レイヤーの概要

次のポイントは、フラッターのアーキテクチャをまとめています-

  • Flutterでは、すべてがウィジェットであり、複雑なウィジェットは既存のウィジェットで構成されています。
  • _GestureDetector_ウィジェットを使用すると、必要に応じてインタラクティブ機能を組み込むことができます。
  • ウィジェットの状態は、_StatefulWidget_ウィジェットを使用して必要なときにいつでも維持できます。
  • Flutterはレイヤー化されたデザインを提供しているため、タスクの複雑さに応じて任意のレイヤーをプログラムできます。

これらすべての概念については、今後の章で詳しく説明します。

Flutter-Dartプログラミングの概要

Dartは、オープンソースの汎用プログラミング言語です。 もともとはGoogleによって開発されました。 Dartは、Cスタイルの構文を持つオブジェクト指向言語です。 他のプログラミング言語とは異なり、インターフェイス、クラスなどのプログラミング概念をサポートしています。Dartは配列をサポートしていません。 Dartコレクションを使用して、配列、ジェネリック、オプションの型指定などのデータ構造を複製できます。

次のコードは、単純なDartプログラムを示しています-

void main() {
   print("Dart language is easy to learn");
}

変数とデータ型

_Variable_はストレージの場所と呼ばれ、_Data types_は単に変数と関数に関連付けられたデータのタイプとサイズを指します。

Dartは_var_キーワードを使用して変数を宣言します。 _var_の構文は以下に定義されています。

var name = 'Dart';

_final_および_const_キーワードは、定数を宣言するために使用されます。 それらは以下のように定義されます-

void main() {
   final a = 12;
   const pi = 3.14;
   print(a);
   print(pi);
}

Dart言語は、次のデータ型をサポートしています-

  • 数字-数値リテラル–整数および倍精度を表すために使用されます。
  • 文字列-文字のシーケンスを表します。 文字列値は、一重引用符または二重引用符で指定されます。
  • ブール-Dartは_bool_キーワードを使用してブール値(trueおよびfalse)を表します。
  • リストとマップ-オブジェクトのコレクションを表すために使用されます。 単純なリストは以下のように定義できます-。
void main() {
   var list = [1,2,3,4,5];
   print(list);
}

上記のリストは[1,2,3,4,5]リストを生成します。

マップはここに示すように定義することができます-

void main() {
   var mapping = {'id': 1,'name':'Dart'};
   print(mapping);
}
  • 動的-変数の型が定義されていない場合、デフォルトの型は動的です。 次の例は、動的型変数を示しています-
void main() {
   dynamic name = "Dart";
   print(name);
}

意思決定とループ

意思決定ブロックは、命令が実行される前に条件を評価します。 DartはIf、If..elseおよびswitchステートメントをサポートしています。

ループは、特定の条件が満たされるまでコードのブロックを繰り返すために使用されます。 Dartは、for..in、while、do..whileループをサポートしています。

制御文とループの使用に関する簡単な例を理解しましょう-

void main() {
   for( var i = 1 ; i <= 10; i++ ) {
      if(i%2==0) {
         print(i);
      }
   }
}

上記のコードは、1〜10の偶数を出力します。

関数

関数は、特定のタスクを一緒に実行するステートメントのグループです。 ここに示すように、Dartの単純な関数を見てみましょう-

void main() {
   add(3,4);
}
void add(int a,int b) {
   int c;
   c = a+b;
   print(c);
}

上記の関数は2つの値を追加し、出力として7を生成します。

オブジェクト指向プログラミング

Dartはオブジェクト指向言語です。 クラス、インターフェースなどのオブジェクト指向プログラミング機能をサポートしています。

クラスは、オブジェクトを作成するための青写真です。 クラス定義には次のものが含まれます-

  • フィールド
  • ゲッターとセッター
  • コンストラクタ
  • 関数

さて、上記の定義を使用して簡単なクラスを作成しましょう-

class Employee {
   String name;

  //getter method
   String get emp_name {
      return name;
   }
  //setter method
   void set emp_name(String name) {
      this.name = name;
   }
  //function definition
   void result() {
      print(name);
   }
}
void main() {
  //object creation
   Employee emp = new Employee();
   emp.name = "employee1";
   emp.result();//function call
}

Flutter-ウィジェットの紹介

前の章で学んだように、ウィジェットはFlutterフレームワークのすべてです。 前の章で新しいウィジェットを作成する方法をすでに学びました。

この章では、ウィジェットを作成する背後にある実際の概念と、_Flutter_フレームワークで使用可能なさまざまなタイプのウィジェットを理解しましょう。

_Hello World_アプリケーションの_MyHomePage_ウィジェットを確認しましょう。 この目的のためのコードは以下のとおりです-

class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key);

   final String title;
   @override
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text(this.title), ),
         body: Center(child: Text( 'Hello World',)),
      );
   }
}

ここでは、_StatelessWidget_を拡張して新しいウィジェットを作成しました。

_StatelessWidget_では、派生クラスに実装される単一のメソッド_build_のみが必要であることに注意してください。 _build_メソッドは、_BuildContext_パラメーターを介してウィジェットをビルドするために必要なコンテキスト環境を取得し、ビルドしたウィジェットを返します。

コードでは、コンストラクター引数の1つとして_title_を使用し、別の引数として_Key_を使用しました。 _title_はタイトルを表示するために使用され、_Key_はビルド環境でウィジェットを識別するために使用されます。

ここで、_build_メソッドは_Scaffold_の_build_メソッドを呼び出し、_Scaffold_は_AppBar_および_Center_の_build_メソッドを呼び出して、そのユーザーインターフェイスを_build_します。

最後に、_Center_ビルドメソッドは_Text_ビルドメソッドを呼び出します。

より良い理解のために、同じものの視覚的表現を以下に示します-

視覚的表現

ウィジェットビルドの視覚化

_Flutter_では、以下にリストするように、ウィジェットを機能に基づいて複数のカテゴリにグループ化できます-

  • プラットフォーム固有のウィジェット
  • レイアウトウィジェット
  • 状態維持ウィジェット
  • プラットフォームに依存しない/基本的なウィジェット

それぞれについて詳しく説明しましょう。

プラットフォーム固有のウィジェット

Flutterには、特定のプラットフォーム(AndroidまたはiOS)に固有のウィジェットがあります。

Android固有のウィジェットは、Android OSの_Material design guideline_に従って設計されています。 Android固有のウィジェットは、_Materialウィジェット_と呼ばれます。

iOS固有のウィジェットは、Appleの_Human Interface Guidelines_に従って設計されており、_Cupertino_ウィジェットと呼ばれます。

最も使用される材料ウィジェットのいくつかは次のとおりです-

  • 足場
  • AppBar
  • BottomNavigationBar
  • TabBar
  • TabBarView
  • ListTile
  • RaisedButton
  • FloatingActionButton
  • フラットボタン
  • IconButton
  • DropdownButton
  • PopupMenuButton
  • ButtonBar
  • テキストフィールド
  • チェックボックス
  • 無線
  • スイッチ
  • スライダー
  • 日付と時刻のピッカー
  • SimpleDialog
  • AlertDialog

最も使用される_Cupertino_ウィジェットのいくつかは次のとおりです-

  • クパチーノボタン
  • クパチーノピッカー
  • クパチーノデイトピッカー
  • CupertinoTimerPicker
  • CupertinoNavigationBar
  • CupertinoTabBar
  • クパチーノタブ足場
  • CupertinoTabView
  • CupertinoTextField
  • クパチーノダイアログ
  • CupertinoDialogAction
  • CupertinoFullscreenDialogTransition
  • CupertinoPageScaffold
  • CupertinoPageTransition
  • クパチーノアクションシート
  • CupertinoActivityIndi​​cator
  • CupertinoAlertDialog
  • CupertinoPopupSurface
  • クパチーノスライダー

レイアウトウィジェット

Flutterでは、1つ以上のウィジェットを構成することでウィジェットを作成できます。 複数のウィジェットを単一のウィジェットに合成するために、_Flutter_はレイアウト機能を備えた多数のウィジェットを提供します。 たとえば、子ウィジェットは、_Center_ウィジェットを使用して中央に配置できます。

人気のあるレイアウトウィジェットのいくつかは次のとおりです-

  • コンテナ-背景、境界線、影付きの_BoxDecoration_ウィジェットを使用して装飾された長方形のボックス。
  • Center -子ウィジェットを中央に配置します。
  • -子を水平方向に配置します。
  • -子を垂直方向に配置します。
  • スタック-上下に並べます。

レイアウトウィジェットの詳細については、今後の「レイアウトウィジェットの紹介」の章で確認します。

状態維持ウィジェット

Flutterでは、すべてのウィジェットは_StatelessWidget_または_StatefulWidget_から派生します。

_StatelessWidget_から派生したウィジェットには状態情報はありませんが、_StatefulWidget_から派生したウィジェットを含めることができます。 アプリケーションの動的な性質は、ウィジェットの対話型動作と対話中の状態の変化によるものです。 たとえば、カウンターボタンをタップすると、カウンターの内部状態が1つずつ増加/減少し、_Flutter_ウィジェットの反応性により、新しい状態情報を使用してウィジェットが自動的に再レン​​ダリングされます。

_StatefulWidget_ウィジェットの概念については、今後の_State管理の章で詳しく学習します。

プラットフォームに依存しない/基本的なウィジェット

_Flutter_は、プラットフォームに依存しない方法で単純なユーザーインターフェイスと複雑なユーザーインターフェイスを作成するための多数の基本的なウィジェットを提供します。 この章の基本的なウィジェットのいくつかを見てみましょう。

テキスト

_Text_ウィジェットは、文字列を表示するために使用されます。 文字列のスタイルは、_style_プロパティと_TextStyle_クラスを使用して設定できます。 この目的のためのサンプルコードは次のとおりです-

Text('Hello World!', style: TextStyle(fontWeight: FontWeight.bold))

_Text_ウィジェットには、_Text.rich_という特別なコンストラクターがあり、_TextSpan_型の子を受け入れて、異なるスタイルの文字列を指定します。 _TextSpan_ウィジェットは本質的に再帰的であり、_TextSpan_をその子として受け入れます。 この目的のためのサンプルコードは次のとおりです-

Text.rich(
   TextSpan(
      children: <TextSpan>[
         TextSpan(text: "Hello ", style:
         TextStyle(fontStyle: FontStyle.italic)),
         TextSpan(text: "World", style:
         TextStyle(fontWeight: FontWeight.bold)),
      ],
   ),
)

_Text_ウィジェットの最も重要なプロパティは次のとおりです-

  • maxLines、int -表示する最大行数
  • overflow、TextOverFlow -_TextOverFlow_クラスを使用して視覚的なオーバーフローを処理する方法を指定します
  • style、TextStyle -_TextStyle_クラスを使用して文字列のスタイルを指定します
  • textAlign、TextAlign -_TextAlign_クラスを使用した、右、左、両端揃えなどのテキストの配置
  • textDirection、TextDirection -フローするテキストの方向、左から右または右から左

画像

_Image_ウィジェットは、アプリケーションで画像を表示するために使用されます。 _Image_ウィジェットは、複数のソースから画像をロードするためのさまざまなコンストラクタを提供し、それらは次のとおりです-

  • 画像-_ImageProvider_を使用した汎用画像ローダー
  • Image.asset -flutterプロジェクトのアセットから画像を読み込みます
  • Image.file -システムフォルダから画像をロード
  • Image.memory -メモリから画像をロード
  • Image.Network -ネットワークから画像をロード

_Flutter_に画像を読み込んで表示する最も簡単なオプションは、画像をアプリケーションのアセットとして含め、オンデマンドでウィジェットに読み込むことです。

  • プロジェクトフォルダーにフォルダーとアセットを作成し、必要な画像を配置します。
  • 以下に示すように、pubspec.yamlでアセットを指定します-
flutter:
   assets:
      - assets/smiley.png
  • 次に、アプリケーションに画像をロードして表示します。
Image.asset('assets/smiley.png')
  • hello worldアプリケーションの_MyHomePage_ウィジェットの完全なソースコードと結果は、以下のようになります-
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key);
   final String title;

   @override
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar( title: Text(this.title), ),
         body: Center( child: Image.asset("assets/smiley.png")),
      );
   }
}

ロードされた画像は以下のようになります-

Hello Worldアプリケーション出力

_Image_ウィジェットの最も重要なプロパティは次のとおりです-

  • image、ImageProvider -ロードする実際の画像
  • width、double -画像の幅
  • height、double -画像の高さ
  • alignment、AlignmentGeometry -境界内で画像を配置する方法

アイコン

_Icon_ウィジェットは、_IconData_クラスで説明されているフォントからグリフを表示するために使用されます。 シンプルなメールアイコンをロードするコードは次のとおりです-

Icon(Icons.email)

Hello Worldアプリケーションに適用する完全なソースコードは次のとおりです-

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

   @override
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text(this.title),),
         body: Center( child: Icon(Icons.email)),
      );
   }
}

ロードされたアイコンは以下のとおりです-

ホームページ

Flutter-レイアウトの概要

_Flutter_の中心概念は_Everything_が_widget_であるため、_Flutter_はウィジェット自体にユーザーインターフェイスレイアウト機能を組み込んでいます。 _Flutter_は、_Container、Center、Align_などの特別に設計された多くのウィジェットを提供しますが、これらはユーザーインターフェイスをレイアウトするためだけのものです。 ウィジェットは、他のウィジェットを構成することにより構築され、通常はレイアウトウィジェットを使用します。 この章の_Flutter_レイアウトの概念を学んでみましょう。

レイアウトウィジェットのタイプ

レイアウトウィジェットは、その子に基づいて2つの異なるカテゴリにグループ化することができます-

  • 単一の子をサポートするウィジェット
  • 複数の子をサポートするウィジェット

今後のセクションで、ウィジェットのタイプとその機能の両方を学びましょう。

単一の子ウィジェット

このカテゴリでは、ウィジェットには子として1つのウィジェットのみがあり、すべてのウィジェットには特別なレイアウト機能があります。

たとえば、_Center_ウィジェットは、親ウィジェットに対して子ウィジェットを中央に配置するだけで、_Container_ウィジェットは、パディング、装飾などのさまざまなオプションを使用して、その内部の任意の場所に子を配置する完全な柔軟性を提供します。

単一の子ウィジェットは、ボタン、ラベルなどの単一の機能を備えた高品質のウィジェットを作成するための優れたオプションです。

_Container_ウィジェットを使用してシンプルなボタンを作成するコードは次のとおりです-

class MyButton extends StatelessWidget {
   MyButton({Key key}) : super(key: key);

   @override
   Widget build(BuildContext context) {
      return Container(
         decoration: const BoxDecoration(
            border: Border(
               top: BorderSide(width: 1.0, color: Color(0xFFFFFFFFFF)),
               left: BorderSide(width: 1.0, color: Color(0xFFFFFFFFFF)),
               right: BorderSide(width: 1.0, color: Color(0xFFFF000000)),
               bottom: BorderSide(width: 1.0, color: Color(0xFFFF000000)),
            ),
         ),
         child: Container(
            padding: const
            EdgeInsets.symmetric(horizontal: 20.0, vertical: 2.0),
            decoration: const BoxDecoration(
               border: Border(
                  top: BorderSide(width: 1.0, color: Color(0xFFFFDFDFDF)),
                  left: BorderSide(width: 1.0, color: Color(0xFFFFDFDFDF)),
                  right: BorderSide(width: 1.0, color: Color(0xFFFF7F7F7F)),
                  bottom: BorderSide(width: 1.0, color: Color(0xFFFF7F7F7F)),
               ),
               color: Colors.grey,
            ),
            child: const Text(
               'OK',textAlign: TextAlign.center, style: TextStyle(color: Colors.black)
            ),
         ),
      );
   }
}

ここでは、_Container_ウィジェットと_Text_ウィジェットの2つのウィジェットを使用しました。 ウィジェットの結果は、以下に示すようにカスタムボタンとしてです-

OK

_Flutter_が提供する最も重要な単一の子レイアウトウィジェットのいくつかをチェックしましょう−

  • Padding -指定されたパディングで子ウィジェットを配置するために使用されます。 ここでは、_EdgeInsets_クラスによってパディングを提供できます。
  • Align -_alignment_プロパティの値を使用して、子ウィジェットをそれ自体内で位置合わせします。 _alignment_プロパティの値は、_FractionalOffset_クラスによって提供できます。 _FractionalOffset_クラスは、左上からの距離でオフセットを指定します。

オフセットの可能な値のいくつかは次のとおりです-

  • FractionalOffset(1.0、0.0)は右上を表します。
  • FractionalOffset(0.0、1.0)は左下を表します。

オフセットに関するサンプルコードを以下に示します-

Center(
   child: Container(
      height: 100.0,
      width: 100.0,
      color: Colors.yellow, child: Align(
         alignment: FractionalOffset(0.2, 0.6),
         child: Container( height: 40.0, width:
            40.0, color: Colors.red,
         ),
      ),
   ),
)
  • FittedBox -子ウィジェットをスケーリングし、指定されたフィットに応じて配置します。
  • AspectRatio -指定されたアスペクト比に子ウィジェットのサイズを変更しようとします
  • ConstrainedBox
  • ベースライン
  • FractinallySizedBox
  • IntrinsicHeight
  • IntrinsicWidth
  • LiimitedBox
  • オフステージ
  • OverflowBox
  • SizedBox
  • SizedOverflowBox
  • 変換する
  • CustomSingleChildLayout

私たちのhello worldアプリケーションは、マテリアルベースのレイアウトウィジェットを使用してホームページを設計しています。 hello worldアプリケーションを変更して、以下に指定されている基本的なレイアウトウィジェットを使用してホームページを構築します。

  • Container -位置合わせ、パディング、境界線、マージンを備えた汎用の単一の子、ボックスベースのコンテナウィジェットと豊富なスタイリング機能。
  • Center -子ウィジェットを中央に配置するシンプルな単一の子コンテナウィジェット。

_MyHomePage_および_MyApp_ウィジェットの変更されたコードは以下のとおりです-

class MyApp extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
      return MyHomePage(title: "Hello World demo app");
   }
}
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key);
   final String title;
   @override
   Widget build(BuildContext context) {
      return Container(
         decoration: BoxDecoration(color: Colors.white,),
         padding: EdgeInsets.all(25), child: Center(
            child:Text(
               'Hello World', style: TextStyle(
                  color: Colors.black, letterSpacing: 0.5, fontSize: 20,
               ),
               textDirection: TextDirection.ltr,
            ),
         )
      );
   }
}

ここに、

  • _Container_ウィジェットは、トップレベルまたはルートウィジェットです。 _Container_は、_decoration_および_padding_プロパティを使用してコンテンツをレイアウトするように構成されます。
  • _BoxDecoration_には、_Container_ウィジェットを装飾するための色、境界線などの多くのプロパティがあり、ここでは、_color_を使用してコンテナの色を設定します。
  • _Container_ウィジェットの_padding_は、パディング値を指定するオプションを提供する_dgeInsets_クラスを使用して設定されます。
  • _Center_は、_Container_ウィジェットの子ウィジェットです。 繰り返しますが、_Text_は_Center_ウィジェットの子です。 _Text_はメッセージを表示するために使用され、_Center_は親ウィジェット_Container_に対してテキストメッセージを中央に配置するために使用されます。

上記のコードの最終結果は、以下に示すようなレイアウトサンプルです-

最終結果

複数の子ウィジェット

このカテゴリでは、特定のウィジェットに複数の子ウィジェットがあり、各ウィジェットのレイアウトは一意です。

たとえば、_Row_ウィジェットでは、子を水平方向にレイアウトできますが、_Column_ウィジェットでは、子を垂直方向にレイアウトできます。 _Row_と_Column_を構成することにより、あらゆるレベルの複雑さを持つウィジェットを構築できます。

このセクションで頻繁に使用されるウィジェットのいくつかを学びましょう。

  • -子を水平方向に配置できます。
  • -子を垂直方向に配置できます。
  • ListView -子をリストとして配置できます。
  • GridView -子をギャラリーとして配置できます。
  • Expanded -Row and Columnウィジェットの子を作成して、可能な限り最大の領域を占めるようにします。
  • Table -テーブルベースのウィジェット。
  • Flow -フローベースのウィジェット。
  • Stack -スタックベースのウィジェット。

高度なレイアウトアプリケーション

このセクションでは、単一および複数の子レイアウトウィジェットを使用して、カスタムデザインで_product Listing_の複雑なユーザーインターフェイスを作成する方法を学びましょう。

この目的のために、以下に示すシーケンスに従ってください-

  • Androidスタジオで_product_layout_app_に新しい_Flutter_アプリケーションを作成します。
  • _main.dart_コードを次のコードに置き換えます-
import 'package:flutter/material.dart';
void main() => runApp(MyApp());

class MyApp 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: MyHomePage(title: 'Product layout demo home page'),
      );
   }
}
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key);
   final String title;

   @override
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text(this.title),),
         body: Center(child: Text( 'Hello World', )),
      );
   }
}
  • ここに、
  • デフォルトの_StatefulWidget_の代わりに_StatelessWidget_を拡張して_MyHomePage_ウィジェットを作成し、関連するコードを削除しました。
  • 次に、以下に示すように、指定されたデザインに従って、_ProductBox_という新しいウィジェットを作成します-

ProductBox

  • _ProductBox_のコードは次のとおりです。
class ProductBox extends StatelessWidget {
   ProductBox({Key key, this.name, this.description, this.price, this.image})
      : super(key: key);
   final String name;
   final String description;
   final int price;
   final String image;

   Widget build(BuildContext context) {
      return Container(
         padding: EdgeInsets.all(2), height: 120,  child: Card(
            child: Row(
               mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[
                  Image.asset("assets/appimages/" +image), Expanded(
                     child: Container(
                        padding: EdgeInsets.all(5), child: Column(
                           mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                              children: <Widget>[

                              Text(this.name, style: TextStyle(fontWeight:
                                 FontWeight.bold)), Text(this.description),
                              Text("Price: " + this.price.toString()),
                           ],
                        )
                     )
                  )
               ]
            )
         )
      );
   }
}
  • コードで次のことに注意してください-

_ProductBox_は、以下に指定されている4つの引数を使用しています-

  • name-製品名
  • 説明-製品の説明
  • 価格-製品の価格
  • 画像-製品の画像

_ProductBox_は、以下に指定されている7つの組み込みウィジェットを使用します-

  • 容器
  • 拡大
  • Row
  • カラム
  • Card
  • Text
  • 画像

_ProductBox_は、上記のウィジェットを使用して設計されています。 ウィジェットの配置または階層は、以下に示す図で指定されています-

ウィジェットの階層

  • さて、アプリケーションのassetsフォルダに製品情報のダミー画像(以下を参照)を配置し、以下に示すようにpubspec.yamlファイルでassetsフォルダを設定します-
assets:
   - assets/appimages/floppy.png
   - assets/appimages/iphone.png
   - assets/appimages/laptop.png
   - assets/appimages/pendrive.png
   - assets/appimages/pixel.png
   - assets/appimages/tablet.png

iphone

iPhone.png

ピクセル

Pixel.png

ラップトップ

Laptop.png

タブレット

Tablet.png

ペンドライブ

Pendrive.png

フロッピーディスク

Floppy.png

最後に、以下に指定されているように、_MyHomePage_ウィジェットで_ProductBox_ウィジェットを使用します-

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

   @override
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title:Text("Product Listing")),
         body: ListView(
            shrinkWrap: true, padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
            children: <Widget> [
               ProductBox(
                  name: "iPhone",
                  description: "iPhone is the stylist phone ever",
                  price: 1000,
                  image: "iphone.png"
               ),
               ProductBox(
                  name: "Pixel",
                  description: "Pixel is the most featureful phone ever",
                  price: 800,
                  image: "pixel.png"
               ),
               ProductBox(
                  name: "Laptop",
                  description: "Laptop is most productive development tool",
                  price: 2000,
                  image: "laptop.png"
               ),
               ProductBox(
                  name: "Tablet",
                  description: "Tablet is the most useful device ever for meeting",
                  price: 1500,
                  image: "tablet.png"
               ),
               ProductBox(
                  name: "Pendrive",
                  description: "Pendrive is useful storage medium",
                  price: 100,
                  image: "pendrive.png"
               ),
               ProductBox(
                  name: "Floppy Drive",
                  description: "Floppy drive is useful rescue storage medium",
                  price: 20,
                  image: "floppy.png"
               ),
            ],
         )
      );
   }
}
  • ここでは、_ListView_ウィジェットの子として_ProductBox_を使用しました。
  • 製品レイアウトアプリケーション_(product_layout_app)の完全なコード(main.dart)_は次のとおりです-
import 'package:flutter/material.dart';
void main() => runApp(MyApp());

class MyApp 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: MyHomePage(title: 'Product layout demo home page'),
      );
   }
}
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key);
   final String title;

   @override
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text("Product Listing")),
         body: ListView(
            shrinkWrap: true,
            padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
            children: <Widget>[
               ProductBox(
                  name: "iPhone",
                  description: "iPhone is the stylist phone ever",
                  price: 1000,
                  image: "iphone.png"
               ),
               ProductBox(
                  name: "Pixel",
                  description: "Pixel is the most featureful phone ever",
                  price: 800,
                  image: "pixel.png"
               ),
               ProductBox(
                  name: "Laptop",
                  description: "Laptop is most productive development tool",
                  price: 2000,
                  image: "laptop.png"
               ),
               ProductBox(
                  name: "Tablet",
                  description: "Tablet is the most useful device ever for meeting",
                  price: 1500,
                  image: "tablet.png"
               ),
               ProductBox(
                  name: "Pendrive",
                  description: "Pendrive is useful storage medium",
                  price: 100,
                  image: "pendrive.png"
               ),
               ProductBox(
                  name: "Floppy Drive",
                  description: "Floppy drive is useful rescue storage medium",
                  price: 20,
                  image: "floppy.png"
               ),
            ],
         )
      );
   }
}
class ProductBox extends StatelessWidget {
   ProductBox({Key key, this.name, this.description, this.price, this.image}) :
      super(key: key);
   final String name;
   final String description;
   final int price;
   final String image;

   Widget build(BuildContext context) {
      return Container(
         padding: EdgeInsets.all(2),
         height: 120,
         child: Card(
            child: Row(
               mainAxisAlignment: MainAxisAlignment.spaceEvenly,
               children: <Widget>[
                  Image.asset("assets/appimages/" + image),
                  Expanded(
                     child: Container(
                        padding: EdgeInsets.all(5),
                        child: Column(
                           mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                           children: <Widget>[
                              Text(
                                 this.name, style: TextStyle(
                                    fontWeight: FontWeight.bold
                                 )
                              ),
                              Text(this.description), Text(
                                 "Price: " + this.price.toString()
                              ),
                           ],
                        )
                     )
                  )
               ]
            )
         )
      );
   }
}

アプリケーションの最終的な出力は次のとおりです-

製品リスト

フラッター-ジェスチャーの紹介

_ジェスチャー_は、主にユーザーがモバイル(またはタッチベースのデバイス)アプリケーションと対話する方法です。 ジェスチャは、一般に、モバイルデバイスの特定のコントロールをアクティブにすることを目的とした、ユーザーの物理的な動作/動きとして定義されます。 ジェスチャーは、モバイルデバイスの画面をタップして、ゲームアプリケーションで使用されるより複雑なアクションを実行するのと同じくらい簡単です。

広く使用されているジェスチャのいくつかはここに記載されています-

  • タップ-指先で短時間デバイスの表面に触れてから指先を離します。
  • ダブルタップ-短時間で2回タップ。
  • ドラッグ-指先でデバイスの表面に触れてから、安定した方法で指先を動かし、最後に指先を離します。
  • フリック-ドラッグに似ていますが、より高速に実行します。
  • ピンチ-2本の指でデバイスの表面をつまむ。
  • スプレッド/ズーム-ピンチの反対。
  • パンニング-指先でデバイスの表面に触れ、指先を離さずに任意の方向に動かします。

Flutterは、専用のウィジェットである GestureDetector を介して、あらゆる種類のジェスチャに対する優れたサポートを提供します。 GestureDetectorは、主にユーザーのジェスチャーを検出するために使用される非視覚的なウィジェットです。 ウィジェットを対象とするジェスチャを識別するために、ウィジェットをGestureDetectorウィジェット内に配置できます。 GestureDetectorはジェスチャをキャプチャし、ジェスチャに基づいて複数のイベントをディスパッチします。

ジェスチャのいくつかと対応するイベントを以下に示します-

Tap

  • onTapDown
  • onTapUp
  • onTap
  • onTapCancel

ダブルタップ

  • onDoubleTap

長押し

  • onLongPress

垂直ドラッグ

  • onVerticalDragStart
  • onVerticalDragUpdate
  • onVerticalDragEnd

水平ドラッグ

  • onHorizo​​ntalDragStart
  • onHorizo​​ntalDragUpdate
  • onHorizo​​ntalDragEnd

Pan

  • onPanStart
  • onPanUpdate
  • onPanEnd

ここで、hello worldアプリケーションを変更してジェスチャ検出機能を含め、概念を理解してみましょう。

  • 以下に示すように、_MyHomePage_ウィジェットの本文の内容を変更します-
body: Center(
   child: GestureDetector(
      onTap: () {
         _showDialog(context);
      },
      child: Text( 'Hello World', )
   )
),
  • ここで、ウィジェット階層のTextウィジェットの上に_GestureDetector_ウィジェットを配置し、onTapイベントをキャプチャし、最後にダイアログウィンドウを表示したことに注目してください。
  • _showDialog 関数を実装して、ユーザーが_hello world message_をタブで移動したときにダイアログを表示します。 汎用の_showDialog_および_AlertDialog_ウィジェットを使用して、新しいダイアログウィジェットを作成します。 コードは以下に示されています-
//user defined function void _showDialog(BuildContext context) {
  //flutter defined function
   showDialog(
      context: context, builder: (BuildContext context) {
        //return object of type Dialog
         return AlertDialog(
            title: new Text("Message"),
            content: new Text("Hello World"),
            actions: <Widget>[
               new FlatButton(
                  child: new Text("Close"),
                  onPressed: () {
                     Navigator.of(context).pop();
                  },
               ),
            ],
         );
      },
   );
}
  • アプリケーションは、ホットリロード機能を使用してデバイスにリロードします。 今、メッセージ、Hello Worldをクリックするだけで、以下のようなダイアログが表示されます-

ホットリロード機能

  • 次に、ダイアログの_close_オプションをクリックして、ダイアログを閉じます。
  • 完全なコード(main.dart)は次のとおりです-
import 'package:flutter/material.dart';
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  //This widget is the root of your application.
   @override
   Widget build(BuildContext context) {
      return MaterialApp(
         title: 'Hello World Demo Application',
         theme: ThemeData( primarySwatch: Colors.blue,),
         home: MyHomePage(title: 'Home page'),
      );
   }
}
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key);
   final String title;

  //user defined function
   void _showDialog(BuildContext context) {
     //flutter defined function showDialog(
         context: context, builder: (BuildContext context) {
           //return object of type Dialog return AlertDialog(
               title: new Text("Message"),
               content: new Text("Hello World"),
               actions: <Widget>[
                  new FlatButton(
                     child: new Text("Close"),
                     onPressed: () {
                        Navigator.of(context).pop();
                     },
                  ),
               ],
            );
         },
      );
   }
   @override
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text(this.title),),
         body: Center(
            child: GestureDetector(
               onTap: () {
                  _showDialog(context);
               },
            child: Text( 'Hello World', )
            )
         ),
      );
   }
}

最後に、Flutterは_Listener_ウィジェットを介して低レベルのジェスチャー検出メカニズムも提供します。 すべてのユーザーインタラクションを検出し、次のイベントをディスパッチします-

  • PointerDownEvent
  • PointerMoveEvent
  • PointerUpEvent
  • PointerCancelEvent

Flutterは、特定のジェスチャだけでなく高度なジェスチャを実行するための小さなウィジェットセットも提供します。 ウィジェットは以下のとおりです-

  • Dismissible -ウィジェットを閉じるフリックジェスチャをサポートします。
  • ドラッグ可能-ウィジェットを移動するためのドラッグジェスチャーをサポートしています。
  • LongPressDraggable -親ウィジェットもドラッグ可能な場合、ウィジェットを移動するドラッグジェスチャをサポートします。
  • DragTarget -_Draggable_ウィジェットを受け入れます
  • IgnorePointer -ジェスチャ検出プロセスからウィジェットとその子を非表示にします。
  • AbsorbPointer -ジェスチャ検出プロセス自体を停止するため、重複するウィジェットもジェスチャ検出プロセスに参加できないため、イベントは発生しません。
  • スクロール可能-ウィジェット内で利用可能なコンテンツのスクロールをサポートします。

序章

状態管理には、アプリケーション全体の状態変化の追跡が含まれます。

プロバイダーパッケージは、状態管理のニーズに対応する1つのソリューションです。

この記事では、providerをサンプルのFlutterアプリケーションに適用して、ユーザーアカウント情報の状態を管理する方法を学習します。

前提条件

このチュートリアルを完了するには、次のものが必要です。

  • Flutterをダウンロードしてインストールします。
  • Android StudioまたはVisual StudioCodeをダウンロードしてインストールするには。
  • コードエディタ用のプラグインをインストールすることをお勧めします。 AndroidStudio用にインストールされたFlutterおよびDartプラグイン。 VisualStudioCode用にインストールされたFlutter拡張機能。
  • ナビゲーションとルートに精通していることは有益ですが、必須ではありません。
  • フォームの状態に精通していることも有益ですが、必須ではありません。

このチュートリアルは、Flutter v2.0.6、Android SDK v31.0.2、およびAndroidStudiov4.1で検証されました。

問題を理解する

名前などのユーザーのデータを使用して画面の一部をカスタマイズするアプリを作成する場合を考えてみます。 画面間でデータを渡すための通常の方法は、すぐにコールバック、未使用のデータ、および不必要に再構築されたウィジェットの絡み合った混乱になります。 Reactのようなフロントエンドライブラリでは、これはプロップドリルと呼ばれる一般的な問題です。

これらのウィジェットのいずれかからデータを渡したい場合は、未使用のコールバックを増やすことで、すべての中間ウィジェットをさらに膨らませる必要があります。 ほとんどの小さな機能の場合、これにより、ほとんど努力する価値がなくなる可能性があります。

幸いなことに、providerパッケージを使用すると、MaterialAppを初期化する場合と同様に、上位のウィジェットにデータを保存し、サブウィジェットから直接アクセスして変更することができます。ネストし、その間のすべてを再構築せずに。

ステップ1—プロジェクトの設定

Flutter用に環境を設定したら、次のコマンドを実行して新しいアプリケーションを作成できます。

flutter create flutter_provider_example

新しいプロジェクトディレクトリに移動します。

cd flutter_provider_example

flutter createを使用すると、ボタンがクリックされた回数を表示するデモアプリケーションが作成されます。

ステップ2—providerプラグインを追加する

次に、pubspec.yaml内にproviderプラグインを追加する必要があります。

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter

  provider: ^3.1.0

次に、変更をファイルに保存します。

注: VS Codeを使用している場合は、依存関係をすばやく追加するために PubspecAssist拡張機能の使用を検討することをお勧めします。


これで、iOSまたはAndroidシミュレーター、または選択したデバイスでこれを実行できます。

ステップ3—プロジェクトの足場

2つの画面、ルーター、およびナビゲーションバーが必要になります。 アカウントデータを表示するページと、ルーターから保存、変更、受け継がれる状態自体で更新するページを設定しています。

コードエディタでmain.dartを開き、次のコード行を変更します。

lib / main.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import './screens/account.dart';
import './screens/settings.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Provider Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: AccountScreen(), routes: {
      'account_screen': (context) => AccountScreen(),
      'settings_screen': (context) => SettingsScreen(),
    });
  }
}

navbar.dartファイルを作成し、コードエディタで開きます。

lib / navbar.dart

import 'package:flutter/material.dart';
import './screens/account.dart';
import './screens/settings.dart';

class Navbar extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: <Widget>[
          TextButton(
            onPressed: () =>
              Navigator.pushReplacementNamed(context, AccountScreen.id),
            child: Icon(Icons.account_circle, color: Colors.white)
          ),
          TextButton(
            onPressed: () =>
              Navigator.pushReplacementNamed(context, SettingsScreen.id),
            child: Icon(Icons.settings, color: Colors.white)
          ),
        ],
      ),
    );
  }
}

libディレクトリに、新しいscreensサブディレクトリを作成します。

mkdir lib/screens

このサブディレクトリに、settings.dartファイルを作成します。 これは、フォームの状態を作成し、入力を保存するためのマップを設定し、後で使用する送信ボタンを追加するために使用されます。

lib /screens/settings.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../main.dart';
import '../navbar.dart';

class SettingsScreen extends StatelessWidget {
  static const String id = 'settings_screen';

  final formKey = GlobalKey<FormState>();

  final Map data = {'name': String, 'email': String, 'age': int};

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: Navbar(),
      appBar: AppBar(title: Text('Change Account Details')),
      body: Center(
        child: Container(
        padding: EdgeInsets.symmetric(vertical: 20, horizontal: 30),
        child: Form(
          key: formKey,
          child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                TextFormField(
                  decoration: InputDecoration(labelText: 'Name'),
                  onSaved: (input) => data['name'] = input,
                ),
                TextFormField(
                  decoration: InputDecoration(labelText: 'Email'),
                  onSaved: (input) => data['email'] = input,
                ),
                TextFormField(
                  decoration: InputDecoration(labelText: 'Age'),
                  onSaved: (input) => data['age'] = input,
                ),
                TextButton(
                  onPressed: () => formKey.currentState.save(),
                  child: Text('Submit'),
                  style: TextButton.styleFrom(
                    primary: Colors.white,
                    backgroundColor: Colors.blue,
                  ),
                )
              ]
            ),
          ),
        ),
      ),
    );
  }
}

また、このサブディレクトリに、account.dartファイルを作成します。 これは、アカウント情報を表示するために使用されます。

lib /screens/account.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../main.dart';
import '../navbar.dart';

class AccountScreen extends StatelessWidget {
  static const String id = 'account_screen';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: Navbar(),
      appBar: AppBar(
        title: Text('Account Details'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('Name: '),
            Text('Email: '),
            Text('Age: '),
          ],
        ),
      ),
    );
  }
}

コードをコンパイルして、エミュレーターで実行します。

この時点で、アカウント画面と設定画面を備えたアプリケーションができました。

ステップ4—Providerを使用する

providerを設定するには、MaterialAppProviderにデータの種類でラップする必要があります。

main.dartに再度アクセスして、コードエディタで開きます。 このチュートリアルでは、データ型はMapです。 最後に、createを設定して、contextdataを使用する必要があります。

lib / main.dart

// ...

class _MyHomePageState extends State<MyHomePage> {
  Map data = {
    'name': 'Sammy Shark',
    'email': '[email protected]',
    'age': 42
  };

  @override
  Widget build(BuildContext context) {
    return Provider<Map>(
      create: (context) => data,
      child: MaterialApp(home: AccountScreen(), routes: {
        'account_screen': (context) => AccountScreen(),
        'settings_screen': (context) => SettingsScreen(),
      }),
    );
  }
}

dataマップは、main.dartproviderパッケージを呼び出してインポートする他のすべての画面とウィジェットで使用できるようになりました。

Providerクリエーターに渡したものはすべて、Provider.of<Map>(context)で利用できるようになりました。 渡すタイプは、Providerが期待するデータのタイプと一致する必要があることに注意してください。

注: VS Codeを使用している場合は、providerに頻繁にアクセスする可能性があるため、スニペットの使用を検討することをお勧めします。

dart.json

"Provider": {
  "prefix": "provider",
  "body": [
    "Provider.of<$1>(context).$2"
  ]
}

account.dartに再度アクセスして、コードエディタで開きます。 次のコード行を追加します。

lib /screens/account.dart

// ...

body: Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Text('Name: ' + Provider.of<Map>(context)['name'].toString()),
      Text('Email: ' + Provider.of<Map>(context)['email'].toString()),
      Text('Age: ' + Provider.of<Map>(context)['age'].toString()),
    ]),
  ),
)

// ...

コードをコンパイルして、エミュレーターで実行します。

この時点で、アカウント画面に表示されるハードコードされたユーザーデータを含むアプリケーションがあります。

ステップ5—ChangeNotifierを使用する

Providerをこのように使用すると、非常にトップダウンのように見えます。データを渡し、マップを変更したい場合はどうでしょうか。 Providerだけでは十分ではありません。 まず、データをChangeNotifierを拡張する独自のクラスに分割する必要があります。 Providerはそれでは機能しないため、ChangeNotifierProviderに変更し、代わりにDataクラスのインスタンスを渡す必要があります。

これで、単一の変数だけでなく、クラス全体を渡すことになります。これは、データを操作できるメソッドの作成を開始できることを意味します。このメソッドは、Providerにアクセスするすべてのユーザーが利用できます。

グローバルデータのいずれかを変更した後、notifyListenersを使用します。これにより、それに依存するすべてのウィジェットが再構築されます。

main.dartに再度アクセスして、コードエディタで開きます。

lib / main.dart

// ...

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Data>(
      create: (context) => Data(),
      child: MaterialApp(home: AccountScreen(), routes: {
        'account_screen': (context) => AccountScreen(),
        'settings_screen': (context) => SettingsScreen(),
      }),
    );
  }
}

class Data extends ChangeNotifier {
  Map data = {
    'name': 'Sammy Shark',
    'email': '[email protected]',
    'age': 42
  };

  void updateAccount(input) {
    data = input;
    notifyListeners();
  }
}

Providerタイプを変更したため、そのタイプへの呼び出しを更新する必要があります。 account.dartに再度アクセスして、コードエディタで開きます。

lib /screens/account.dart

// ...

body: Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Text('Name: ' + Provider.of<Data>(context).data['name'].toString()),
      Text('Email: ' + Provider.of<Data>(context).data['email'].toString()),
      Text('Age: ' + Provider.of<Data>(context).data['age'].toString()),
    ]),
  ),
)

// ...

データを渡すには、Dataクラスで渡されたメソッドを使用してProviderにアクセスする必要があります。 settings.dartに再度アクセスして、コードエディタで開きます。

lib /screens/settings.dart

TextButton(
  onPressed: () {
    formKey.currentState.save();
    Provider.of<Data>(context, listen: false).updateAccount(data);
    formKey.currentState.reset();
  },
)

コードをコンパイルして、エミュレーターで実行します。

この時点で、[設定]画面でのユーザー情報の更新と[アカウント]画面での変更の表示をサポートするアプリケーションができました。

結論

この記事では、providerをサンプルのFlutterアプリケーションに適用して、ユーザーアカウント情報の状態を管理する方法を学びました。

Flutterについて詳しく知りたい場合は、Flutterトピックページで演習とプログラミングプロジェクトを確認してください。

フラッター-アニメーション

アニメーションは、モバイルアプリケーションでは複雑な手順です。 複雑さにもかかわらず、Animationはユーザーエクスペリエンスを新しいレベルに高め、リッチなユーザーインタラクションを提供します。 その豊富さにより、アニメーションは現代のモバイルアプリケーションの不可欠な部分になります。 Flutterフレームワークは、アニメーションの重要性を認識し、あらゆる種類のアニメーションを開発するためのシンプルで直感的なフレームワークを提供します。

前書き

アニメーションは、特定の時間内に特定の順序で一連の画像/画像を表示して、動きの錯覚を与えるプロセスです。 アニメーションの最も重要な側面は次のとおりです-

  • アニメーションには、開始値と終了値の2つの異なる値があります。 アニメーションは_Start_値から始まり、一連の中間値を通過し、最終的に終了値で終了します。 たとえば、ウィジェットをアニメーション化してフェードアウトさせる場合、初期値は完全な不透明度になり、最終値は不透明度ゼロになります。
  • 中間値は、本質的に線形または非線形(曲線)であり、構成できます。 アニメーションは設定どおりに機能することを理解してください。 各構成は、アニメーションに異なる感触を提供します。 たとえば、ウィジェットのフェードは本質的に線形になりますが、ボールの跳ね返りは本質的に非線形になります。
  • アニメーションプロセスの継続時間は、アニメーションの速度(低速または高速)に影響します。
  • アニメーションの開始、アニメーションの停止、アニメーションの繰り返し回数の設定、アニメーションのプロセスの反転など、アニメーションプロセスを制御する機能。
  • Flutterでは、アニメーションシステムは実際のアニメーションを行いません。 代わりに、画像をレンダリングするためにすべてのフレームで必要な値のみを提供します。

アニメーションベースのクラス

フラッターアニメーションシステムは、アニメーションオブジェクトに基づいています。 コアアニメーションクラスとその使用法は次のとおりです-

アニメーション

特定の期間にわたって2つの数値の間に補間値を生成します。 最も一般的なアニメーションのクラスは-

  • Animation <double> -2つの10進数の間の値を補間します
  • Animation <Color> -2つの色の間の色を補間する
  • Animation <Size> -2つのサイズの間のサイズを補間します
  • AnimationController -アニメーション自体を制御する特別なアニメーションオブジェクト。 アプリケーションが新しいフレームの準備ができるたびに新しい値を生成します。 リニアベースのアニメーションをサポートし、値は0.0から1.0で始まります
controller = AnimationController(duration: const Duration(seconds: 2), vsync: this);

ここで、コントローラーはアニメーションを制御し、持続時間オプションはアニメーションプロセスの持続時間を制御します。 vsyncは、アニメーションで使用されるリソースを最適化するために使用される特別なオプションです。

曲線アニメーション

AnimationControllerに似ていますが、非線形アニメーションをサポートしています。 CurvedAnimationは、以下のようにAnimationオブジェクトとともに使用できます-

controller = AnimationController(duration: const Duration(seconds: 2), vsync: this);
animation = CurvedAnimation(parent: controller, curve: Curves.easeIn)

トゥイーン<T>

Animatable <T>から派生し、0と1以外の2つの数値の間の数値を生成するために使用されます。 animateメソッドを使用して実際のAnimationオブジェクトを渡すことにより、Animationオブジェクトと共に使用できます。

AnimationController controller = AnimationController(
   duration: const Duration(milliseconds: 1000),
vsync: this); Animation<int> customTween = IntTween(
   begin: 0, end: 255).animate(controller);
  • Tweenは、以下のようにCurvedAnimationとともに使用することもできます-
AnimationController controller = AnimationController(
   duration: const Duration(milliseconds: 500), vsync: this);
final Animation curve = CurvedAnimation(parent: controller, curve: Curves.easeOut);
Animation<int> customTween = IntTween(begin: 0, end: 255).animate(curve);

ここで、コントローラーは実際のアニメーションコントローラーです。 curveは非線形性のタイプを提供し、customTweenは0〜255のカスタム範囲を提供します。

Flutter Animationのワークフロー

アニメーションのワークフローは次のとおりです-

  • StatefulWidgetのinitStateでアニメーションコントローラーを定義して開始します。
AnimationController(duration: const Duration(seconds: 2), vsync: this);
animation = Tween<double>(begin: 0, end: 300).animate(controller);
controller.forward();
  • アニメーションベースのリスナー、addListenerを追加して、ウィジェットの状態を変更します。
animation = Tween<double>(begin: 0, end: 300).animate(controller) ..addListener(() {
   setState(() {
     //The state that has changed here is the animation object’s value.
   });
});
  • ビルドインウィジェット、AnimatedWidgetおよびAnimatedBuilderを使用して、このプロセスをスキップできます。 両方のウィジェットはAnimationオブジェクトを受け入れ、アニメーションに必要な現在の値を取得します。
  • ウィジェットのビルドプロセス中にアニメーション値を取得し、元の値の代わりに幅、高さ、または関連するプロパティに適用します。
child: Container(
   height: animation.value,
   width: animation.value,
   child: <Widget>,
)

実用的なアプリケーション

Flutterフレームワークのアニメーションの概念を理解するために、簡単なアニメーションベースのアプリケーションを作成しましょう。

  • Androidスタジオでproduct_animation_appに新しい_Flutter_アプリケーションを作成します。
  • アセットフォルダーをproduct_nav_appからproduct_animation_appにコピーし、pubspec.yamlファイル内にアセットを追加します。
flutter:
   assets:
   - assets/appimages/floppy.png
   - assets/appimages/iphone.png
   - assets/appimages/laptop.png
   - assets/appimages/pendrive.png
   - assets/appimages/pixel.png
   - assets/appimages/tablet.png
  • デフォルトの起動コード(main.dart)を削除します。
  • インポートと基本的なメイン機能を追加します。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
  • StatefulWidgtetから派生したMyAppウィジェットを作成します。
class MyApp extends StatefulWidget {
   _MyAppState createState() => _MyAppState();
}
  • _MyAppStateウィジェットを作成し、デフォルトのビルドメソッドに加えてinitStateを実装して破棄します。
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
   Animation<double> animation;
   AnimationController controller;
   @override void initState() {
      super.initState();
      controller = AnimationController(
         duration: const Duration(seconds: 10), vsync: this
      );
      animation = Tween<double>(begin: 0.0, end: 1.0).animate(controller);
      controller.forward();
   }
  //This widget is the root of your application.
   @override
   Widget build(BuildContext context) {
      controller.forward();
      return MaterialApp(
         title: 'Flutter Demo',
         theme: ThemeData(primarySwatch: Colors.blue,),
         home: MyHomePage(title: 'Product layout demo home page', animation: animation,)
      );
   }
   @override
   void dispose() {
      controller.dispose();
      super.dispose();
   }
}

ここに、

  • initStateメソッドでは、アニメーションコントローラーオブジェクト(コントローラー)、アニメーションオブジェクト(アニメーション)を作成し、controller.forwardを使用してアニメーションを開始しました。
  • disposeメソッドでは、アニメーションコントローラーオブジェクト(コントローラー)を破棄しました。
  • buildメソッドで、コンストラクターを介してMyHomePageウィジェットにアニメーションを送信します。 これで、MyHomePageウィジェットはアニメーションオブジェクトを使用してそのコンテンツをアニメーション化できます。
  • 次に、ProductBoxウィジェットを追加します
class ProductBox extends StatelessWidget {
   ProductBox({Key key, this.name, this.description, this.price, this.image})
      : super(key: key);
   final String name;
   final String description;
   final int price;
   final String image;

   Widget build(BuildContext context) {
      return Container(
         padding: EdgeInsets.all(2),
         height: 140,
         child: Card(
            child: Row(
               mainAxisAlignment: MainAxisAlignment.spaceEvenly,
               children: <Widget>[
                  Image.asset("assets/appimages/" + image),
                  Expanded(
                     child: Container(
                        padding: EdgeInsets.all(5),
                        child: Column(
                           mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                           children: <Widget>[
                              Text(this.name, style:
                                 TextStyle(fontWeight: FontWeight.bold)),
                              Text(this.description),
                                 Text("Price: " + this.price.toString()),
                           ],
                        )
                     )
                  )
               ]
            )
         )
      );
   }
}
  • 新しいウィジェットMyAnimatedWidgetを作成して、不透明度を使用した単純なフェードアニメーションを作成します。
class MyAnimatedWidget extends StatelessWidget {
   MyAnimatedWidget({this.child, this.animation});

   final Widget child;
   final Animation<double> animation;

   Widget build(BuildContext context) => Center(
   child: AnimatedBuilder(
      animation: animation,
      builder: (context, child) => Container(
         child: Opacity(opacity: animation.value, child: child),
      ),
      child: child),
   );
}
  • ここでは、AniatedBuilderを使用してアニメーションを作成しました。 AnimatedBuilderは、同時にアニメーションを実行しながらコンテンツを構築するウィジェットです。 アニメーションオブジェクトを受け入れて、現在のアニメーション値を取得します。 アニメーション値、animation.valueを使用して、子ウィジェットの不透明度を設定しました。 実際には、ウィジェットは不透明度の概念を使用して子ウィジェットをアニメーション化します。
  • 最後に、MyHomePageウィジェットを作成し、アニメーションオブジェクトを使用してそのコンテンツをアニメーション化します。
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title, this.animation}) : super(key: key);

   final String title;
   final Animation<double>
   animation;

   @override
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text("Product Listing")),body: ListView(
            shrinkWrap: true,
            padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
            children: <Widget>[
               FadeTransition(
                  child: ProductBox(
                     name: "iPhone",
                     description: "iPhone is the stylist phone ever",
                     price: 1000,
                     image: "iphone.png"
                  ), opacity: animation
               ),
               MyAnimatedWidget(child: ProductBox(
                  name: "Pixel",
                  description: "Pixel is the most featureful phone ever",
                  price: 800,
                  image: "pixel.png"
               ), animation: animation),
               ProductBox(
                  name: "Laptop",
                  description: "Laptop is most productive development tool",
                  price: 2000,
                  image: "laptop.png"
               ),
               ProductBox(
                  name: "Tablet",
                  description: "Tablet is the most useful device ever for meeting",
                  price: 1500,
                  image: "tablet.png"
               ),
               ProductBox(
                  name: "Pendrive",
                  description: "Pendrive is useful storage medium",
                  price: 100,
                  image: "pendrive.png"
               ),
               ProductBox(
                  name: "Floppy Drive",
                  description: "Floppy drive is useful rescue storage medium",
                  price: 20,
                  image: "floppy.png"
               ),
            ],
         )
      );
   }
}

ここでは、FadeAnimationとMyAnimationWidgetを使用して、リストの最初の2つのアイテムをアニメーション化しました。 FadeAnimationはビルドインアニメーションクラスであり、不透明度の概念を使用して子をアニメーション化するために使用しました。

  • 完全なコードは次のとおりです-
import 'package:flutter/material.dart';
void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
   _MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
   Animation<double> animation;
   AnimationController controller;

   @override
   void initState() {
      super.initState();
      controller = AnimationController(
         duration: const Duration(seconds: 10), vsync: this);
      animation = Tween<double>(begin: 0.0, end: 1.0).animate(controller);
      controller.forward();
   }
  //This widget is the root of your application.
   @override
   Widget build(BuildContext context) {
      controller.forward();
      return MaterialApp(
         title: 'Flutter Demo', theme: ThemeData(primarySwatch: Colors.blue,),
         home: MyHomePage(title: 'Product layout demo home page', animation: animation,)
      );
   }
   @override
   void dispose() {
      controller.dispose();
      super.dispose();
   }
}
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title, this.animation}): super(key: key);
   final String title;
   final Animation<double> animation;

   @override
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text("Product Listing")),
         body: ListView(
            shrinkWrap: true,
            padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
            children: <Widget>[
               FadeTransition(
                  child: ProductBox(
                     name: "iPhone",
                     description: "iPhone is the stylist phone ever",
                     price: 1000,
                     image: "iphone.png"
                  ),
                  opacity: animation
               ),
               MyAnimatedWidget(
                  child: ProductBox(
                     name: "Pixel",
                     description: "Pixel is the most featureful phone ever",
                     price: 800,
                     image: "pixel.png"
                  ),
                  animation: animation
               ),
               ProductBox(
                  name: "Laptop",
                  description: "Laptop is most productive development tool",
                  price: 2000,
                  image: "laptop.png"
               ),
               ProductBox(
                  name: "Tablet",
                  description: "Tablet is the most useful device ever for meeting",
                  price: 1500,
                  image: "tablet.png"
               ),
               ProductBox(
                  name: "Pendrive",
                  description: "Pendrive is useful storage medium",
                  price: 100,
                  image: "pendrive.png"
               ),
               ProductBox(
                  name: "Floppy Drive",
                  description: "Floppy drive is useful rescue storage medium",
                  price: 20,
                  image: "floppy.png"
               ),
            ],
         )
      );
   }
}
class ProductBox extends StatelessWidget {
   ProductBox({Key key, this.name, this.description, this.price, this.image}) :
      super(key: key);
   final String name;
   final String description;
   final int price;
   final String image;
   Widget build(BuildContext context) {
      return Container(
         padding: EdgeInsets.all(2),
         height: 140,
         child: Card(
            child: Row(
               mainAxisAlignment: MainAxisAlignment.spaceEvenly,
               children: <Widget>[
                  Image.asset("assets/appimages/" + image),
                  Expanded(
                     child: Container(
                        padding: EdgeInsets.all(5),
                        child: Column(
                           mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                           children: <Widget>[
                              Text(
                                 this.name, style: TextStyle(
                                    fontWeight: FontWeight.bold
                                 )
                              ),
                              Text(this.description), Text(
                                 "Price: " + this.price.toString()
                              ),
                           ],
                        )
                     )
                  )
               ]
            )
         )
      );
   }
}
class MyAnimatedWidget extends StatelessWidget {
   MyAnimatedWidget({this.child, this.animation});
   final Widget child;
   final Animation<double> animation;

   Widget build(BuildContext context) => Center(
      child: AnimatedBuilder(
         animation: animation,
         builder: (context, child) => Container(
            child: Opacity(opacity: animation.value, child: child),
         ),
         child: child
      ),
   );
}
  • アプリケーションをコンパイルして実行し、結果を確認します。 アプリケーションの初期および最終バージョンは次のとおりです-

初期バージョン

最終バージョン

Flutter-Android固有のコードを書く

Flutterは、プラットフォーム固有の機能にアクセスするための一般的なフレームワークを提供します。 これにより、開発者はプラットフォーム固有のコードを使用して_Flutter_フレームワークの機能を拡張できます。 フレームワークを介して、カメラ、バッテリーレベル、ブラウザなどのプラットフォーム固有の機能に簡単にアクセスできます。

プラットフォーム固有のコードにアクセスする一般的な考え方は、単純なメッセージングプロトコルを使用することです。 フラッターコード、クライアント、プラットフォームコード、およびホストは、共通のメッセージチャネルにバインドします。 クライアントは、メッセージチャネルを介してホストにメッセージを送信します。 ホストはメッセージチャネルをリッスンし、メッセージを受信して​​必要な機能を実行し、最後にメッセージチャネルを介してクライアントに結果を返します。

プラットフォーム固有のコードアーキテクチャは、以下のブロック図に示されています-

特定のコードアーキテクチャ

メッセージングプロトコルは、数値、文字列、ブール値などのJSONのような値のバイナリシリアル化をサポートする標準メッセージコーデック(StandardMessageCodecクラス)を使用します。シリアル化と逆シリアル化は、クライアントとホストの間で透過的に機能します。

_Android SDK_を使用してブラウザを開く簡単なアプリケーションを作成し、その方法を理解しましょう

  • Androidスタジオで新しいFlutterアプリケーション_flutter_browser_app_を作成します
  • main.dartコードを以下のコードに置き換えます-
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
      return MaterialApp(
         title: 'Flutter Demo',
         theme: ThemeData(
            primarySwatch: Colors.blue,
         ),
         home: MyHomePage(title: 'Flutter Demo Home Page'),
      );
   }
}
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key);
   final String title;

   @override
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(
            title: Text(this.title),
         ),
         body: Center(
            child: RaisedButton(
               child: Text('Open Browser'),
               onPressed: null,
            ),
         ),
      );
   }
}
  • ここでは、ブラウザーを開いてそのonPressedメソッドをnullに設定する新しいボタンを作成しました。
  • 今、次のパッケージをインポートします-
import 'dart:async';
import 'package:flutter/services.dart';
  • ここで、services.dartには、プラットフォーム固有のコードを呼び出す機能が含まれています。
  • MyHomePageウィジェットで新しいメッセージチャネルを作成します。
static const platform = const
MethodChannel('flutterapp.finddevguides.com/browser');
  • プラットフォーム固有のメソッドである_openBrowserメソッド、メッセージチャネルを介したopenBrowserメソッドを呼び出すメソッドを記述します。
Future<void> _openBrowser() async {
   try {
      final int result = await platform.invokeMethod(
         'openBrowser', <String, String>{
            'url': "https://flutter.dev"
         }
      );
   }
   on PlatformException catch (e) {
     //Unable to open the browser
      print(e);
   }
}

ここでは、platform.invokeMethodを使用してopenBrowserを呼び出しています(次の手順で説明します)。 openBrowserには、特定のURLを開くための引数urlがあります。

  • RaisedButtonのonPressedプロパティの値をnullから_openBrowserに変更します。
onPressed: _openBrowser,
  • MainActivity.java(androidフォルダ内)を開き、必要なライブラリをインポートします-
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;

import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugins.GeneratedPluginRegistrant;
  • メソッドを作成し、ブラウザを開くopenBrowser
private void openBrowser(MethodCall call, Result result, String url) {
   Activity activity = this;
   if (activity == null) {
      result.error("ACTIVITY_NOT_AVAILABLE",
      "Browser cannot be opened without foreground
      activity", null);
      return;
   }
   Intent intent = new Intent(Intent.ACTION_VIEW);
   intent.setData(Uri.parse(url));

   activity.startActivity(intent);
   result.success((Object) true);
}
  • 今、MainActivityクラスでチャンネル名を設定します-
private static final String CHANNEL = "flutterapp.finddevguides.com/browser";
  • onCreateメソッドでメッセージ処理を設定するAndroid固有のコードを記述します-
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
   new MethodCallHandler() {
   @Override
   public void onMethodCall(MethodCall call, Result result) {
      String url = call.argument("url");
      if (call.method.equals("openBrowser")) {
         openBrowser(call, result, url);
      } else {
         result.notImplemented();
      }
   }
});

ここでは、MethodChannelクラスを使用してメッセージチャネルを作成し、MethodCallHandlerクラスを使用してメッセージを処理しました。 onMethodCallは、メッセージを確認することで正しいプラットフォーム固有のコードを呼び出す実際のメソッドです。 onMethodCallメソッドは、メッセージからURLを抽出し、メソッド呼び出しがopenBrowserの場合にのみopenBrowserを呼び出します。 それ以外の場合、notImplementedメソッドを返します。

アプリケーションの完全なソースコードは次のとおりです-

*main.dart*
*MainActivity.java*
package com.finddevguides.flutterapp.flutter_browser_app;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {
   private static final String CHANNEL = "flutterapp.finddevguides.com/browser";
   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      GeneratedPluginRegistrant.registerWith(this);
      new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
         new MethodCallHandler() {
            @Override
            public void onMethodCall(MethodCall call, Result result) {
               String url = call.argument("url");
               if (call.method.equals("openBrowser")) {
                  openBrowser(call, result, url);
               } else {
                  result.notImplemented();
               }
            }
         }
      );
   }
   private void openBrowser(MethodCall call, Result result, String url) {
      Activity activity = this; if (activity == null) {
         result.error(
            "ACTIVITY_NOT_AVAILABLE", "Browser cannot be opened without foreground activity", null
         );
         return;
      }
      Intent intent = new Intent(Intent.ACTION_VIEW);
      intent.setData(Uri.parse(url));
      activity.startActivity(intent);
      result.success((Object) true);
   }
}
*main.dart*
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';

void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
      return MaterialApp(
         title: 'Flutter Demo',
         theme: ThemeData(
            primarySwatch: Colors.blue,
         ),
         home: MyHomePage(
            title: 'Flutter Demo Home Page'
         ),
      );
   }
}
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key);
   final String title;
   static const platform = const MethodChannel('flutterapp.finddevguides.com/browser');
   Future<void> _openBrowser() async {
      try {
         final int result = await platform.invokeMethod('openBrowser', <String, String>{
            'url': "https://flutter.dev"
         });
      }
      on PlatformException catch (e) {
        //Unable to open the browser print(e);
      }
   }
   @override
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(
            title: Text(this.title),
         ),
         body: Center(
            child: RaisedButton(
               child: Text('Open Browser'),
               onPressed: _openBrowser,
            ),
         ),
      );
   }
}

アプリケーションを実行し、「ブラウザーを開く」ボタンをクリックすると、ブラウザーが起動していることがわかります。 ブラウザアプリ-ホームページは、ここのスクリーンショットに示すとおりです-

Flutter Demo Home Page

プロダクティブビルドアプリ

Flutter-IOS固有のコードの記述

iOS固有のコードへのアクセスは、iOS固有の言語(Objective-CまたはSwiftとiOS SDK)を使用することを除いて、Androidプラットフォームのコードに似ています。 それ以外の場合、概念はAndroidプラットフォームの概念と同じです。

iOSプラットフォームについても、前の章と同じアプリケーションを作成してみましょう。

  • Android Studio(macOS)で新しいアプリケーション_flutter_browser_ios_app_を作成しましょう
  • 前の章のように、手順2〜6に従います。
  • XCodeを起動し、[ファイル]→[開く]をクリックします*
  • flutterプロジェクトのiosディレクトリの下にあるxcodeプロジェクトを選択します。
  • *ランナー→ランナーパス*でAppDelegate.mを開きます。 次のコードが含まれています-
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
   didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
     //[GeneratedPluginRegistrant registerWithRegistry:self];
     //Override point for customization after application launch.
      return [super application:application didFinishLaunchingWithOptions:launchOptions];
   }
@end
*指定されたURLでブラウザーを開くメソッドopenBrowserを追加しました。 単一の引数urlを受け入れます。
- (void)openBrowser:(NSString* )urlString {
   NSURL *url = [NSURL URLWithString:urlString];
   UIApplication *application = [UIApplication sharedApplication];
   [application openURL:url];
}
  • didFinishLaunchingWithOptionsメソッドで、コントローラーを見つけてコントローラー変数に設定します。
FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
  • didFinishLaunchingWithOptionsメソッドで、ブラウザチャネルをflutterapp.finddevguides.com/browseに設定します-
FlutterMethodChannel* browserChannel = [
   FlutterMethodChannel methodChannelWithName:
   @"flutterapp.finddevguides.com/browser" binaryMessenger:controller];
  • 変数、weakSelfを作成し、現在のクラスを設定します-
__weak typeof(self) weakSelf = self;
  • 次に、setMethodCallHandlerを実装します。 call.methodに一致させてopenBrowserを呼び出します。 call.argumentsを呼び出してURLを取得し、openBrowserの呼び出し中にそれを渡します。
[browserChannel setMethodCallHandler:^(FlutterMethodCall *call, FlutterResult result) {
   if ([@"openBrowser" isEqualToString:call.method]) {
      NSString* url = call.arguments[@"url"];
      [weakSelf openBrowser:url];
   } else { result(FlutterMethodNotImplemented); }
}];
*完全なコードは次のとおりです-
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
@implementation AppDelegate

- (BOOL)application:(UIApplication* )application
   didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

  //custom code starts
   FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
   FlutterMethodChannel* browserChannel = [
      FlutterMethodChannel methodChannelWithName:
      @"flutterapp.finddevguides.com/browser" binaryMessenger:controller];

   __weak typeof(self) weakSelf = self;
   [browserChannel setMethodCallHandler:^(
      FlutterMethodCall *call, FlutterResult result) {

      if ([@"openBrowser" isEqualToString:call.method]) {
         NSString* url = call.arguments[@"url"];
         [weakSelf openBrowser:url];
      } else { result(FlutterMethodNotImplemented); }
   }];
  //custom code ends
   [GeneratedPluginRegistrant registerWithRegistry:self];

  //Override point for customization after application launch.
   return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)openBrowser:(NSString *)urlString {
   NSURL *url = [NSURL URLWithString:urlString];
   UIApplication *application = [UIApplication sharedApplication];
   [application openURL:url];
}
@end
  • プロジェクト設定を開きます。
  • *機能*に移動し、*バックグラウンドモード*を有効にします。
  • バックグラウンドフェッチ*および*リモート通知*を追加します。
  • 次に、アプリケーションを実行します。 Androidバージョンと同様に機能しますが、クロムではなくSafariブラウザーが開きます。

Flutter-パッケージの概要

Dartの一連の機能を整理および共有する方法は、パッケージを使用することです。 Dartパッケージは、単に共有可能なライブラリまたはモジュールです。 一般に、DartパッケージはDartアプリケーションと同じですが、Dartパッケージにはアプリケーションエントリポイントmainがありません。

パッケージの一般的な構造(デモパッケージmy_demo_packageを検討してください)は以下のとおりです-

  • lib/src/ *-プライベートDartコードファイル。
  • lib/my_demo_package.dart -メインDartコードファイル。 次のようにアプリケーションにインポートできます-
import 'package:my_demo_package/my_demo_package.dart'
  • 以下に示すように、必要に応じて、他のプライベートコードファイルをメインコードファイル(my_demo_package.dart)にエクスポートできます-
export src/my_private_code.dart
  • lib/ *-任意のカスタムフォルダー構造に配置された任意の数のDartコードファイル。 コードには、次のようにアクセスできます。
import 'package:my_demo_package/custom_folder/custom_file.dart'
  • pubspec.yaml -アプリケーションと同じプロジェクト仕様、

パッケージ内のすべてのDartコードファイルは単にDartクラスであり、Dartコードをパッケージに含めるための特別な要件はありません。

パッケージの種類

Dartパッケージは基本的に同様の機能の小さなコレクションであるため、機能に基づいて分類できます。

ダーツパッケージ

Web環境とモバイル環境の両方で使用できる汎用Dartコード。 たとえば、english_wordsは約5000の単語を含むパッケージの1つで、名詞(英語の名詞をリストする)、音節(単語の音節の数を指定する)などの基本的なユーティリティ機能があります。

フラッターパッケージ

Flutterフレームワークに依存し、モバイル環境でのみ使用できる汎用Dartコード。 たとえば、fluroはフラッター用のカスタムルーターです。 Flutterフレームワークに依存します。

Flutterプラグイン

一般的なDartコード。Flutterフレームワークと、基盤となるプラットフォームコード(Android SDKまたはiOS SDK)に依存します。 たとえば、カメラはデバイスのカメラと対話するためのプラグインです。 Flutterフレームワークとカメラにアクセスするための基礎となるフレームワークに依存します。

Dartパッケージを使用する

Dartパッケージはホストされ、ライブサーバーhttps://pub.dev [[[3]]]に公開されます。また、Flutterは、アプリケーションでDartパッケージを管理するための単純なツールpubを提供します。 パッケージとして使用するために必要な手順は次のとおりです-

  • 以下に示すように、pubspec.yamlに必要なパッケージ名とバージョンを含めます-
dependencies: english_words: ^3.1.5
  • 最新のバージョン番号は、オンラインサーバーを確認することで確認できます。
  • 次のコマンドを使用して、アプリケーションにパッケージをインストールします-
flutter packages get
  • Androidスタジオでの開発中に、Android Studioはpubspec.yamlの変更を検出し、以下に示すように開発者にAndroidスタジオパッケージアラートを表示します-

パッケージアラート

  • Dartパッケージは、メニューオプションを使用してAndroid Studioにインストールまたは更新できます。
  • 以下に示すコマンドを使用して必要なファイルをインポートし、作業を開始します-
import 'package:english_words/english_words.dart';
  • パッケージで利用可能な任意の方法を使用し、
nouns.take(50).forEach(print);
  • ここでは、名詞関数を使用して上位50ワードを取得および印刷しました。

Flutterプラグインパッケージを開発する

Flutterプラグインの開発は、DartアプリケーションまたはDartパッケージの開発に似ています。 唯一の例外は、プラグインがシステムAPI(AndroidまたはiOS)を使用して、必要なプラットフォーム固有の機能を取得することです。

前の章でプラットフォームコードにアクセスする方法を既に学習したので、プラグイン開発プロセスを理解するために単純なプラグインmy_browserを開発しましょう。 my_browserプラグインの機能は、アプリケーションがプラットフォーム固有のブラウザーで特定のWebサイトを開くことを許可することです。

  • Android Studioを起動します。
  • [ファイル]→[新しいFlutterプロジェクト]をクリックし、[Flutterプラグイン]オプションを選択します。
  • ここに示すように、Flutterプラグインの選択ウィンドウが表示されます-

フラッタープラグイン

  • プロジェクト名としてmy_browserを入力し、[次へ]をクリックします。
  • ここに示すように、ウィンドウにプラグイン名とその他の詳細を入力します-

新しいFlutterプラグインの構成

  • 下のウィンドウに会社のドメインflutterplugins.finddevguides.comを入力し、[*完了]をクリックします。 新しいプラグインを開発するためのスタートアップコードが生成されます。

パッケージ名

  • my_browser.dartファイルを開き、メソッドopenBrowserを記述して、プラットフォーム固有のopenBrowserメソッドを呼び出します。
Future<void> openBrowser(String urlString) async {
   try {
      final int result = await _channel.invokeMethod(
         'openBrowser', <String, String>{ 'url': urlString }
      );
   }
   on PlatformException catch (e) {
     //Unable to open the browser print(e);
   }
}
  • MyBrowserPlugin.javaファイルを開き、次のクラスをインポートします-
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
  • ここでは、Androidからブラウザを開くために必要なライブラリをインポートする必要があります。
  • MyBrowserPluginクラスにRegistrarタイプの新しいプライベート変数mRegistrarを追加します。
private final Registrar mRegistrar;
  • ここでは、Registrarを使用して、呼び出し元コードのコンテキスト情報を取得します。
  • MyBrowserPluginクラスにRegistrarを設定するコンストラクターを追加します。
private MyBrowserPlugin(Registrar registrar) {
   this.mRegistrar = registrar;
}
  • registerWithを変更して、MyBrowserPluginクラスに新しいコンストラクターを含めます。
public static void registerWith(Registrar registrar) {
   final MethodChannel channel = new MethodChannel(registrar.messenger(), "my_browser");
   MyBrowserPlugin instance = new MyBrowserPlugin(registrar);
   channel.setMethodCallHandler(instance);
}
  • onBrowseCallメソッドを変更して、myBrowserPluginクラスにopenBrowserメソッドを含めます。
@Override
public void onMethodCall(MethodCall call, Result result) {
   String url = call.argument("url");
   if (call.method.equals("getPlatformVersion")) {
      result.success("Android " + android.os.Build.VERSION.RELEASE);
   }
   else if (call.method.equals("openBrowser")) {
      openBrowser(call, result, url);
   } else {
      result.notImplemented();
   }
}
  • プラットフォーム固有のopenBrowserメソッドを記述して、MyBrowserPluginクラスのブラウザーにアクセスします。
private void openBrowser(MethodCall call, Result result, String url) {
   Activity activity = mRegistrar.activity();
   if (activity == null) {
      result.error("ACTIVITY_NOT_AVAILABLE",
      "Browser cannot be opened without foreground activity", null);
      return;
   }
   Intent intent = new Intent(Intent.ACTION_VIEW);
   intent.setData(Uri.parse(url));
   activity.startActivity(intent);
   result.success((Object) true);
}
  • my_browserプラグインの完全なソースコードは次のとおりです-

    *my_browser.dart*
import 'dart:async';
import 'package:flutter/services.dart';

class MyBrowser {
   static const MethodChannel _channel = const MethodChannel('my_browser');
   static Future<String> get platformVersion async {
      final String version = await _channel.invokeMethod('getPlatformVersion'); return version;
   }
   Future<void> openBrowser(String urlString) async {
      try {
         final int result = await _channel.invokeMethod(
            'openBrowser', <String, String>{'url': urlString});
      }
      on PlatformException catch (e) {
        //Unable to open the browser print(e);
      }
   }
}
*MyBrowserPlugin.java*
package com.finddevguides.flutterplugins.my_browser;

import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;

/* *MyBrowserPlugin*/
public class MyBrowserPlugin implements MethodCallHandler {
   private final Registrar mRegistrar;
   private MyBrowserPlugin(Registrar registrar) {
      this.mRegistrar = registrar;
   }
  /* *Plugin registration.*/
   public static void registerWith(Registrar registrar) {
      final MethodChannel channel = new MethodChannel(
         registrar.messenger(), "my_browser");
      MyBrowserPlugin instance = new MyBrowserPlugin(registrar);
      channel.setMethodCallHandler(instance);
   }
   @Override
   public void onMethodCall(MethodCall call, Result result) {
      String url = call.argument("url");
      if (call.method.equals("getPlatformVersion")) {
         result.success("Android " + android.os.Build.VERSION.RELEASE);
      }
      else if (call.method.equals("openBrowser")) {
         openBrowser(call, result, url);
      } else {
         result.notImplemented();
      }
   }
   private void openBrowser(MethodCall call, Result result, String url) {
      Activity activity = mRegistrar.activity();
      if (activity == null) {
         result.error("ACTIVITY_NOT_AVAILABLE",
            "Browser cannot be opened without foreground activity", null);
         return;
      }
      Intent intent = new Intent(Intent.ACTION_VIEW);
      intent.setData(Uri.parse(url));
      activity.startActivity(intent);
      result.success((Object) true);
   }
}
  • 新しいプロジェクト_my_browser_plugin_test_を作成して、新しく作成したプラグインをテストします。
  • pubspec.yamlを開き、my_browserをプラグインの依存関係として設定します。
dependencies:
   flutter:
      sdk: flutter
   my_browser:
      path: ../my_browser
  • Androidスタジオは、pubspec.yamlが以下のAndroidスタジオパッケージアラートに示されているように更新されたことを警告します-

Android Studioパッケージアラート

  • [依存関係の取得]オプションをクリックします。 Androidスタジオはインターネットからパッケージを取得し、アプリケーション用に適切に構成します。
  • main.dartを開き、以下のようにmy_browserプラグインを含めます-
import 'package:my_browser/my_browser.dart';
  • 以下に示すように、my_browserプラグインからopenBrowser関数を呼び出します-
onPressed: () => MyBrowser().openBrowser("https://flutter.dev"),
  • main.dartの完全なコードは次のとおりです-
import 'package:flutter/material.dart';
import 'package:my_browser/my_browser.dart';

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

class MyApp extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
      return MaterialApp(
         title: 'Flutter Demo',
         theme: ThemeData(
            primarySwatch: Colors.blue,
         ),
         home: MyHomePage(
            title: 'Flutter Demo Home Page'
         ),
      );,
   }
}
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key);
   final String title;
   @override
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(
            title: Text(this.title),
         ),
         body: Center(
            child: RaisedButton(
               child: Text('Open Browser'),
               onPressed: () => MyBrowser().openBrowser("https://flutter.dev"),
            ),
         ),
      );
   }
}
  • アプリケーションを実行し、「ブラウザーを開く」ボタンをクリックして、ブラウザーが起動することを確認します。 あなたは、ブラウザアプリを見ることができます-以下に示すスクリーンショットに示すようにホームページ-

ブラウザを開く

あなたは、ブラウザアプリを見ることができます-下に示すスクリーンショットに示されているブラウザ画面-

フラッターインフラストラクチャ

Flutter-REST APIへのアクセス

Flutterは、HTTPリソースを消費するhttpパッケージを提供します。 httpはFutureベースのライブラリであり、awaitおよびasync機能を使用します。 多くの高レベルのメソッドを提供し、RESTベースのモバイルアプリケーションの開発を簡素化します。

基本概念

httpパッケージは、Web要求を行うための高レベルのクラスとhttpを提供します。

  • httpクラスは、すべてのタイプのHTTP要求を実行する機能を提供します。
  • httpメソッドは、URL、およびDart Mapを介した追加情報(投稿データ、追加ヘッダーなど)を受け入れます。 サーバーを要求し、async/awaitパターンで応答を収集します。 たとえば、次のコードは、指定されたURLからデータを読み取り、コンソールに出力します。
print(await http.read('https://flutter.dev/'));

コアメソッドのいくつかは次のとおりです-

  • 読み取り-GETメソッドを使用して指定されたURLを要求し、Future <String>として応答を返します
  • get -GETメソッドを使用して指定されたURLを要求し、Future <Response>として応答を返します。 応答は、応答情報を保持するクラスです。
  • post -指定されたURLをPOSTメソッドで要求し、提供されたデータをポストして、Future <Response>として応答を返します。
  • put -PUTメソッドを介して指定されたURLを要求し、Future <Response>として応答を返します
  • head -HEADメソッドで指定されたURLを要求し、Future <Response>として応答を返します
  • delete -DELETEメソッドを使用して指定されたURLを要求し、Future <Response>として応答を返します

httpは、より標準的なHTTPクライアントクラスであるclientも提供します。 クライアントは永続的な接続をサポートします。 特定のサーバーに対して大量の要求が行われる場合に役立ちます。 closeメソッドを使用して適切に閉じる必要があります。 それ以外の場合は、httpクラスに似ています。 サンプルコードは次のとおりです-

var client = new http.Client();
try {
   print(await client.get('https://flutter.dev/'));
}
finally {
   client.close();
}

製品サービスAPIへのアクセス

Webサーバーから製品データを取得する単純なアプリケーションを作成し、_ListView_を使用して製品を表示します。

  • Androidスタジオで_product_rest_app_に新しい_Flutter_アプリケーションを作成します。
  • デフォルトの起動コード(main.dart)を_product_nav_app_コードに置き換えます。
  • アセットフォルダーを_product_nav_app_から_product_rest_app_にコピーし、pubspec.yamlファイル内にアセットを追加します。
flutter:
   assets:
      - assets/appimages/floppy.png
      - assets/appimages/iphone.png
      - assets/appimages/laptop.png
      - assets/appimages/pendrive.png
      - assets/appimages/pixel.png
      - assets/appimages/tablet.png
  • 以下に示すように、pubspec.yamlファイルでhttpパッケージを構成します-
dependencies:
   http: ^0.12.0+2
  • ここでは、httpパッケージの最新バージョンを使用します。 Androidスタジオは、pubspec.yamlが更新されたことを示すパッケージアラートを送信します。

最新バージョン

  • [依存関係の取得]オプションをクリックします。 Androidスタジオはインターネットからパッケージを取得し、アプリケーション用に適切に構成します。
  • main.dartファイルにhttpパッケージをインポートします-
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
  • 以下に示すように、製品情報を持つ新しいJSONファイル、products.jsonを作成します-
[
   {
      "name": "iPhone",
      "description": "iPhone is the stylist phone ever",
      "price": 1000,
      "image": "iphone.png"
   },
   {
      "name": "Pixel",
      "description": "Pixel is the most feature phone ever",
      "price": 800,
      "image": "pixel.png"
   },
   {
      "name": "Laptop",
      "description": "Laptop is most productive development tool",
      "price": 2000,
      "image": "laptop.png"
   },
   {
      "name": "Tablet",
      "description": "Tablet is the most useful device ever for meeting",
      "price": 1500,
      "image": "tablet.png"
   },
   {
      "name": "Pendrive",
      "description": "Pendrive is useful storage medium",
      "price": 100,
      "image": "pendrive.png"
   },
   {
      "name": "Floppy Drive",
      "description": "Floppy drive is useful rescue storage medium",
      "price": 20,
      "image": "floppy.png"
   }
]

新しいフォルダーJSONWebServerを作成し、JSONファイルproducts.jsonを配置します。

JSONWebServerをルートディレクトリとしてWebサーバーを実行し、Webパスを取得します。 たとえば、http://192.168.184.1:8000/products.json。 apache、nginxなどの任意のWebサーバーを使用できます。

最も簡単な方法は、ノードベースのhttpサーバーアプリケーションをインストールすることです。 http-serverアプリケーションをインストールして実行するには、以下の手順に従ってください

  • Nodejsアプリケーションをインストールします(https://nodejs.org/en/[nodejs.org])
  • JSONWebServerフォルダーに移動します。
cd/path/to/JSONWebServer
  • npmを使用してhttp-serverパッケージをインストールします。
npm install -g http-server
  • 次に、サーバーを実行します。
http-server . -p 8000

Starting up http-server, serving .
Available on:
   http://192.168.99.1:8000
   http://127.0.0.1:8000
   Hit CTRL-C to stop the server
  • libフォルダーに新しいファイルProduct.dartを作成し、Productクラスをその中に移動します。
  • ProductクラスのファクトリコンストラクタProduct.fromMapを記述して、マップされたデータMapをProductオブジェクトに変換します。 通常、JSONファイルはDart Mapオブジェクトに変換されてから、関連するオブジェクト(製品)に変換されます。
factory Product.fromJson(Map<String, dynamic> data) {
   return Product(
      data['name'],
      data['description'],
      data['price'],
      data['image'],
   );
}
  • Product.dartの完全なコードは次のとおりです-
class Product {
   final String name;
   final String description;
   final int price;
   final String image;

   Product(this.name, this.description, this.price, this.image);
   factory Product.fromMap(Map<String, dynamic> json) {
      return Product(
         json['name'],
         json['description'],
         json['price'],
         json['image'],
      );
   }
}
  • メインクラスに2つのメソッドparseProductsとfetchProductsを記述して、WebサーバーからList <Product>オブジェクトに製品情報をフェッチしてロードします。
List<Product> parseProducts(String responseBody) {
   final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
   return parsed.map<Product>((json) =>Product.fromJson(json)).toList();
}
Future<List<Product>> fetchProducts() async {
   final response = await http.get('http://192.168.1.2:8000/products.json');
   if (response.statusCode == 200) {
      return parseProducts(response.body);
   } else {
      throw Exception('Unable to fetch products from the REST API');
   }
}

ここで次の点に注意してください-

  • Futureは、製品情報を遅延ロードするために使用されます。 遅延読み込みは、必要になるまでコードの実行を延期する概念です。
  • http.getは、インターネットからデータを取得するために使用されます。
  • json.decodeは、JSONデータをDart Mapオブジェクトにデコードするために使用されます。 JSONデータがデコードされると、ProductクラスのfromMapを使用してList <Product>に変換されます。
  • MyAppクラスで、新しいメンバー変数、タイプFuture <Product>の製品を追加し、コンストラクターに含めます。
class MyApp extends StatelessWidget {
   final Future<List<Product>> products;
   MyApp({Key key, this.products}) : super(key: key);
   ...
  • MyHomePageクラスで、Future <Product>型の新しいメンバー変数製品を追加し、コンストラクターに含めます。 また、items変数とその関連メソッドであるgetProductsメソッド呼び出しを削除します。 製品変数をコンストラクターに配置します。 アプリケーションを最初に起動したときに一度だけインターネットから製品を取得できます。
class MyHomePage extends StatelessWidget {
   final String title;
   final Future<ListList<Product>> products;
   MyHomePage({Key key, this.title, this.products}) : super(key: key);
   ...
  • 上記の変更に対応するために、MyAppウィジェットのビルドメソッドでホームオプション(MyHomePage)を変更します-
home: MyHomePage(title: 'Product Navigation demo home page', products: products),
  • Future <Product>引数を含むようにメイン関数を変更します-
void main() => runApp(MyApp(fetchProduct()));
  • 新しいウィジェットProductBoxListを作成して、ホームページに製品リストを作成します。
class ProductBoxList extends StatelessWidget {
   final List<Product> items;
   ProductBoxList({Key key, this.items});

   @override
   Widget build(BuildContext context) {
      return ListView.builder(
         itemCount: items.length,
         itemBuilder: (context, index) {
            return GestureDetector(
               child: ProductBox(item: items[index]),
               onTap: () {
                  Navigator.push(
                     context, MaterialPageRoute(
                        builder: (context) =gt; ProductPage(item: items[index]),
                     ),
                  );
               },
            );
         },
      );
   }
}

List <Product>タイプの製品(オブジェクト)を渡すことで個別のウィジェットとして設計されていることを除いて、ナビゲーションアプリケーションで使用されているものと同じ概念を使用して製品をリストしていることに注意してください。

  • 最後に、_MyHomePage_ウィジェットのビルドメソッドを変更して、通常のメソッド呼び出しの代わりにFutureオプションを使用して製品情報を取得します。
Widget build(BuildContext context) {
   return Scaffold(
      appBar: AppBar(title: Text("Product Navigation")),
      body: Center(
         child: FutureBuilder<List<Product>>(
            future: products, builder: (context, snapshot) {
               if (snapshot.hasError) print(snapshot.error);
               return snapshot.hasData ? ProductBoxList(items: snapshot.data)

              //return the ListView widget :
               Center(child: CircularProgressIndicator());
            },
         ),
      )
   );
}
  • ここでは、FutureBuilderウィジェットを使用してウィジェットをレンダリングしたことに注意してください。 FutureBuilderは、Future <List <Product >>型のfutureプロパティからデータを取得しようとします。 futureプロパティがデータを返す場合、ProductBoxListを使用してウィジェットをレンダリングします。そうでない場合はエラーをスローします。
  • main.dartの完全なコードは次のとおりです-
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'Product.dart';

void main() => runApp(MyApp(products: fetchProducts()));

List<Product> parseProducts(String responseBody) {
   final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
   return parsed.map<Product>((json) => Product.fromMap(json)).toList();
}
Future<List<Product>> fetchProducts() async {
   final response = await http.get('http://192.168.1.2:8000/products.json');
   if (response.statusCode == 200) {
      return parseProducts(response.body);
   } else {
      throw Exception('Unable to fetch products from the REST API');
   }
}
class MyApp extends StatelessWidget {
   final Future<List<Product>> products;
   MyApp({Key key, this.products}) : super(key: key);

  //This widget is the root of your application.
   @override
   Widget build(BuildContext context) {
      return MaterialApp(
         title: 'Flutter Demo',
         theme: ThemeData(
            primarySwatch: Colors.blue,
         ),
         home: MyHomePage(title: 'Product Navigation demo home page', products: products),
      );
   }
}
class MyHomePage extends StatelessWidget {
   final String title;
   final Future<List<Product>> products;
   MyHomePage({Key key, this.title, this.products}) : super(key: key);

  //final items = Product.getProducts();
   @override
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text("Product Navigation")),
         body: Center(
            child: FutureBuilder<List<Product>>(
               future: products, builder: (context, snapshot) {
                  if (snapshot.hasError) print(snapshot.error);
                  return snapshot.hasData ? ProductBoxList(items: snapshot.data)

                 //return the ListView widget :
                  Center(child: CircularProgressIndicator());
               },
            ),
         )
      );
   }
}
class ProductBoxList extends StatelessWidget {
   final List<Product> items;
   ProductBoxList({Key key, this.items});

   @override
   Widget build(BuildContext context) {
      return ListView.builder(
         itemCount: items.length,
         itemBuilder: (context, index) {
            return GestureDetector(
               child: ProductBox(item: items[index]),
               onTap: () {
                  Navigator.push(
                     context, MaterialPageRoute(
                        builder: (context) => ProductPage(item: items[index]),
                     ),
                  );
               },
            );
         },
      );
   }
}
class ProductPage extends StatelessWidget {
   ProductPage({Key key, this.item}) : super(key: key);
   final Product item;
   @override
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text(this.item.name),),
         body: Center(
            child: Container(
               padding: EdgeInsets.all(0),
               child: Column(
                  mainAxisAlignment: MainAxisAlignment.start,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                     Image.asset("assets/appimages/" + this.item.image),
                     Expanded(
                        child: Container(
                           padding: EdgeInsets.all(5),
                           child: Column(
                              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                              children: <Widget>[
                                 Text(this.item.name, style:
                                    TextStyle(fontWeight: FontWeight.bold)),
                                 Text(this.item.description),
                                 Text("Price: " + this.item.price.toString()),
                                 RatingBox(),
                              ],
                           )
                        )
                     )
                  ]
               ),
            ),
         ),
      );
   }
}
class RatingBox extends StatefulWidget {
   @override
   _RatingBoxState createState() =>_RatingBoxState();
}
class _RatingBoxState extends State<RatingBox> {
   int _rating = 0;
   void _setRatingAsOne() {
      setState(() {
         _rating = 1;
      });
   }
   void _setRatingAsTwo() {
      setState(() {
         _rating = 2;
      });
   }
   void _setRatingAsThree() {
      setState(() {
         _rating = 3;
      });
   }
   Widget build(BuildContext context) {
      double _size = 20;
      print(_rating);
      return Row(
         mainAxisAlignment: MainAxisAlignment.end,
         crossAxisAlignment: CrossAxisAlignment.end,
         mainAxisSize: MainAxisSize.max,

         children: <Widget>[
            Container(
               padding: EdgeInsets.all(0),
               child: IconButton(
                  icon: (
                     _rating >= 1
                     ? Icon(Icons.star, ize: _size,)
                     : Icon(Icons.star_border, size: _size,)
                  ),
                  color: Colors.red[500], onPressed: _setRatingAsOne, iconSize: _size,
               ),
            ),
            Container(
               padding: EdgeInsets.all(0),
               child: IconButton(
                  icon: (
                     _rating >= 2
                     ? Icon(Icons.star, size: _size,)
                     : Icon(Icons.star_border, size: _size, )
                  ),
                  color: Colors.red[500],
                  onPressed: _setRatingAsTwo,
                  iconSize: _size,
               ),
            ),
            Container(
               padding: EdgeInsets.all(0),
               child: IconButton(
                  icon: (
                     _rating >= 3 ?
                     Icon(Icons.star, size: _size,)
                     : Icon(Icons.star_border, size: _size,)
                  ),
                  color: Colors.red[500],
                  onPressed: _setRatingAsThree,
                  iconSize: _size,
               ),
            ),
         ],
      );
   }
}
class ProductBox extends StatelessWidget {
   ProductBox({Key key, this.item}) : super(key: key);
   final Product item;

   Widget build(BuildContext context) {
      return Container(
         padding: EdgeInsets.all(2), height: 140,
         child: Card(
            child: Row(
               mainAxisAlignment: MainAxisAlignment.spaceEvenly,
               children: <Widget>[
                  Image.asset("assets/appimages/" + this.item.image),
                  Expanded(
                     child: Container(
                        padding: EdgeInsets.all(5),
                        child: Column(
                           mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                           children: <Widget>[
                              Text(this.item.name, style:TextStyle(fontWeight: FontWeight.bold)),
                              Text(this.item.description),
                              Text("Price: " + this.item.price.toString()),
                              RatingBox(),
                           ],
                        )
                     )
                  )
               ]
            ),
         )
      );
   }
}

最後に、アプリケーションを実行して結果を確認します。 アプリケーションのコーディング中に入力されたローカルの静的データではなく、インターネットからのデータであること以外は、_Navigation_の例と同じです。

Flutter-データベースの概念

Flutterは、データベースを操作するための多くの高度なパッケージを提供します。 最も重要なパッケージは-

  • sqflite -SQLiteデータベースへのアクセスと操作に使用されます。
  • firebase_database -Googleからクラウドホスト型NoSQLデータベースにアクセスして操作するために使用されます。

この章では、それぞれについて詳しく説明します。

SQLite

SQLiteデータベースは、事実上の標準のSQLベースの組み込みデータベースエンジンです。 小規模で実績のあるデータベースエンジンです。 sqfliteパッケージは、SQLiteデータベースを効率的に使用するための多くの機能を提供します。 SQLiteデータベースエンジンを操作する標準的な方法を提供します。 sqfliteパッケージによって提供されるコア機能は次のとおりです-

  • SQLiteデータベースを作成/開く(openDatabaseメソッド)。
  • SQLiteデータベースに対してSQLステートメント(実行メソッド)を実行します。
  • SQLiteデータベースから情報を照会および取得するために必要なコードを削減するための高度な照会メソッド(照会メソッド)。

sqfliteパッケージを使用して標準SQLiteデータベースエンジンから製品情報を保存および取得する製品アプリケーションを作成し、SQLiteデータベースおよびsqfliteパッケージの背後にある概念を理解しましょう。

  • Androidスタジオでproduct_sqlite_appに新しいFlutterアプリケーションを作成します。
  • デフォルトの起動コード(main.dart)を_product_rest_app_コードに置き換えます。
  • アセットフォルダーを_product_nav_app_から_product_rest_app_にコピーし、* pubspec.yaml`ファイル内にアセットを追加します。
flutter:
   assets:
      - assets/appimages/floppy.png
      - assets/appimages/iphone.png
      - assets/appimages/laptop.png
      - assets/appimages/pendrive.png
      - assets/appimages/pixel.png
      - assets/appimages/tablet.png
  • 以下に示すようにpubspec.yamlファイルでsqfliteパッケージを構成します-
dependencies: sqflite: any

sqfliteの最新バージョン番号を代わりに使用します

  • 以下に示すように、pubspec.yamlファイルでpath_providerパッケージを構成します-
dependencies: path_provider: any
  • ここでは、path_providerパッケージを使用して、システムの一時フォルダーパスとアプリケーションのパスを取得します。 _any_の代わりに_sqflite_の最新バージョン番号を使用します。
  • Androidスタジオは、pubspec.yamlが更新されたことを警告します。

更新済み

  • [依存関係の取得]オプションをクリックします。 Androidスタジオはインターネットからパッケージを取得し、アプリケーション用に適切に構成します。
  • データベースでは、名前、価格などの製品プロパティとともに主キー、IDが追加フィールドとして必要なので、Productクラスにidプロパティを追加します。 また、新しいメソッドtoMapを追加して、製品オブジェクトをMapオブジェクトに変換します。 fromMapとtoMapは、Productオブジェクトのシリアル化と非シリアル化に使用され、データベース操作メソッドで使用されます。
class Product {
   final int id;
   final String name;
   final String description;
   final int price;
   final String image;
   static final columns = ["id", "name", "description", "price", "image"];
   Product(this.id, this.name, this.description, this.price, this.image);
   factory Product.fromMap(Map<String, dynamic> data) {
      return Product(
         data['id'],
         data['name'],
         data['description'],
         data['price'],
         data['image'],
      );
   }
   Map<String, dynamic> toMap() => {
      "id": id,
      "name": name,
      "description": description,
      "price": price,
      "image": image
   };
}
  • libフォルダーに新しいファイルDatabase.dartを作成して、_SQLite_関連機能を記述します。
  • Database.dartに必要なインポートステートメントをインポートします。
import 'dart:async';
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'Product.dart';
  • ここで次の点に注意してください-
  • async は非同期メソッドを記述するために使用されます。
  • io はファイルとディレクトリへのアクセスに使用されます。
  • path は、ファイルパスに関連するdartコアユーティリティ関数にアクセスするために使用されます。
  • path_provider は、一時パスとアプリケーションパスを取得するために使用されます。
  • sqflite は、SQLiteデータベースを操作するために使用されます。
  • 新しいクラス SQLiteDbProvider を作成します
  • 以下に指定されているように、シングルトンベースの静的SQLiteDbProviderオブジェクトを宣言します-
class SQLiteDbProvider {
   SQLiteDbProvider._();
   static final SQLiteDbProvider db = SQLiteDbProvider._();
   static Database _database;
}
  • SQLiteDBProvoiderオブジェクトとそのメソッドは、静的db変数を介してアクセスできます。
SQLiteDBProvoider.db.<emthod>
  • タイプFuture <Database>のデータベース(Futureオプション)を取得するメソッドを作成します。 データベース自体の作成中に製品テーブルを作成し、初期データをロードします。
Future<Database> get database async {
   if (_database != null)
   return _database;
   _database = await initDB();
   return _database;
}
initDB() async {
   Directory documentsDirectory = await getApplicationDocumentsDirectory();
   String path = join(documentsDirectory.path, "ProductDB.db");
   return await openDatabase(
      path,
      version: 1,
      onOpen: (db) {},
      onCreate: (Database db, int version) async {
         await db.execute(
            "CREATE TABLE Product ("
            "id INTEGER PRIMARY KEY,"
            "name TEXT,"
            "description TEXT,"
            "price INTEGER,"
            "image TEXT" ")"
         );
         await db.execute(
            "INSERT INTO Product ('id', 'name', 'description', 'price', 'image')
            values (?, ?, ?, ?, ?)",
            [1, "iPhone", "iPhone is the stylist phone ever", 1000, "iphone.png"]
         );
         await db.execute(
            "INSERT INTO Product ('id', 'name', 'description', 'price', 'image')
            values (?, ?, ?, ?, ?)",
            [2, "Pixel", "Pixel is the most feature phone ever", 800, "pixel.png"]
         );
         await db.execute(
            "INSERT INTO Product ('id', 'name', 'description', 'price', 'image')
            values (?, ?, ?, ?, ?)",
            [3, "Laptop", "Laptop is most productive development tool", 2000, "laptop.png"]\
         );
         await db.execute(
            "INSERT INTO Product ('id', 'name', 'description', 'price', 'image')
            values (?, ?, ?, ?, ?)",
            [4, "Tablet", "Laptop is most productive development tool", 1500, "tablet.png"]
         );
         await db.execute(
            "INSERT INTO Product
            ('id', 'name', 'description', 'price', 'image')
            values (?, ?, ?, ?, ?)",
            [5, "Pendrive", "Pendrive is useful storage medium", 100, "pendrive.png"]
         );
         await db.execute(
            "INSERT INTO Product
            ('id', 'name', 'description', 'price', 'image')
            values (?, ?, ?, ?, ?)",
            [6, "Floppy Drive", "Floppy drive is useful rescue storage medium", 20, "floppy.png"]
         );
      }
   );
}

ここでは、次の方法を使用しました-

  • getApplicationDocumentsDirectory -アプリケーションディレクトリパスを返します
  • join -システム固有のパスを作成するために使用されます。 データベースパスの作成に使用しました。
  • openDatabase -SQLiteデータベースを開くために使用
  • onOpen -データベースを開くときにコードを書くために使用
  • onCreate -データベースの初回作成時にコードを記述するために使用
  • db.execute -SQLクエリの実行に使用されます。 クエリを受け入れます。 クエリにプレースホルダー(?)がある場合、2番目の引数のリストとして値を受け入れます。

データベース内のすべての製品を取得するメソッドを記述します-

Future<List<Product>> getAllProducts() async {
   final db = await database;
   List<Map>
   results = await db.query("Product", columns: Product.columns, orderBy: "id ASC");

   List<Product> products = new List();
   results.forEach((result) {
      Product product = Product.fromMap(result);
      products.add(product);
   });
   return products;
}

ここでは、次のことを行いました-

  • クエリメソッドを使用して、すべての製品情報を取得します。 queryは、クエリ全体を記述せずにテーブル情報をクエリするショートカットを提供します。 queryメソッドは、columns、orderByなどの入力を使用して適切なクエリ自体を生成します。

  • ProductのfromMapメソッドを使用して、テーブル内のすべての行を保持する結果オブジェクトをループすることにより、製品の詳細を取得しました。

    *id* に固有の製品を取得するメソッドを記述します
Future<Product> getProductById(int id) async {
   final db = await database;
   var result = await db.query("Product", where: "id = ", whereArgs: [id]);
   return result.isNotEmpty ? Product.fromMap(result.first) : Null;
}
  • ここでは、whereおよびwhereArgsを使用してフィルターを適用しました。
  • データベースから製品を挿入、更新、削除する3つのメソッド-insert、update、deleteメソッドを作成します。
insert(Product product) async {
   final db = await database;
   var maxIdResult = await db.rawQuery(
      "SELECT MAX(id)+1 as last_inserted_id FROM Product");

   var id = maxIdResult.first["last_inserted_id"];
   var result = await db.rawInsert(
      "INSERT Into Product (id, name, description, price, image)"
      " VALUES (?, ?, ?, ?, ?)",
      [id, product.name, product.description, product.price, product.image]
   );
   return result;
}
update(Product product) async {
   final db = await database;
   var result = await db.update("Product", product.toMap(),
   where: "id = ?", whereArgs: [product.id]); return result;
}
delete(int id) async {
   final db = await database;
   db.delete("Product", where: "id = ?", whereArgs: [id]);
}
  • Database.dartの最終的なコードは次のとおりです-
import 'dart:async';
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'Product.dart';

class SQLiteDbProvider {
   SQLiteDbProvider._();
   static final SQLiteDbProvider db = SQLiteDbProvider._();
   static Database _database;

   Future<Database> get database async {
      if (_database != null)
      return _database;
      _database = await initDB();
      return _database;
   }
   initDB() async {
      Directory documentsDirectory = await
      getApplicationDocumentsDirectory();
      String path = join(documentsDirectory.path, "ProductDB.db");
      return await openDatabase(
         path, version: 1,
         onOpen: (db) {},
         onCreate: (Database db, int version) async {
            await db.execute(
               "CREATE TABLE Product ("
               "id INTEGER PRIMARY KEY,"
               "name TEXT,"
               "description TEXT,"
               "price INTEGER,"
               "image TEXT"")"
            );
            await db.execute(
               "INSERT INTO Product ('id', 'name', 'description', 'price', 'image')
               values (?, ?, ?, ?, ?)",
               [1, "iPhone", "iPhone is the stylist phone ever", 1000, "iphone.png"]
            );
            await db.execute(
               "INSERT INTO Product ('id', 'name', 'description', 'price', 'image')
               values (?, ?, ?, ?, ?)",
               [2, "Pixel", "Pixel is the most feature phone ever", 800, "pixel.png"]
            );
            await db.execute(
               "INSERT INTO Product ('id', 'name', 'description', 'price', 'image')
               values (?, ?, ?, ?, ?)",
               [3, "Laptop", "Laptop is most productive development tool", 2000, "laptop.png"]
            );
            await db.execute(
               "INSERT INTO Product ('id', 'name', 'description', 'price', 'image')
               values (?, ?, ?, ?, ?)",
               [4, "Tablet", "Laptop is most productive development tool", 1500, "tablet.png"]
            );
            await db.execute(
               "INSERT INTO Product ('id', 'name', 'description', 'price', 'image')
               values (?, ?, ?, ?, ?)",
               [5, "Pendrive", "Pendrive is useful storage medium", 100, "pendrive.png"]
            );
            await db.execute(
               "INSERT INTO Product ('id', 'name', 'description', 'price', 'image')
               values (?, ?, ?, ?, ?)",
               [6, "Floppy Drive", "Floppy drive is useful rescue storage medium", 20, "floppy.png"]
            );
         }
      );
   }
   Future<List<Product>> getAllProducts() async {
      final db = await database;
      List<Map> results = await db.query(
         "Product", columns: Product.columns, orderBy: "id ASC"
      );
      List<Product> products = new List();
      results.forEach((result) {
         Product product = Product.fromMap(result);
         products.add(product);
      });
      return products;
   }
   Future<Product> getProductById(int id) async {
      final db = await database;
      var result = await db.query("Product", where: "id = ", whereArgs: [id]);
      return result.isNotEmpty ? Product.fromMap(result.first) : Null;
   }
   insert(Product product) async {
      final db = await database;
      var maxIdResult = await db.rawQuery("SELECT MAX(id)+1 as last_inserted_id FROM Product");
      var id = maxIdResult.first["last_inserted_id"];
      var result = await db.rawInsert(
         "INSERT Into Product (id, name, description, price, image)"
         " VALUES (?, ?, ?, ?, ?)",
         [id, product.name, product.description, product.price, product.image]
      );
      return result;
   }
   update(Product product) async {
      final db = await database;
      var result = await db.update(
         "Product", product.toMap(), where: "id = ?", whereArgs: [product.id]
      );
      return result;
   }
   delete(int id) async {
      final db = await database;
      db.delete("Product", where: "id = ?", whereArgs: [id]);
   }
}
  • mainメソッドを変更して、製品情報を取得します。
void main() {
   runApp(MyApp(products: SQLiteDbProvider.db.getAllProducts()));
}
  • ここでは、getAllProductsメソッドを使用して、データベースからすべての製品をフェッチしました。
  • アプリケーションを実行し、結果を確認します。 製品情報が保存され、ローカルSQLiteデータベースから取得されることを除いて、前の例_Accessing Product service API_に似ています。

クラウドファイヤーストア

FirebaseはBaaSアプリ開発プラットフォームです。 認証サービス、クラウドストレージなどのモバイルアプリケーション開発を高速化する多くの機能を提供します。Firebaseの主な機能の1つは、クラウドベースのリアルタイムNoSQLデータベースであるCloud Firestoreです。

Flutterは、Cloud Firestoreでプログラムするための特別なパッケージcloud_firestoreを提供します。 Cloud Firestoreでオンライン製品ストアを作成し、製品ストアにアクセスするためのアプリケーションを作成しましょう。

  • Androidスタジオでproduct_firebase_appに新しいFlutterアプリケーションを作成します。
  • デフォルトの起動コード(main.dart)を_product_rest_app_コードに置き換えます。
  • Product.dartファイルをproduct_rest_appからlibフォルダーにコピーします。
class Product {
   final String name;
   final String description;
   final int price;
   final String image;

   Product(this.name, this.description, this.price, this.image);
   factory Product.fromMap(Map<String, dynamic> json) {
      return Product(
         json['name'],
         json['description'],
         json['price'],
         json['image'],
      );
   }
}
  • アセットフォルダーをproduct_rest_appからproduct_firebase_appにコピーし、pubspec.yamlファイル内にアセットを追加します。
flutter:
   assets:
   - assets/appimages/floppy.png
   - assets/appimages/iphone.png
   - assets/appimages/laptop.png
   - assets/appimages/pendrive.png
   - assets/appimages/pixel.png
   - assets/appimages/tablet.png
  • 以下に示すように、pubspec.yamlファイルでcloud_firestoreパッケージを構成します-
dependencies: cloud_firestore: ^0.9.13+1
  • ここでは、cloud_firestoreパッケージの最新バージョンを使用します。
  • Androidスタジオは、pubspec.yamlがここに示すように更新されたことを警告します-

Cloud Firestore Package

[依存関係の取得]オプションをクリックします。 Androidスタジオはインターネットからパッケージを取得し、アプリケーション用に適切に構成します。

次の手順を使用してFirebaseでプロジェクトを作成します-

  • https://firebase.google.com/pricing/。で無料プランを選択して、Firebaseアカウントを作成します。
  • Firebaseアカウントが作成されると、プロジェクト概要ページにリダイレクトされます。 Firebaseベースのプロジェクトをすべてリストし、新しいプロジェクトを作成するオプションを提供します。
  • [プロジェクトの追加]をクリックすると、プロジェクト作成ページが開きます。
  • 製品名としてプロジェクト名を入力し、[プロジェクトの作成]オプションをクリックします。
  • * Firebaseコンソールに移動します。
  • [プロジェクトの概要]をクリックします。 プロジェクトの概要ページが開きます。
  • Androidアイコンをクリックします。 Android開発に固有のプロジェクト設定が開きます。
  • Androidパッケージ名com.finddevguides.flutterapp.product_firebase_appを入力します。
  • アプリの登録をクリックします。 プロジェクト構成ファイルgoogle_service.jsonを生成します。
  • google_service.jsonをダウンロードして、プロジェクトのandroid/appディレクトリに移動します。 このファイルは、アプリケーションとFirebase間の接続です。
  • android/app/build.gradleを開き、次のコードを含めます-
apply plugin: 'com.google.gms.google-services'
  • android/build.gradleを開き、次の構成を含めます-
buildscript {
   repositories {
     //...
   }
   dependencies {
     //...
      classpath 'com.google.gms:google-services:3.2.1'//new
   }
}

ここでは、プラグインとクラスパスはgoogle_service.jsonファイルを読み取る目的で使用されます。

  • android/app/build.gradleを開き、次のコードも含めます。
android {
   defaultConfig {
      ...
      multiDexEnabled true
   }
   ...
}
dependencies {
   ...
   compile 'com.android.support: multidex:1.0.3'
}

この依存関係により、Androidアプリケーションは複数のdex機能を使用できます。

  • Firebase Consoleの残りの手順に従うか、単にスキップします。

次の手順を使用して、新しく作成されたプロジェクトに製品ストアを作成します-

  • Firebaseコンソールに移動します。
  • 新しく作成したプロジェクトを開きます。
  • 左側のメニューで[データベース]オプションをクリックします。
  • [データベースオプションの作成]をクリックします。
  • [テストモードで開始]をクリックし、[有効にする]をクリックします。
  • [コレクションを追加]をクリックします。 コレクション名として製品を入力し、[次へ]をクリックします。
  • ここに画像に示すようにサンプル製品情報を入力してください-

サンプル製品情報

  • [ドキュメントの追加]オプションを使用して、追加の製品情報を追加します。
  • main.dartファイルを開き、Cloud Firestoreプラグインファイルをインポートして、httpパッケージを削除します。
import 'package:cloud_firestore/cloud_firestore.dart';
  • parseProductsを削除し、fetchProductsを更新して、製品サービスAPIではなくCloud Firestoreから製品を取得します。
Stream<QuerySnapshot> fetchProducts() {
   return Firestore.instance.collection('product').snapshots(); }
  • ここでは、Firestore.instance.collectionメソッドを使用して、クラウドストアで利用可能な製品コレクションにアクセスします。 Firestore.instance.collectionには、コレクションをフィルタリングして必要なドキュメントを取得するための多くのオプションが用意されています。 ただし、すべての製品情報を取得するためのフィルターは適用していません。
  • Cloud FirestoreはDart Streamコンセプトを通じてコレクションを提供するため、MyAppおよびMyHomePageウィジェットの製品タイプをFuture <list <Product >>からStream <QuerySnapshot>に変更します。
  • MyHomePageウィジェットのビルドメソッドを変更して、FutureBuilderではなくStreamBuilderを使用します。
@override
Widget build(BuildContext context) {
   return Scaffold(
      appBar: AppBar(title: Text("Product Navigation")),
      body: Center(
         child: StreamBuilder<QuerySnapshot>(
            stream: products, builder: (context, snapshot) {
               if (snapshot.hasError) print(snapshot.error);
               if(snapshot.hasData) {
                  List<DocumentSnapshot>
                  documents = snapshot.data.documents;

                  List<Product>
                  items = List<Product>();

                  for(var i = 0; i < documents.length; i++) {
                     DocumentSnapshot document = documents[i];
                     items.add(Product.fromMap(document.data));
                  }
                  return ProductBoxList(items: items);
               } else {
                  return Center(child: CircularProgressIndicator());
               }
            },
         ),
      )
   );
}
  • ここでは、List <DocumentSnapshot>タイプとして製品情報を取得しました。 ウィジェットのProductBoxListはドキュメントと互換性がないため、ドキュメントをList <Product>タイプに変換し、さらに使用しました。
  • 最後に、アプリケーションを実行して結果を確認します。 _SQLite application_と同じ製品情報を使用し、記憶媒体のみを変更したため、結果のアプリケーションは_SQLite application_アプリケーションと同一に見えます。

Flutter-国際化

現在、モバイルアプリケーションはさまざまな国のお客様によって使用されており、その結果、アプリケーションはさまざまな言語でコンテンツを表示する必要があります。 アプリケーションを複数の言語で動作させることを、アプリケーションの国際化と呼びます。

アプリケーションが異なる言語で動作するためには、まずアプリケーションが実行されているシステムの現在のロケールを見つけてから、その特定のロケールでコンテンツを表示する必要があります。このプロセスはローカリゼーションと呼ばれます。

Flutterフレームワークは、ローカライズ用の3つの基本クラスと、アプリケーションをローカライズするための基本クラスから派生した広範なユーティリティクラスを提供します。

基本クラスは次のとおりです-

  • Locale-ロケールは、ユーザーの言語を識別するために使用されるクラスです。 たとえば、en-usはアメリカ英語を識別し、次のように作成できます。
Locale en_locale = Locale('en', 'US')

ここで、最初の引数は言語コードであり、2番目の引数は国コードです。 _Argentinaスペイン語(es-ar)_ロケールを作成する別の例は次のとおりです-

Locale es_locale = Locale('es', 'AR')
  • Localizations-Localizationsは、Localeとその子のローカライズされたリソースを設定するために使用される一般的なウィジェットです。
class CustomLocalizations {
   CustomLocalizations(this.locale);
   final Locale locale;
   static CustomLocalizations of(BuildContext context) {
      return Localizations.of<CustomLocalizations>(context, CustomLocalizations);
   }
   static Map<String, Map<String, String>> _resources = {
      'en': {
         'title': 'Demo',
         'message': 'Hello World'
      },
      'es': {
         'title': 'Manifestación',
         'message': 'Hola Mundo',
      },
   };
   String get title {
      return _resources[locale.languageCode]['title'];
   }
   String get message {
      return _resources[locale.languageCode]['message'];
   }
}

ここで、CustomLocalizationsは、ウィジェットの特定のローカライズされたコンテンツ(タイトルとメッセージ)を取得するために特別に作成された新しいカスタムクラスです。 ofメソッドは、Localizationsクラスを使用して新しいCustomLocalizationsクラスを返します。

LocalizationsDelegate <T>-LocalizationsDelegate <T>は、Localizationsウィジェットがロードされるファクトリクラスです。 それは3つのオーバーライド可能なメソッドを持っています-

  • isSupported-ロケールを受け入れ、指定されたロケールがサポートされているかどうかを返します。
@override
bool isSupported(Locale locale) => ['en', 'es'].contains(locale.languageCode);
  • load-ロケールを受け入れ、指定されたロケールのリソースのロードを開始します。
@override
Future<CustomLocalizations> load(Locale locale) {
   return SynchronousFuture<CustomLocalizations>(CustomLocalizations(locale));
}
  • shouldReload-Localizationsウィジェットの再構築時にCustomLocalizationsの再読み込みが必要かどうかを指定します。
@override
bool shouldReload(CustomLocalizationsDelegate old) => false;
  • CustomLocalizationDelegateの完全なコードは次のとおりです-
class CustomLocalizationsDelegate extends
LocalizationsDelegate<CustomLocalizations> {
   const CustomLocalizationsDelegate();
   @override
   bool isSupported(Locale locale) => ['en', 'es'].contains(locale.languageCode);
   @override
   Future<CustomLocalizations> load(Locale locale) {
      return SynchronousFuture<CustomLocalizations>(CustomLocalizations(locale));
   }
   @override bool shouldReload(CustomLocalizationsDelegate old) => false;
}

一般的に、Flutterアプリケーションは2つのルートレベルウィジェット、MaterialAppまたはWidgetsAppに基づいています。 Flutterは、両方のウィジェットに既製のローカライズを提供し、それらはMaterialLocalizationsとWidgetsLocaliationsです。 さらに、FlutterはMaterialLocalizationsとWidgetsLocaliationsをロードするデリゲートも提供します。これらはそれぞれGlobalMaterialLocalizations.delegateとGlobalWidgetsLocalizations.delegateです。

概念をテストして理解するために、簡単な国際化対応アプリケーションを作成しましょう。

  • 新しいflutterアプリケーションflutter_localization_appを作成します。
  • Flutterは、専用flutterパッケージflutter_localizationsを使用した国際化をサポートしています。 ローカライズされたコンテンツをメインSDKから分離するという考え方です。 pubspec.yamlを開き、以下のコードを追加して国際化パッケージを有効にします-
dependencies:
   flutter:
      sdk: flutter
   flutter_localizations:
      sdk: flutter
  • Androidスタジオは、pubspec.yamlが更新されたことを示す次のアラートを表示します。

アラート

  • [依存関係の取得]オプションをクリックします。 Androidスタジオはインターネットからパッケージを取得し、アプリケーション用に適切に構成します。
  • 次のようにmain.dartにflutter_localizationsパッケージをインポートします-
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter/foundation.dart' show SynchronousFuture;
  • ここで、SynchronousFutureの目的は、カスタムローカリゼーションを同期的にロードすることです。
  • 以下に指定されているように、カスタムローカリゼーションとそれに対応するデリゲートを作成します-
class CustomLocalizations {
   CustomLocalizations(this.locale);
   final Locale locale;
   static CustomLocalizations of(BuildContext context) {
      return Localizations.of<CustomLocalizations>(context, CustomLocalizations);
   }
   static Map<String, Map<String, String>> _resources = {
      'en': {
         'title': 'Demo',
         'message': 'Hello World'
      },
      'es': {
         'title': 'Manifestación',
         'message': 'Hola Mundo',
      },
   };
   String get title {
      return _resources[locale.languageCode]['title'];
   }
   String get message {
      return _resources[locale.languageCode]['message'];
   }
}
class CustomLocalizationsDelegate extends
LocalizationsDelegate<CustomLocalizations> {
   const CustomLocalizationsDelegate();

   @override
   bool isSupported(Locale locale) => ['en', 'es'].contains(locale.languageCode);

   @override
   Future<CustomLocalizations> load(Locale locale) {
      return SynchronousFuture<CustomLocalizations>(CustomLocalizations(locale));
   }
   @override bool shouldReload(CustomLocalizationsDelegate old) => false;
}
  • ここでは、アプリケーションのタイトルとメッセージのローカライズをサポートするためにCustomLocalizationsが作成され、CustomLocalizationsをロードするためにCustomLocalizationsDelegateが使用されます。
  • MaterialAppプロパティ、localizationsDelegates、supportedLocalesを使用して、下で指定されているように、MaterialApp、WidgetsApp、およびCustomLocalizationのデリゲートを追加します-
localizationsDelegates: [
   const CustomLocalizationsDelegate(),
   GlobalMaterialLocalizations.delegate,
   GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
   const Locale('en', ''),
   const Locale('es', ''),
],
  • CustomLocalizationsメソッドを使用して、ローカライズされたタイトルとメッセージの値を取得し、以下に指定されている適切な場所で使用します-
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key);
   final String title;
   @override
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text(CustomLocalizations .of(context) .title), ),
         body: Center(
            child: Column(
               mainAxisAlignment: MainAxisAlignment.center,
               children: <Widget>[
                  Text( CustomLocalizations .of(context) .message, ),
               ],
            ),
         ),
      );
   }
}
  • ここでは、簡単にするためにMyHomePageクラスをStatefulWidgetからStatelessWidgetに変更し、CustomLocalizationsを使用してタイトルとメッセージを取得しました。
  • アプリケーションをコンパイルして実行します。 アプリケーションは、コンテンツを英語で表示します。 アプリケーションを閉じます。 設定→システム→言語と入力→言語**に移動します。
  • [言語の追加]オプションをクリックして、スペイン語を選択します。 これにより、スペイン語がインストールされ、オプションの1つとしてリストされます。
  • スペイン語を選択し、英語の上に移動します。 これは、第一言語としてスペイン語に設定され、すべてがスペイン語のテキストに変更されます。
  • 国際化アプリケーションを再起動すると、タイトルとメッセージがスペイン語で表示されます。
  • 設定で英語オプションをスペイン語オプションの上に移動することにより、言語を英語に戻すことができます。
  • アプリケーションの結果(スペイン語)は、以下のスクリーンショットに示されています-

マニフェスタシオン

intlパッケージの使用

Flutterは、ローカライズされたモバイルアプリケーションの開発をさらに簡素化するintlパッケージを提供します。 intlパッケージは、言語固有のメッセージを半自動生成するための特別なメソッドとツールを提供します。

intlパッケージを使用して新しいローカライズアプリケーションを作成し、概念を理解しましょう。

  • 新しいflutterアプリケーションflutter_intl_appを作成します。
  • pubspec.yamlを開き、パッケージの詳細を追加します。
dependencies:
   flutter:
      sdk: flutter
   flutter_localizations:
      sdk: flutter
   intl: ^0.15.7
   intl_translation: ^0.17.3
  • Androidスタジオは、pubspec.yamlが更新されたことを通知する以下のアラートを表示します。

更新の通知

  • [依存関係の取得]オプションをクリックします。 Androidスタジオはインターネットからパッケージを取得し、アプリケーション用に適切に構成します。
  • 前のサンプルflutter_internationalization_appからmain.dartをコピーします。
  • 以下に示すように、国際パッケージをインポートします-
import 'package:intl/intl.dart';
  • 以下に示すコードに示すように、CustomLocalizationクラスを更新します-
class CustomLocalizations {
   static Future<CustomLocalizations> load(Locale locale) {
      final String name = locale.countryCode.isEmpty ? locale.languageCode : locale.toString();
      final String localeName = Intl.canonicalizedLocale(name);

      return initializeMessages(localeName).then((_) {
         Intl.defaultLocale = localeName;
         return CustomLocalizations();
      });
   }
   static CustomLocalizations of(BuildContext context) {
      return Localizations.of<CustomLocalizations>(context, CustomLocalizations);
   }
   String get title {
      return Intl.message(
         'Demo',
         name: 'title',
         desc: 'Title for the Demo application',
      );
   }
   String get message{
      return Intl.message(
         'Hello World',
         name: 'message',
         desc: 'Message for the Demo application',
      );
   }
}
class CustomLocalizationsDelegate extends
LocalizationsDelegate<CustomLocalizations> {
   const CustomLocalizationsDelegate();

   @override
   bool isSupported(Locale locale) => ['en', 'es'].contains(locale.languageCode);
   @override
   Future<CustomLocalizations> load(Locale locale) {
      return CustomLocalizations.load(locale);
   }
   @override
   bool shouldReload(CustomLocalizationsDelegate old) => false;
}

ここでは、カスタムメソッドの代わりにintlパッケージの3つのメソッドを使用しました。 それ以外は、概念は同じです。

  • Intl.canonicalizedLocale-正しいロケール名を取得するために使用されます。

  • Intl.defaultLocale-現在のロケールを設定するために使用

  • Intl.message-新しいメッセージを定義するために使用されます。

    *l10n/messages_all.dart* ファイルをインポートします。 このファイルをすぐに生成します
import 'l10n/messages_all.dart';
  • 次に、フォルダーlib/l10nを作成します
  • コマンドプロンプトを開き、アプリケーションのルートディレクトリ(pubspec.yamlが利用可能)に移動し、次のコマンドを実行します-
flutter packages pub run intl_translation:extract_to_arb --output-
   dir=lib/l10n lib/main.dart
  • ここで、コマンドは、異なるロケールでメッセージを作成するためのテンプレートintl_message.arbファイルを生成します。 ファイルの内容は次のとおりです-
{
   "@@last_modified": "2019-04-19T02:04:09.627551",
   "title": "Demo",
   "@title": {
      "description": "Title for the Demo application",
      "type": "text",
      "placeholders": {}
   },
   "message": "Hello World",
   "@message": {
      "description": "Message for the Demo
      application",
      "type": "text",
      "placeholders": {}
   }
}
  • intl_message.arbをコピーして、新しいファイルintl_en.arbを作成します。
  • intl_message.arbをコピーし、新しいファイルintl_es.arbを作成して、以下に示すようにコンテンツをスペイン語に変更します-
{
   "@@last_modified": "2019-04-19T02:04:09.627551",
   "title": "Manifestación",
   "@title": {
      "description": "Title for the Demo application",
      "type": "text",
      "placeholders": {}
   },
   "message": "Hola Mundo",
   "@message": {
      "description": "Message for the Demo application",
      "type": "text",
      "placeholders": {}
   }
}
  • 次に、次のコマンドを実行して、最終的なメッセージファイルmessages_all.dartを作成します。
flutter packages pub run intl_translation:generate_from_arb
--output-dir=lib\l10n --no-use-deferred-loading
lib\main.dart lib\l10n\intl_en.arb lib\l10n\intl_es.arb
  • アプリケーションをコンパイルして実行します。 上記のアプリケーションflutter_localization_appと同様に機能します。

フラッター-テスト

テストは、アプリケーションの開発ライフサイクルにおける非常に重要なフェーズです。 アプリケーションが高品質であることを保証します。 テストには、慎重な計画と実行が必要です。 また、開発の最も時間のかかるフェーズです。

Dart言語とFlutterフレームワークは、アプリケーションの自動テストの広範なサポートを提供します。

テストの種類

一般に、アプリケーションを完全にテストするには、3種類のテストプロセスを使用できます。 彼らは次のとおりです-

単体テスト

単体テストは、アプリケーションをテストする最も簡単な方法です。 クラスのメソッドのコード(一般的には関数)の正確さを保証することに基づいています。 しかし、それは実際の環境を反映していないため、バグを見つけるための最小のオプションです。

ウィジェットのテスト

ウィジェットのテストは、ウィジェットの作成、レンダリング、および他のウィジェットとの対話が期待どおりであることを確認することに基づいています。 さらに一歩進んで、ほぼリアルタイムの環境を提供して、より多くのバグを見つけます。

統合テスト

統合テストには、データベース、Webサービスなどのアプリケーションの外部コンポーネントとともに、単体テストとウィジェットテストの両方が含まれます。実際の環境をシミュレートまたはモックして、ほぼすべてのバグを検出しますが、最も複雑なプロセスです。

Flutterは、あらゆる種類のテストをサポートします。 ウィジェットのテストを広範囲かつ排他的にサポートします。 この章では、ウィジェットのテストについて詳しく説明します。

ウィジェットのテスト

Flutterテストフレームワークは、ウィジェットをテストするtestWidgetsメソッドを提供します。 それは2つの引数を受け入れます-

  • テストの説明
  • テストコード
testWidgets('test description: find a widget', '<test code>');

関与するステップ

ウィジェットのテストには、3つの異なるステップが含まれます-

  • テスト環境でウィジェットをレンダリングします。
  • WidgetTesterは、Flutterテストフレームワークによって提供される、ウィジェットを構築してレンダリングするクラスです。 WidgetTesterクラスのpumpWidgetメソッドは、任意のウィジェットを受け入れ、テスト環境でレンダリングします。
testWidgets('finds a specific instance', (WidgetTester tester) async {
   await tester.pumpWidget(MaterialApp(
      home: Scaffold(
         body: Text('Hello'),
      ),
   ));
});

テストする必要があるウィジェットを見つけます。

Flutterフレームワークは、テスト環境でレンダリングされたウィジェットを見つけるための多くのオプションを提供し、それらは一般にファインダーと呼ばれます。 最も頻繁に使用されるファインダーは、find.text、find.byKey、find.byWidgetです。

  • find.textは、指定されたテキストを含むウィジェットを検索します。
find.text('Hello')
  • find.byKeyは、特定のキーでウィジェットを検索します。
find.byKey('home')
  • find.byWidgetは、インスタンス変数でウィジェットを見つけます。
find.byWidget(homeWidget)

ウィジェットが期待どおりに機能することを確認します。

Flutterフレームワークには、ウィジェットを期待されるウィジェットと一致させるための多くのオプションがあり、通常は_Matchers_と呼ばれます。 テストフレームワークが提供するexpectメソッドを使用してウィジェットを一致させることができます。これは、2番目のステップで、マッチャーのいずれかを選択することで、期待されるウィジェットと一致します。 重要なマッチャーの一部は次のとおりです。

  • findsOneWidget-単一のウィジェットが見つかったことを確認します。
expect(find.text('Hello'), findsOneWidget);
  • findsNothing-ウィジェットが見つからないことを確認します
expect(find.text('Hello World'), findsNothing);
  • findsWidgets-単一のウィジェットが見つかったことを確認します。
expect(find.text('Save'), findsWidgets);
  • findsNWidgets-N個のウィジェットが見つかったことを確認します。
expect(find.text('Save'), findsNWidgets(2));

完全なテストコードは次のとおりです-

testWidgets('finds hello widget', (WidgetTester tester) async {
   await tester.pumpWidget(MaterialApp(
      home: Scaffold(
         body: Text('Hello'),
      ),
   ));
   expect(find.text('Hello'), findsOneWidget);
});

ここでは、本文にTextウィジェットを使用して、テキストHelloを使用してMaterialAppウィジェットをレンダリングしました。 次に、find.textを使用してウィジェットを検索し、findsOneWidgetを使用して一致させました。

実施例

単純なフラッターアプリケーションを作成し、ウィジェットテストを作成して、関連する手順と概念をよりよく理解しましょう。

  • Androidスタジオで新しいflutterアプリケーションflutter_test_appを作成します。
  • テストフォルダーでwidget_test.dartを開きます。 以下に示すサンプルのテストコードがあります-
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
  //Build our app and trigger a frame.
   await tester.pumpWidget(MyApp());

  //Verify that our counter starts at 0.
   expect(find.text('0'), findsOneWidget);
   expect(find.text('1'), findsNothing);

  //Tap the '+' icon and trigger a frame.
   await tester.tap(find.byIcon(Icons.add));
   await tester.pump();

  //Verify that our counter has incremented.
   expect(find.text('0'), findsNothing);
   expect(find.text('1'), findsOneWidget);
});

ここでは、テストコードは次の機能を実行します-

  • tester.pumpWidgetを使用してMyAppウィジェットをレンダリングします。
  • findsOneWidgetおよびfindsNothingマッチャーを使用して、カウンターが最初にゼロになるようにします。
  • find.byIconメソッドを使用して、カウンターインクリメントボタンを検索します。
  • tester.tapメソッドを使用して、カウンターインクリメントボタンをタップします。
  • findsOneWidgetおよびfindsNothingマッチャーを使用して、カウンターが増加するようにします。

カウンターのインクリメントボタンをもう一度タップして、カウンターが2に増えたかどうかを確認します。

await tester.tap(find.byIcon(Icons.add));
await tester.pump();

expect(find.text('2'), findsOneWidget);
  • [実行]メニューをクリックします。
  • widget_test.dartオプションのテストをクリックします。 これにより、テストが実行され、結果ウィンドウに結果が報告されます。

フラッターテスト

フラッター-展開

この章では、AndroidプラットフォームとiOSプラットフォームの両方でFlutterアプリケーションを展開する方法について説明します。

Androidアプリケーション

  • Androidマニフェストファイルのandroid:labelエントリを使用して、アプリケーション名を変更します。 AndroidアプリマニフェストファイルであるAndroidManifest.xmlは、<app dir>/android/app/src/mainにあります。 これには、Androidアプリケーションに関する詳細がすべて含まれています。 android:labelエントリを使用してアプリケーション名を設定できます。
  • マニフェストファイルのandroid:iconエントリを使用してランチャーアイコンを変更します。
  • 必要に応じて、標準オプションを使用してアプリに署名します。
  • 必要に応じて、標準オプションを使用してプロガードと難読化を有効にします。
  • 以下のコマンドを実行してリリースAPKファイルを作成します-
cd/path/to/my/application
flutter build apk
  • 次のように出力を見ることができます-
Initializing gradle...                                            8.6s
Resolving dependencies...                                        19.9s
Calling mockable JAR artifact transform to create file:
/Users/.gradle/caches/transforms-1/files-1.1/android.jar/
c30932f130afbf3fd90c131ef9069a0b/android.jar with input
/Users/Library/Android/sdk/platforms/android-28/android.jar
Running Gradle task 'assembleRelease'...
Running Gradle task 'assembleRelease'...
Done                                                             85.7s
Built build/app/outputs/apk/release/app-release.apk (4.8MB).
  • 次のコマンドを使用してデバイスにAPKをインストールします-
flutter install
  • アプリバンドルを作成してアプリケーションをGoogle Playstoreに公開し、標準の方法を使用してPlaystoreにプッシュします。
flutter build appbundle

iOSアプリケーション

  • 標準的な方法を使用して、_App Store Connect_にiOSアプリケーションを登録します。 アプリケーションの登録中に使用した = Bundle ID を保存します。
  • XCodeプロジェクト設定の表示名を更新して、アプリケーション名を設定します。
  • XCodeプロジェクト設定のバンドルIDを更新して、ステップ1で使用したバンドルIDを設定します。
  • 標準的な方法を使用して、必要に応じて符号をコーディングします。
  • 標準的な方法を使用して、必要に応じて新しいアプリアイコンを追加します。
  • 次のコマンドを使用してIPAファイルを生成します-
flutter build ios
  • 今、あなたは次の出力を見ることができます-
Building com.example.MyApp for device (ios-release)...
Automatically signing iOS for device deployment
using specified development team in Xcode project:
Running Xcode build...                                   23.5s
......................
  • 標準の方法を使用して、アプリケーション、IPAファイルをTestFlightにプッシュすることにより、アプリケーションをテストします。
  • 最後に、標準の方法を使用して、アプリケーションを_App Store_にプッシュします。

Flutter-開発ツール

この章では、Flutter開発ツールについて詳しく説明します。 クロスプラットフォーム開発ツールキットの最初の安定版リリースは、2018年12月4日にリリースされたFlutter 1.0です。 Googleはさまざまな開発ツールを使用してFlutterフレームワークの改善と強化を継続的に行っています。

ウィジェットセット

Googleは、素材およびクパチーノウィジェットセット用に更新し、コンポーネント設計でピクセル単位の完璧な品質を提供します。 flutter 1.2の次期バージョンは、デスクトップキーボードイベントとマウスホバーサポートをサポートするように設計されます。

Visual Studio Codeを使用したフラッター開発

Visual Studio Codeはフラッター開発をサポートし、迅速かつ効率的な開発のための広範なショートカットを提供します。 フラッター開発のためにVisual Studio Codeが提供する主要な機能の一部を以下に示します-

  • コードアシスト-オプションを確認したい場合、 Ctrl + Space を使用してコード補完オプションのリストを取得できます。
  • クイックフィックス- Ctrl + 。 コードの修正に役立つクイック修正ツールです。
  • コーディング中のショートカット。
  • コメントで詳細なドキュメントを提供します。
  • デバッグのショートカット。
  • ホットリスタート。

Dart DevTools

Android StudioまたはVisual Studio Code、または他のIDEを使用してコードを記述し、プラグインをインストールできます。 Googleの開発チームは、Dart DevToolsと呼ばれるさらに別の開発ツールに取り組んでいます。これはWebベースのプログラミングスイートです。 AndroidプラットフォームとiOSプラットフォームの両方をサポートしています。 タイムラインビューに基づいているため、開発者はアプリケーションを簡単に分析できます。

DevToolsをインストールする

DevToolsをインストールするには、コンソールで次のコマンドを実行します-

flutter packages pub global activate devtools

今、あなたは次の出力を見ることができます-

Resolving dependencies...
+ args 1.5.1
+ async 2.2.0
+ charcode 1.1.2
+ codemirror 0.5.3+5.44.0
+ collection 1.14.11
+ convert 2.1.1
+ devtools 0.0.16
+ devtools_server 0.0.2
+ http 0.12.0+2
+ http_parser 3.1.3
+ intl 0.15.8
+ js 0.6.1+1
+ meta 1.1.7
+ mime 0.9.6+2
..................
..................
Installed executable devtools.
Activated devtools 0.0.16.

サーバーを実行

次のコマンドを使用して、DevToolsサーバーを実行できます-

flutter packages pub global run devtools

これで、次のような応答が得られます。

Serving DevTools at http://127.0.0.1:9100

アプリケーションを開始する

アプリケーションに移動し、シミュレータを開き、次のコマンドを使用して実行します-

flutter run --observatory-port=9200

これで、DevToolsに接続されました。

ブラウザでDevToolsを起動します

次に、ブラウザで以下のURLにアクセスして、DevToolsを起動します-

http://localhost:9100/?port=9200

あなたは以下に示すように応答を取得します-

Dart Dev Tools

Flutter SDK

Flutter SDKを更新するには、次のコマンドを使用します-

flutter upgrade

次のように出力を見ることができます-

Flutter SDK

Flutterパッケージをアップグレードするには、次のコマンドを使用します-

flutter packages upgrade

次の応答が表示されます。

Running "flutter packages upgrade" in my_app... 7.4s

フラッターインスペクター

フラッターウィジェットツリーを探索するために使用されます。 これを実現するには、コンソールで次のコマンドを実行し、

flutter run --track-widget-creation

次のように出力を見ることができます-

Launching lib/main.dart on iPhone X in debug mode...
─Assembling Flutter resources...                       3.6s
Compiling, linking and signing...                      6.8s
Xcode build done.                                     14.2s
2,904ms (!)
To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R".
An Observatory debugger and profiler on iPhone X is available at: http://127.0.0.1:50399/
For a more detailed help message, press "h". To detach, press "d"; to quit, press "q".

今、URL、http://127.0.0.1:50399/に移動すると、次の結果が表示されます-

結果

Flutter-高度なアプリケーションの作成

この章では、本格的なモバイルアプリケーション、expense_calculatorの作成方法を学習します。 Expense_Calculatorの目的は、経費情報を保存することです。 アプリケーションの完全な機能は次のとおりです-

  • 費用リスト。
  • 新しい費用を入力するフォーム。
  • 既存の費用を編集/削除するオプション。
  • 任意のインスタンスでの総費用。

Flutterフレームワークの下記の高度な機能を使用して、expense_calculatorアプリケーションをプログラミングします。

  • 経費リストを表示するためのListViewの高度な使用。

  • フォームプログラミング。

  • 経費を保存するSQLiteデータベースプログラミング。

  • プログラミングを簡素化するscoped_model状態管理。

    *expense_calculator* アプリケーションのプログラミングを始めましょう。
  • Androidスタジオで新しいFlutterアプリケーション、expense_calculatorを作成します。

  • pubspec.yamlを開き、パッケージの依存関係を追加します。

dependencies:
   flutter:
      sdk: flutter
   sqflite: ^1.1.0
   path_provider: ^0.5.0+1
   scoped_model: ^1.0.1
   intl: any

ここでこれらのポイントを観察します-

  • sqfliteは、SQLiteデータベースプログラミングに使用されます。
  • path_providerは、システム固有のアプリケーションパスを取得するために使用されます。
  • scoped_modelは状態管理に使用されます。
  • intlは日付のフォーマットに使用されます。

Androidスタジオは、pubspec.yamlが更新されたことを示す次のアラートを表示します。

高度なアプリケーションを作成するアラート

[依存関係の取得]オプションをクリックします。 Androidスタジオはインターネットからパッケージを取得し、アプリケーション用に適切に構成します。

main.dartの既存のコードを削除します。

新しいファイルExpense.dartを追加して、Expenseクラスを作成します。 経費クラスには、以下のプロパティとメソッドがあります。

  • property:id -SQLiteデータベースの経費エントリを表す一意のID。
  • property:amount -費やされた金額。
  • property:date -金額が費やされた日付。
  • プロパティ:カテゴリ-カテゴリは、金額が費やされる領域を表します。 例:食べ物、旅行など
  • formattedDate -日付プロパティのフォーマットに使用
  • fromMap -データベーステーブルのフィールドを経費オブジェクトのプロパティにマップし、新しい経費オブジェクトを作成するために使用します。
factory Expense.fromMap(Map<String, dynamic> data) {
   return Expense(
      data['id'],
      data['amount'],
      DateTime.parse(data['date']),
      data['category']
   );
}
  • toMap -経費オブジェクトをDart Mapに変換するために使用され、データベースプログラミングでさらに使用できます。
Map<String, dynamic> toMap() => {
   "id" : id,
   "amount" : amount,
   "date" : date.toString(),
   "category" : category,
};
  • columns -データベースフィールドを表すために使用される静的変数。

次のコードを入力してExpense.dartファイルに保存します。

import 'package:intl/intl.dart'; class Expense {
   final int id;
   final double amount;
   final DateTime date;
   final String category;
   String get formattedDate {
      var formatter = new DateFormat('yyyy-MM-dd');
      return formatter.format(this.date);
   }
   static final columns = ['id', 'amount', 'date', 'category'];
   Expense(this.id, this.amount, this.date, this.category);
   factory Expense.fromMap(Map<String, dynamic> data) {
      return Expense(
         data['id'],
         data['amount'],
         DateTime.parse(data['date']), data['category']
      );
   }
   Map<String, dynamic> toMap() => {
      "id" : id,
      "amount" : amount,
      "date" : date.toString(),
      "category" : category,
   };
}

上記のコードは単純で自明です。

新しいファイルDatabase.dartを追加して、SQLiteDbProviderクラスを作成します。 SQLiteDbProviderクラスの目的は次のとおりです-

  • getAllExpensesメソッドを使用して、データベースで利用可能なすべての費用を取得します。 すべてのユーザーの費用情報をリストするために使用されます。
Future<List<Expense>> getAllExpenses() async {
   final db = await database;

   List<Map> results = await db.query(
      "Expense", columns: Expense.columns, orderBy: "date DESC"
   );
   List<Expense> expenses = new List();
   results.forEach((result) {
      Expense expense = Expense.fromMap(result);
      expenses.add(expense);
   });
   return expenses;
}
  • getExpenseByIdメソッドを使用して、データベースで使用可能な経費IDに基づいて特定の経費情報を取得します。 特定の経費情報をユーザーに表示するために使用されます。
Future<Expense> getExpenseById(int id) async {
   final db = await database;
   var result = await db.query("Expense", where: "id = ", whereArgs: [id]);

   return result.isNotEmpty ?
   Expense.fromMap(result.first) : Null;
}
  • getTotalExpenseメソッドを使用して、ユーザーの総費用を取得します。 現在の総費用をユーザーに示すために使用されます。
Future<double> getTotalExpense() async {
   final db = await database;
   List<Map> list = await db.rawQuery(
      "Select SUM(amount) as amount from expense"
   );
   return list.isNotEmpty ? list[0]["amount"] : Null;
}
  • insertメソッドを使用して、新しい経費情報をデータベースに追加します。 ユーザーが新しい経費エントリをアプリケーションに追加するために使用されます。
Future<Expense> insert(Expense expense) async {
   final db = await database;
   var maxIdResult = await db.rawQuery(
      "SELECT MAX(id)+1 as last_inserted_id FROM Expense"
   );
   var id = maxIdResult.first["last_inserted_id"];
   var result = await db.rawInsert(
      "INSERT Into Expense (id, amount, date, category)"
      " VALUES (?, ?, ?, ?)", [
         id, expense.amount, expense.date.toString(), expense.category
      ]
   );
   return Expense(id, expense.amount, expense.date, expense.category);
}
  • 更新方法を使用して、既存の費用情報を更新します。 ユーザーがシステムで使用できる既存の経費エントリを編集および更新するために使用されます。
update(Expense product) async {
   final db = await database;

   var result = await db.update("Expense", product.toMap(),
   where: "id = ?", whereArgs: [product.id]);
   return result;
}
  • deleteメソッドを使用して、既存の経費情報を削除します。 ユーザーがシステムで使用可能な既存の経費エントリを削除するために使用されます。
delete(int id) async {
   final db = await database;
   db.delete("Expense", where: "id = ?", whereArgs: [id]);
}
  • SQLiteDbProviderクラスの完全なコードは次のとおりです-
import 'dart:async';
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'Expense.dart';

class SQLiteDbProvider {
   SQLiteDbProvider._();
   static final SQLiteDbProvider db = SQLiteDbProvider._();

   static Database _database; Future<Database> get database async {
      if (_database != null)
         return _database;
      _database = await initDB();
      return _database;
   }
   initDB() async {
      Directory documentsDirectory = await getApplicationDocumentsDirectory();
      String path = join(documentsDirectory.path, "ExpenseDB2.db");
      return await openDatabase(
         path, version: 1, onOpen:(db){}, onCreate: (Database db, int version) async {
            await db.execute(
               "CREATE TABLE Expense (
                  ""id INTEGER PRIMARY KEY," "amount REAL," "date TEXT," "category TEXT""
               )
            ");
            await db.execute(
               "INSERT INTO Expense ('id', 'amount', 'date', 'category')
               values (?, ?, ?, ?)",[1, 1000, '2019-04-01 10:00:00', "Food"]
            );
           /*await db.execute(
               "INSERT INTO Product ('id', 'name', 'description', 'price', 'image')
               values (?, ?, ?, ?, ?)", [
                  2, "Pixel", "Pixel is the most feature phone ever", 800, "pixel.png"
               ]
            );
            await db.execute(
               "INSERT INTO Product ('id', 'name', 'description', 'price', 'image')
               values (?, ?, ?, ?, ?)", [
                  3, "Laptop", "Laptop is most productive development tool", 2000, "laptop.png"
               ]
            );
            await db.execute(
               "INSERT INTO Product ('id', 'name', 'description', 'price', 'image')
               values (?, ?, ?, ?, ?)", [
                  4, "Tablet", "Laptop is most productive development tool", 1500, "tablet.png"
               ]
            );
            await db.execute(
               "INSERT INTO Product ('id', 'name', 'description', 'price', 'image')
               values (?, ?, ?, ?, ?)", [
                  5, "Pendrive", "iPhone is the stylist phone ever", 100, "pendrive.png"
               ]
            );
            await db.execute(
               "INSERT INTO Product ('id', 'name', 'description', 'price', 'image')
               values (?, ?, ?, ?, ?)", [
                  6, "Floppy Drive", "iPhone is the stylist phone ever", 20, "floppy.png"
               ]
            ); */
         }
      );
   }
   Future<List<Expense>> getAllExpenses() async {
      final db = await database;
      List<Map>
      results = await db.query(
         "Expense", columns: Expense.columns, orderBy: "date DESC"
      );
      List<Expense> expenses = new List();
      results.forEach((result) {
         Expense expense = Expense.fromMap(result);
         expenses.add(expense);
      });
      return expenses;
   }
   Future<Expense> getExpenseById(int id) async {
      final db = await database;
      var result = await db.query("Expense", where: "id = ", whereArgs: [id]);
      return result.isNotEmpty ? Expense.fromMap(result.first) : Null;
   }
   Future<double> getTotalExpense() async {
      final db = await database;
      List<Map> list = await db.rawQuery(
         "Select SUM(amount) as amount from expense"
      );
      return list.isNotEmpty ? list[0]["amount"] : Null;
   }
   Future<Expense> insert(Expense expense) async {
      final db = await database;
      var maxIdResult = await db.rawQuery(
         "SELECT MAX(id)+1 as last_inserted_id FROM Expense"
      );
      var id = maxIdResult.first["last_inserted_id"];
      var result = await db.rawInsert(
         "INSERT Into Expense (id, amount, date, category)"
         " VALUES (?, ?, ?, ?)", [
            id, expense.amount, expense.date.toString(), expense.category
         ]
      );
      return Expense(id, expense.amount, expense.date, expense.category);
   }
   update(Expense product) async {
      final db = await database;
      var result = await db.update(
         "Expense", product.toMap(), where: "id = ?", whereArgs: [product.id]
      );
      return result;
   }
   delete(int id) async {
      final db = await database;
      db.delete("Expense", where: "id = ?", whereArgs: [id]);
   }
}

ここに、

  • データベースは、SQLiteDbProviderオブジェクトを取得するプロパティです。
  • initDBは、SQLiteデータベースを選択して開くために使用されるメソッドです。

新しいファイルExpenseListModel.dartを作成して、ExpenseListModelを作成します。 モデルの目的は、ユーザーの費用の完全な情報をメモリに保持し、ユーザーの費用がメモリで変更されるたびにアプリケーションのユーザーインターフェイスを更新することです。 scoped_modelパッケージのModelクラスに基づいています。 次のプロパティとメソッドがあります-

  • _items-費用のプライベートリスト。
  • items-リストへの予期しないまたは偶発的な変更を防ぐためのUnmodifiableListView <Expense>としての_itemsのゲッター。
  • totalExpense-アイテム変数に基づいた総費用のゲッター。
double get totalExpense {
   double amount = 0.0;
   for(var i = 0; i < _items.length; i++) {
      amount = amount + _items[i].amount;
   }
   return amount;
}
  • load-経費をデータベースから_items変数にロードするために使用されます。 また、UIを更新するためにnotifyListenersを呼び出します。
void load() {
   Future<List<Expense>>
   list = SQLiteDbProvider.db.getAllExpenses();
   list.then( (dbItems) {
      for(var i = 0; i < dbItems.length; i++) {
         _items.add(dbItems[i]);
      } notifyListeners();
   });
}
  • byId-_items変数から特定の費用を取得するために使用されます。
Expense byId(int id) {
   for(var i = 0; i < _items.length; i++) {
      if(_items[i].id == id) {
         return _items[i];
      }
   }
   return null;
}
  • add-新しい経費項目を_items変数とデータベースに追加するために使用します。 また、UIを更新するためにnotifyListenersを呼び出します。
void add(Expense item) {
   SQLiteDbProvider.db.insert(item).then((val) {
      _items.add(val); notifyListeners();
   });
}
  • 更新-経費項目を_items変数およびデータベースに更新するために使用されます。 また、UIを更新するためにnotifyListenersを呼び出します。
void update(Expense item) {
   bool found = false;
   for(var i = 0; i < _items.length; i++) {
      if(_items[i].id == item.id) {
         _items[i] = item;
         found = true;
         SQLiteDbProvider.db.update(item); break;
      }
   }
   if(found) notifyListeners();
}
  • delete-データベースからだけでなく、_items変数内の既存の経費項目を削除するために使用されます。 また、UIを更新するためにnotifyListenersを呼び出します。
void delete(Expense item) {
   bool found = false;
   for(var i = 0; i < _items.length; i++) {
      if(_items[i].id == item.id) {
         found = true;
         SQLiteDbProvider.db.delete(item.id);
         _items.removeAt(i); break;
      }
   }
   if(found) notifyListeners();
}
  • ExpenseListModelクラスの完全なコードは次のとおりです-
import 'dart:collection';
import 'package:scoped_model/scoped_model.dart';
import 'Expense.dart';
import 'Database.dart';

class ExpenseListModel extends Model {
   ExpenseListModel() {
      this.load();
   }
   final List<Expense> _items = [];
   UnmodifiableListView<Expense> get items =>
   UnmodifiableListView(_items);

  /*Future<double> get totalExpense {
      return SQLiteDbProvider.db.getTotalExpense();
   }*/

   double get totalExpense {
      double amount = 0.0;
      for(var i = 0; i < _items.length; i++) {
         amount = amount + _items[i].amount;
      }
      return amount;
   }
   void load() {
      Future<List<Expense>> list = SQLiteDbProvider.db.getAllExpenses();
      list.then( (dbItems) {
         for(var i = 0; i < dbItems.length; i++) {
            _items.add(dbItems[i]);
         }
         notifyListeners();
      });
   }
   Expense byId(int id) {
      for(var i = 0; i < _items.length; i++) {
         if(_items[i].id == id) {
            return _items[i];
         }
      }
      return null;
   }
   void add(Expense item) {
      SQLiteDbProvider.db.insert(item).then((val) {
         _items.add(val);
         notifyListeners();
      });
   }
   void update(Expense item) {
      bool found = false;
      for(var i = 0; i < _items.length; i++) {
         if(_items[i].id == item.id) {
            _items[i] = item;
            found = true;
            SQLiteDbProvider.db.update(item);
            break;
         }
      }
      if(found) notifyListeners();
   }
   void delete(Expense item) {
      bool found = false;
      for(var i = 0; i < _items.length; i++) {
         if(_items[i].id == item.id) {
            found = true;
            SQLiteDbProvider.db.delete(item.id);
            _items.removeAt(i); break;
         }
      }
      if(found) notifyListeners();
   }
}
  • main.dartファイルを開きます。 以下に指定されているようにクラスをインポートします-
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'ExpenseListModel.dart';
import 'Expense.dart';
  • メイン関数を追加し、ScopedModel <ExpenseListModel>ウィジェットを渡してrunAppを呼び出します。
void main() {
   final expenses = ExpenseListModel();
   runApp(
      ScopedModel<ExpenseListModel>(model: expenses, child: MyApp(),)
   );
}

ここに、

  • 経費オブジェクトは、データベースからすべてのユーザー経費情報をロードします。 また、アプリケーションを初めて開いたときに、適切なテーブルを使用して必要なデータベースが作成されます。
  • ScopedModelは、アプリケーションのライフサイクル全体で費用情報を提供し、どのインスタンスでもアプリケーションの状態を維持します。 これにより、StatefulWidgetの代わりにStatelessWidgetを使用できます。

MaterialAppウィジェットを使用して簡単なMyAppを作成します。

class MyApp extends StatelessWidget {
  //This widget is the root of your application.
   @override
   Widget build(BuildContext context) {
      return MaterialApp(
         title: 'Expense',
         theme: ThemeData(
            primarySwatch: Colors.blue,
         ),
         home: MyHomePage(title: 'Expense calculator'),
      );
   }
}
  • MyHomePageウィジェットを作成して、すべてのユーザーの費用情報と合計費用を上部に表示します。 右下隅のフローティングボタンは、新しい費用を追加するために使用されます。
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key);
   final String title;
   @override
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(
            title: Text(this.title),
         ),
         body: ScopedModelDescendant<ExpenseListModel>(
            builder: (context, child, expenses) {
               return ListView.separated(
                  itemCount: expenses.items == null ? 1
                  : expenses.items.length + 1,
                  itemBuilder: (context, index) {
                     if (index == 0) {
                        return ListTile(
                           title: Text("Total expenses: "
                           + expenses.totalExpense.toString(),
                           style: TextStyle(fontSize: 24,
                           fontWeight: FontWeight.bold),)
                        );
                     } else {
                        index = index - 1;
                        return Dismissible(
                           key: Key(expenses.items[index].id.toString()),
                              onDismissed: (direction) {
                              expenses.delete(expenses.items[index]);
                              Scaffold.of(context).showSnackBar(
                                 SnackBar(
                                    content: Text(
                                       "Item with id, "
                                       + expenses.items[index].id.toString() +
                                       " is dismissed"
                                    )
                                 )
                              );
                           },
                           child: ListTile( onTap: () {
                              Navigator.push(
                                 context, MaterialPageRoute(
                                    builder: (context) => FormPage(
                                       id: expenses.items[index].id,
                                       expenses: expenses,
                                    )
                                 )
                              );
                           },
                           leading: Icon(Icons.monetization_on),
                           trailing: Icon(Icons.keyboard_arrow_right),
                           title: Text(expenses.items[index].category + ": " +
                           expenses.items[index].amount.toString() +
                           " \nspent on " + expenses.items[index].formattedDate,
                           style: TextStyle(fontSize: 18, fontStyle: FontStyle.italic),))
                        );
                     }
                  },
                  separatorBuilder: (context, index) {
                     return Divider();
                  },
               );
            },
         ),
         floatingActionButton: ScopedModelDescendant<ExpenseListModel>(
            builder: (context, child, expenses) {
               return FloatingActionButton( onPressed: () {
                  Navigator.push(
                     context, MaterialPageRoute(
                        builder: (context) => ScopedModelDescendant<ExpenseListModel>(
                           builder: (context, child, expenses) {
                              return FormPage( id: 0, expenses: expenses, );
                           }
                        )
                     )
                  );
                 //expenses.add(new Expense(
                    //2, 1000, DateTime.parse('2019-04-01 11:00:00'), 'Food')
                  );
                 //print(expenses.items.length);
               },
               tooltip: 'Increment', child: Icon(Icons.add), );
            }
         )
      );
   }
}

ここに、

  • ScopedModelDescendantは、費用モデルをListViewおよびFloatingActionButtonウィジェットに渡すために使用されます。
  • ListView.separatedおよびListTileウィジェットは、経費情報をリストするために使用されます。
  • Dismissibleウィジェットは、スワイプジェスチャーを使用して経費エントリを削除するために使用されます。
  • ナビゲータは、経費エントリの編集インターフェイスを開くために使用されます。 経費入力をタップすることで有効にできます。

FormPageウィジェットを作成します。 FormPageウィジェットの目的は、経費エントリを追加または更新することです。 経費入力の検証も処理します。

class FormPage extends StatefulWidget {
   FormPage({Key key, this.id, this.expenses}) : super(key: key);
   final int id;
   final ExpenseListModel expenses;

   @override _FormPageState createState() => _FormPageState(id: id, expenses: expenses);
}
class _FormPageState extends State<FormPage> {
   _FormPageState({Key key, this.id, this.expenses});

   final int id;
   final ExpenseListModel expenses;
   final scaffoldKey = GlobalKey<ScaffoldState>();
   final formKey = GlobalKey<FormState>();

   double _amount;
   DateTime _date;
   String _category;

   void _submit() {
      final form = formKey.currentState;
      if (form.validate()) {
         form.save();
         if (this.id == 0) expenses.add(Expense(0, _amount, _date, _category));
            else expenses.update(Expense(this.id, _amount, _date, _category));
         Navigator.pop(context);
      }
   }
   @override
   Widget build(BuildContext context) {
      return Scaffold(
         key: scaffoldKey, appBar: AppBar(
            title: Text('Enter expense details'),
         ),
         body: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Form(
               key: formKey, child: Column(
                  children: [
                     TextFormField(
                        style: TextStyle(fontSize: 22),
                        decoration: const InputDecoration(
                           icon: const Icon(Icons.monetization_on),
                           labelText: 'Amount',
                           labelStyle: TextStyle(fontSize: 18)
                        ),
                        validator: (val) {
                           Pattern pattern = r'^[1-9]\d*(\.\d+)?$';
                           RegExp regex = new RegExp(pattern);
                           if (!regex.hasMatch(val))
                           return 'Enter a valid number'; else return null;
                        },
                        initialValue: id == 0
                        ? '' : expenses.byId(id).amount.toString(),
                        onSaved: (val) => _amount = double.parse(val),
                     ),
                     TextFormField(
                        style: TextStyle(fontSize: 22),
                        decoration: const InputDecoration(
                           icon: const Icon(Icons.calendar_today),
                           hintText: 'Enter date',
                           labelText: 'Date',
                           labelStyle: TextStyle(fontSize: 18),
                        ),
                        validator: (val) {
                           Pattern pattern = r'^((?:19|20)\d\d)[-/.]
                              (0[1-9]|1[012])[-/.](0[1-9]|[12][0-9]|3[01])$';
                           RegExp regex = new RegExp(pattern);
                           if (!regex.hasMatch(val))
                              return 'Enter a valid date';
                           else return null;
                        },
                        onSaved: (val) => _date = DateTime.parse(val),
                        initialValue: id == 0
                        ? '' : expenses.byId(id).formattedDate,
                        keyboardType: TextInputType.datetime,
                     ),
                     TextFormField(
                        style: TextStyle(fontSize: 22),
                        decoration: const InputDecoration(
                           icon: const Icon(Icons.category),
                           labelText: 'Category',
                           labelStyle: TextStyle(fontSize: 18)
                        ),
                        onSaved: (val) => _category = val,
                        initialValue: id == 0 ? ''
                        : expenses.byId(id).category.toString(),
                     ),
                     RaisedButton(
                        onPressed: _submit,
                        child: new Text('Submit'),
                     ),
                  ],
               ),
            ),
         ),
      );
   }
}

ここに、

  • TextFormFieldは、フォームエントリの作成に使用されます。
  • TextFormFieldのvalidatorプロパティは、RegExパターンとともにフォーム要素を検証するために使用されます。
  • _submit関数は、経費オブジェクトとともに使用して、経費をデータベースに追加または更新します。
  • main.dartファイルの完全なコードは次のとおりです-
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'ExpenseListModel.dart';
import 'Expense.dart';

void main() {
   final expenses = ExpenseListModel();
   runApp(
      ScopedModel<ExpenseListModel>(
         model: expenses, child: MyApp(),
      )
   );
}
class MyApp extends StatelessWidget {
  //This widget is the root of your application.
   @override
   Widget build(BuildContext context) {
      return MaterialApp(
         title: 'Expense',
         theme: ThemeData(
            primarySwatch: Colors.blue,
         ),
         home: MyHomePage(title: 'Expense calculator'),
      );
   }
}
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key);
   final String title;

   @override
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(
            title: Text(this.title),
         ),
         body: ScopedModelDescendant<ExpenseListModel>(
            builder: (context, child, expenses) {
               return ListView.separated(
                  itemCount: expenses.items == null ? 1
                  : expenses.items.length + 1, itemBuilder: (context, index) {
                     if (index == 0) {
                        return ListTile( title: Text("Total expenses: "
                        + expenses.totalExpense.toString(),
                        style: TextStyle(fontSize: 24,fontWeight:
                        FontWeight.bold),) );
                     } else {
                        index = index - 1; return Dismissible(
                           key: Key(expenses.items[index].id.toString()),
                           onDismissed: (direction) {
                              expenses.delete(expenses.items[index]);
                              Scaffold.of(context).showSnackBar(
                                 SnackBar(
                                    content: Text(
                                       "Item with id, " +
                                       expenses.items[index].id.toString()
                                       + " is dismissed"
                                    )
                                 )
                              );
                           },
                           child: ListTile( onTap: () {
                              Navigator.push( context, MaterialPageRoute(
                                 builder: (context) => FormPage(
                                    id: expenses.items[index].id, expenses: expenses,
                                 )
                              ));
                           },
                           leading: Icon(Icons.monetization_on),
                           trailing: Icon(Icons.keyboard_arrow_right),
                           title: Text(expenses.items[index].category + ": " +
                           expenses.items[index].amount.toString() + " \nspent on " +
                           expenses.items[index].formattedDate,
                           style: TextStyle(fontSize: 18, fontStyle: FontStyle.italic),))
                        );
                     }
                  },
                  separatorBuilder: (context, index) {
                     return Divider();
                  },
               );
            },
         ),
         floatingActionButton: ScopedModelDescendant<ExpenseListModel>(
            builder: (context, child, expenses) {
               return FloatingActionButton(
                  onPressed: () {
                     Navigator.push(
                        context, MaterialPageRoute(
                           builder: (context)
                           => ScopedModelDescendant<ExpenseListModel>(
                              builder: (context, child, expenses) {
                                 return FormPage( id: 0, expenses: expenses, );
                              }
                           )
                        )
                     );
                    //expenses.add(
                        new Expense(
                          //2, 1000, DateTime.parse('2019-04-01 11:00:00'), 'Food'
                        )
                     );
                    //print(expenses.items.length);
                  },
                  tooltip: 'Increment', child: Icon(Icons.add),
               );
            }
         )
      );
   }
}
class FormPage extends StatefulWidget {
   FormPage({Key key, this.id, this.expenses}) : super(key: key);
   final int id;
   final ExpenseListModel expenses;

   @override
   _FormPageState createState() => _FormPageState(id: id, expenses: expenses);
}
class _FormPageState extends State<FormPage> {
   _FormPageState({Key key, this.id, this.expenses});
   final int id;
   final ExpenseListModel expenses;
   final scaffoldKey = GlobalKey<ScaffoldState>();
   final formKey = GlobalKey<FormState>();
   double _amount; DateTime _date;
   String _category;
   void _submit() {
      final form = formKey.currentState;
      if (form.validate()) {
         form.save();
         if (this.id == 0) expenses.add(Expense(0, _amount, _date, _category));
         else expenses.update(Expense(this.id, _amount, _date, _category));
         Navigator.pop(context);
      }
   }
   @override
   Widget build(BuildContext context) {
      return Scaffold(
         key: scaffoldKey, appBar: AppBar(
            title: Text('Enter expense details'),
         ),
         body: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Form(
               key: formKey, child: Column(
                  children: [
                     TextFormField(
                        style: TextStyle(fontSize: 22),
                        decoration: const InputDecoration(
                           icon: const Icon(Icons.monetization_on),
                           labelText: 'Amount',
                           labelStyle: TextStyle(fontSize: 18)
                        ),
                        validator: (val) {
                           Pattern pattern = r'^[1-9]\d*(\.\d+)?$';
                           RegExp regex = new RegExp(pattern);
                           if (!regex.hasMatch(val)) return 'Enter a valid number';
                           else return null;
                        },
                        initialValue: id == 0 ? ''
                        : expenses.byId(id).amount.toString(),
                        onSaved: (val) => _amount = double.parse(val),
                     ),
                     TextFormField(
                        style: TextStyle(fontSize: 22),
                        decoration: const InputDecoration(
                           icon: const Icon(Icons.calendar_today),
                           hintText: 'Enter date',
                           labelText: 'Date',
                           labelStyle: TextStyle(fontSize: 18),
                        ),
                        validator: (val) {
                           Pattern pattern = r'^((?:19|20)\d\d)[-/.]
                           (0[1-9]|1[012])[-/.](0[1-9]|[12][0-9]|3[01])$';
                           RegExp regex = new RegExp(pattern);
                           if (!regex.hasMatch(val)) return 'Enter a valid date';
                           else return null;
                        },
                        onSaved: (val) => _date = DateTime.parse(val),
                        initialValue: id == 0 ? '' : expenses.byId(id).formattedDate,
                        keyboardType: TextInputType.datetime,
                     ),
                     TextFormField(
                        style: TextStyle(fontSize: 22),
                        decoration: const InputDecoration(
                           icon: const Icon(Icons.category),
                           labelText: 'Category',
                           labelStyle: TextStyle(fontSize: 18)
                        ),
                        onSaved: (val) => _category = val,
                        initialValue: id == 0 ? '' : expenses.byId(id).category.toString(),
                     ),
                     RaisedButton(
                        onPressed: _submit,
                        child: new Text('Submit'),
                     ),
                  ],
               ),
            ),
         ),
      );
   }
}
  • 次に、アプリケーションを実行します。
  • フローティングボタンを使用して新しい費用を追加します。
  • 経費入力をタップして、既存の経費を編集します。
  • 経費エントリをいずれかの方向にスワイプして、既存の経費を削除します。

アプリケーションのスクリーンショットのいくつかは次のとおりです-

経費計算ツール

経費詳細の入力

総費用

フラッター-結論

Flutterフレームワークは、真にプラットフォームに依存しない方法でモバイルアプリケーションを構築するための優れたフレームワークを提供することにより、素晴らしい仕事をします。 Flutterフレームワークは、開発プロセスのシンプルさ、結果のモバイルアプリケーションの高性能、AndroidプラットフォームとiOSプラットフォームの両方に対応するリッチで関連性の高いユーザーインターフェイスを提供することで、多くの新しい開発者が近い将来。