前のページ

開発編

次のページ



Pub パッケージを活用する

Pubのサイトには膨大なライブラリが蓄積されている。このライブラリ内を検索すれば、自分のアプリケーション開発に有用なライブラリを見つけることが可能である。本章では読者のPubパッケージの活用の為の参考として、幾つかのパッケージを具体的に紹介してみたい。

なおtimeagosqljocky5、及びloggingのサンプルはIDEの章で取り込んだdart2_code_samplesの中のC:\dart2_code_samples\utilizing_packagesというフォルダの中に含まれている。





timeagosqljocky5、及びloggingの各アプリにはpubspe.yamlというファイルが含まれている。このファイルは外部パッケージを取り込むか、及びどの開発環境を使うかなどを指定するもので、Dart 2のアプリには必須となっている。従ってこれらのファイルを選択してPub getコマンドを実行しないと、上図のように赤のアンダラインでこのままでは実行不能と表示される。

例えばlogging_testでは下図のようにC:\dart2_code_samples\utilizing_packages\logging_test\pubspec.yamlをコード・ビュー上で開き、Get dependenciesをクリックする。





これには少々時間がかかるが、logging及びtestのパッケージが取り込まれ、赤のアンダーラインの警告がが消える。またlogging_test\.dart_toolというフォルダ、及びlogging_test\.packageslogging_test\pubspec.lockという2つのファイルが追加されることに注意する。同様の操作をtimeago及びsqljocky5に対しても行っておく。



mime_type

mime_typeは僭越ながら筆者がDart言語開発初期の2013年にパブリッシュしたもので、HTTPサーバがファイルをクライアントに返す際に必要な'Content-Type'をそのファイルパスをもとに返すシンプルな関数である。その後Dartチームがこのライブラリを拡張mimeパブリッシュし、MIMEマルチパート要求にも対応できるようにしている。従って、このライブラリは歴史的な意味があること、及び極めて簡単なもので誰もがそのコードを見て理解できるという点で価値が未だ存在しているのかもしれない。

筆者がしばらくこの世界から離れていた間に、DartDart 2になり、このライブラリはGoogleRobert Beyer氏が自分のライブラリのroute_providerの依存物として使ってくれている。この際彼はこのライブラリをDart 2に対応するよう手を加えてくれ、筆者がそれを再パブリッシュしたものである。

従ってこのライブラリのページを開くとaboutの枠の中は次のように表示されている:







このように自分が作ったライブラリが、思わぬ人たちによって成熟して行くのが経験できるのはこのシステムの大きな特徴である。それらの経過はgithubissues及びpull requestsのタブの終了したもの(closed)を開くと知ることができる。

このパッケージの使い方は「サーバ編」で解説する。



timeago v2

timeago v2はそのページをアップロードされた時刻等のイベント時刻を与えることで「何日前」とか「何か月前」とか表示するためのものである。几帳面な日本人にはしっくり来ないが、海外ではアバウトなほうが良いらしく、よく目にする。

このライブラリの構成は次のようになっている:

timeago/
    example/
        index.html
        main.dart
    lib/
        src/
            messages/
                lookupmessages.dart
                da_messages.dart

                ja_messages.dart

                zh_messages.dart
            timeago.dart
        timeago.dart
    test/
    CHANGELOG.md
    LICENSE
    README.md
    pubspec.yaml
.

これは「ライブラリ・パッケージを構成する」で説明されているlib/src/内に.dartファイルを置く方式をとっている。これらのファイルはlib/timeago.dartによりエクスポートされている(lib/src/内にもtimeago.dartがあるので混同しないこと)。なおlib/src/messages/loolkupmessages.dartは抽象クラスで、これを各国向けのmessages.dart(例えば日本語ではja_messages.dart)のなかで具体化している方式になっている。

このライブラリを使ったtimeago_testというウェブ・アプリを紹介する。このアプリをブラウザで開いた画面をまずお見せする:





左半分がブラウザ画面で、右半分がデベロッパー・ツールのコンソール表示である。3個のセレクション・メニューに自分のエポック(イベントの時刻等)を選択して「現時刻との差を表示」のボタンをクリックすると、以下のものが表示される:

  • ISO 8601形式での現在の時刻

  • ISO 8601形式でのエポックの時刻

  • 両者の間の差の日数での表示(マイナスは現時刻以後)

  • 両者の間の差の時間での表示(マイナスは現時刻以後)

このプログラムはブラウザのタブを消さない限り走り続けるので、いろいろその動作を試してみることが可能である。

このプログラムは既に読者のIDE上で展開されている筈である。これはウェブ・アプリであるので、Buildコマンドを実行してbuildというフォルダに配布用の以下のファイルを用意しなければならない。





  • timeago_test\build\timeago_test.dart.jswebフォルダ名にあるtimeago_test.dartJavaScriptに変換したファイル

  • timeago_test\build\timeago_test.htmlwebフォルダ名にあるtimeago_test.htmlと同じ物

この2つが組になってクライアント側に渡されることになる。従ってtimeago_test\build\timeago_test.htmlを最初にクライアントに渡すと、クライアントのブラウザはそれに従ってtimeago_test.dartを要求してくる。

コードは次のようになっているので、各自自分のIDE上で自分なりに加工してみると良い:

pubspec.yaml
name: timeago_test
description: >
  A sample web application using Pub timeago library
version: 0.0.1
author:
homepage:
environment:
  sdk: '>=2.0.0-dev <3.0.0'
#dependencies:
dependencies:
  timeago: ^2.0.13
dev_dependencies:
  build_runner: '>=0.8.10 <2.0.0'
  build_test: ^0.10.2
  build_web_compilers: '>=0.3.6 <2.0.0'
web/timeago_test.dart
import 'package:timeago/timeago.dart' as timeago;
import 'dart:html';
main() {
  // Add a new locale messages
  timeago.setLocaleMessages('ja', timeago.JaMessages());
  // See if Dart is running
  var mesArea = document.getElementById('title');
  mesArea.innerHtml = 'dart code started';
  // Go button listening forever
  var goButton = document.getElementById('goTimeago');
  // On click event handler
  goButton.onClick.listen((e) {
    var el, p;
    el = document.getElementById('epochYear') as SelectElement;
    var epochYear = el.value;
    el = document.getElementById('epochMonth') as SelectElement;
    p = ('0' + el.value);
    var epochMonth = p.substring(p.length - 2);
    el = document.getElementById('epochDay') as SelectElement;
    p = ('0' + el.value);
    var epochDay = p.substring(p.length - 2);
    var tstr = '$epochYear-$epochMonth-${epochDay}T12:00';
    var epochDateTime = DateTime.parse(tstr);
    var currentDateTime = DateTime.now();
    var agoDateTime = currentDateTime.difference(epochDateTime);
    print('  epoch time is :  ${epochDateTime.toIso8601String()}');
    print('current time is :  ${currentDateTime.toIso8601String()}');
    print('    duration is :  ${agoDateTime}');
    print('duration_inDays :  ${agoDateTime.inDays}');
    print('duration_inHours:  ${agoDateTime.inHours}');
    document.getElementById('timeAgo1').innerHtml = "<br>"
        "<br>current time is : ${currentDateTime.toIso8601String()}"
        "<br>epoch time is   : ${epochDateTime.toIso8601String()}"
        "<br>inDays :  ${agoDateTime.inDays}"
        "<br>inHours : ${agoDateTime.inHours}";
    var timeagoResult =
        timeago.format(epochDateTime, locale: 'ja', allowFromNow: true);
    print('datetime_output :  $timeagoResult');
    document.getElementById('timeAgo2').innerHtml = "<br> $timeagoResult";
  });
}
web/timeago_test.html
長いので省略(リンクを開いてその画面を右クリックし、「ページのソースを表示」を選択し、コピーする)

読者がこの3つのファイルを使ってIDE上で試されるにあたって、幾つかの注意点を示すと:

  1. これはウェブ・アプリであるので、timeago_test.darttimeago_test.html(それにもしあればCSS)のファイルは必ずweb/の下に置くようにする

  2. timeago_test.dartでは

    el = document.getElementById('epochYear') as SelectElement;
    var epochYear = el.value;

    のように、getElementByIDメソッドはElementのオブジェクトと見做しているが、Element→HtmlElement→SelectElementという実装ツリーになっており、実際は孫のクラスを取り出しているので、asを使って型キャストしなければならない。でないとvalue属性は取得できない。

  3. このパッケージではまず日本語のロケールを以下のようにセットする:

    timeago.setLocaleMessages('ja', timeago.JaMessages());
  4. 次にformatメソッドを呼ぶに際しては:

    timeago.format(epochDateTime, locale: 'ja', allowFromNow: true);

    のように、allowFromNow:trueにしないと現時点以降のエポックが「今から」といった表示がされない。

なお従来は「2 月 前」といった具合に月表示が不自然なので、「か月」表示にするよう作者にバグ報告し、早速対応してくれ、2.0.13にバージョン・アップされている。



sqljocky5

通常大規模アプリではデータベースを使う場合が殆どであろう。その中でもMySQLは世界的に広く使われているRDBMSである。

SQLJockyMySQL用のDart言語用のドライバで、長い歴史を持つ。このコネクタの著者のひとりのJames Otsによれば、sqljockeyという名前は著名なダーツの選手のJocky Wilsonが由来だという。

注意:このパッケージは20193月の時点で最新の2.2.1版にまだ問題が残っているので、この資料ではひとつ前の2.2.0を使用している。新しい2.2.1版ではAPIに一部変更(preparedAllメソッドなどで)があるので、将来新しい版を採用する場合は注意が必要である。その場合のサンプル・コードはここにある。



MySQLのインストール

読者のコンピュータ上にMySQLがインストールされている必要がある。ダウンロードとインストールの日本語の手順はネット上で検索(例えばここ)して頂きたい。

MySQLが初めてのユーザは:

  • 無料のCommunity(MySQL Community Server)をダウンロードする。

  • Windowsの場合、MicrosoftVisualC++がインストールされていないときは、これの最新の再配布版(redistributable package)をインストールしておく。

  • Shellコマンドを使うよりもワークベンチを使うほうが便利であろう。



設定ファイルの変更

Dartsqljocky5ドライバは未だMySQL8.0で追加されたSHA256認証に対応していない。しかし20193月現在作業中で、近い将来対応可能となる予定である。従ってそれまでの間はMySQLcaching_sha2_passwordプラグインからmysql_native_passwordに変更しなければならない。近い将来このドライバがSHA256認証に対応するようになればこの作業は不要となる。

これにはMySQLの設定ファイルに以下のテキストを追加する:

[mysqld]
default-authentication-plugin=mysql_native_password
  1. このファイルのパスはWindowsでは C:\Program Data\MySQL\MySQL Server 8.0\my.ini である。(Program Dataというディレクトリは通常隠しファイルであるので、エクスプローラの表示から隠しファイルのチェックボックスを入れる)

  2. このファイルをメモ帳で開くと、[mysqlid]の区間はSERVER SECTIONの区間の中にある。この区間の中にある

    # The default authentication plugin to be used when connecting to the server
    default_authentication_plugin=caching_sha2_password

    を次のように変更し、上書き保存する:

    # The default authentication plugin to be used when connecting to the server
    # default_authentication_plugin=caching_sha2_password
    default-authentication-plugin=mysql_native_password
  3. MySQLサーバを再起動する。

MySQLサーバの再起動はタスク・マネージャから行ったほうが手っ取り早い:





  1. タスクマネージャ→サービスでMySQL80を選択し、右クリックで停止させ、すこし時間をおいて開始させる。



新しい接続を用意する

ワークベンチ上でsqljocky5の為の新しい接続を以下のように用意する。MySQL Workbench Community では、root コネクションで接続してから操作を行う。最初にウェルカム画面からMySQL ConnectionsLocal instanse MySQL 80の表示個所をクリックしてこの接続の詳細を表示させる:





そうすると下図のようなインスタンス表示画面が表示される:





次にこのインスタンス表示画面の左側のAdministraionタブにあるUsers and Privilegesを選択し、Add Accountボタンをクリックして以下のように設定してやる:

  • user : sqljocky5

  • Login Name : sqljocky5

  • Authentication Type : Standard

  • Limit to Hosta Matching : localhost

  • Password : dart_jaguar

    終了したらApplyボタンをクリックすると、下図のようになる:





次にアカウントにデータベースのアクセス権を設定する。ユーザ・アカウントのsqljocky5を選択して、Add Entry...ボタンを押して、Select “ALL”を選択したのちApplyボタンをクリックする:







データベースのスキーマを用意する

まずワークベンチのLocal instance MYSQL80をクリックすると次のようなクエリのエディタ画面がでてくる:





左上にあるデータベースのアイコンをクリックすると、新規のスキーマ作成画面が表示されるので、以下のように指定する:

  • Name : sqljocky5

  • Caraset : utf8

  • Collation : utf8_general_ci

    入力したらApplyボタンをクリックする





Revew画面が出てくるので、そこでもApplyボタンをクリックする。同様にレビュー画面が出るので、確認の上Applyボタンをクリックする。

これでsqljocky5MySQLに接続可能となった。



サンプル・プログラム

サンプル・プログラムがPubにあるので、これを一部加工したものを示す。このアプリは既に読者のIDE上にC:\dart2_code_samples\utilizing_packages\sqljocky_test\bin\sqljocky_test.dartとして存在している筈である:

sqljocky_test\pubspec.yaml
name: sqljocky5_test
description: >
  A sample code utilising Pub sqljocky5 library
version: 0.0.1
author:
homepage:
environment:
  sdk: '>=2.0.0-dev <3.0.0'
#dependencies:
dependencies:
  sqljocky5: 2.2.0
  test: ^1.3.0
sqljocky_test\bin\sqljocky_test.dart
import 'package:sqljocky5/sqljocky.dart';
import 'dart:async';

/// Drops the tables if they already exist
Future<void> dropTables(MySqlConnection conn) async {
  print("Dropping tables ...");
  await conn.execute("DROP TABLE IF EXISTS pets, people");
  print("Dropped tables!");
}

Future<void> createTables(MySqlConnection conn) async {
  print("Creating tables ...");
  await conn.execute('CREATE TABLE people (id INTEGER NOT NULL auto_increment, '
      'name VARCHAR(255), '
      'age INTEGER, '
      'PRIMARY KEY (id))');
  await conn.execute('CREATE TABLE pets (id INTEGER NOT NULL auto_increment, '
      'name VARCHAR(255), '
      'species TEXT, '
      'owner_id INTEGER, '
      'PRIMARY KEY (id),'
      'FOREIGN KEY (owner_id) REFERENCES people (id))');
  print("Created table!");
}

Future<void> insertRows(MySqlConnection conn) async {
  print("Inserting rows ...");
  List<Results> r1 =
      await conn.preparedMulti("INSERT INTO people (name, age) VALUES (?, ?)", [
    ["Dave", 15],
    ["John", 16],
    ["Mavis", 93],
  ]);
  print("People table insert ids: " + r1.map((r) => r.insertId).toString());
  List<Results> r2 = await conn.preparedMulti(
      "INSERT INTO pets (name, species, owner_id) VALUES (?, ?, ?)", [
    ["Rover", "Dog", 1],
    ["Daisy", "Cow", 2],
    ["Spot", "Dog", 2]
  ]);
  print("Pet table insert ids: " + r2.map((r) => r.insertId).toString());
  print("Rows inserted!");
}

Future<void> readData(MySqlConnection conn) async {
  Results result =
      await conn.execute('SELECT p.id, p.name, p.age, t.name AS pet, t.species '
          'FROM people p '
          'LEFT JOIN pets t ON t.owner_id = p.id');
  print(result);
  print(result.map((r) => r.byName('name')));
}

main() async {
  var s = ConnectionSettings(
    user: "sqljocky5",
    password: "dart_jaguar",
    host: "localhost",
    port: 3306,
    db:"sqljocky5" // Schema
  );
  // create a connection
  print("Opening connection ...");
  var conn = await MySqlConnection.connect(s);
  print("Opened connection!");
  await dropTables(conn);
  await createTables(conn);
  await insertRows(conn);
  await readData(conn);
  await conn.close();
}

このコードはSQLを理解しているユーザは、このコードを追って行けばDartコードでどのようにテーブルの作成、挿入、変更、検索等を行うかが容易に理解されよう。このパッケージのAPIには必要な情報が記されているので、それも参考にされたい。

これを各自のIDE上に置いて動作させると、次のように表示される:

Opening connection ...
Opened connection!
Dropping tables ...
Dropped tables!
Creating tables ...
Created table!
Inserting rows ...
People table insert ids: (1, 2, 3)
Pet table insert ids: (1, 2, 3)
Rows inserted!
([1, Dave, 15, Rover, Dog], [2, John, 16, Daisy, Cow], [2, John, 16, Spot, Dog], [3, Mavis, 93, null, null])
(Dave, John, John, Mavis)

Process finished with exit code 0

このプログラムを実行したあとはpeoplepetsの二つのテーブルが残される。例えばワークベンチでSELECT * FROM people;というコマンドを実行するとpeopleの内容を知ることができる:







logging

アプリケーションの開発から運用に至る各段階でログをとることが欠かせない。DartにおいてもDartチームは早い段階からこのloggingパッケージを用意してきた。このパッケージはClosure JS Logger及びjava.util.logging.Logger等の言語との互換性をなるべく持たせているので、すでに他の言語でロギングを使った経験がある読者にとっては馴染みやすい。



ログのレベル

loggingにはLebelというクラスが定義されている:

各レベル(キー)には整数が割り当てられている。独自のレベルとその値を設定することも可能であるが、あらかじめ定義されているレベルの使用が推奨されている。どの事象に対しどのレベルを割り振るかはユーザの判断による。

キー

意味

ALL

0

総てのレベルに対しログをとるという特別の意味を持つ

FINEST

300

極詳細なトレース

FINER

400

かなり詳細なトレース

FINE

500

詳細なトレース情報

CONFIG

700

staticな設定メッセージ

INFO

800

情報メッセージ

WARNING

900

潜在的問題

SEVERE

1000

致命的障害

SHOUT

1200

付加的なデバッグ用

OFF

2000

総てのレベルに対しログを取るのを停止するという特別の意味を持つ



ロガーの階層化

各ロガーにはドットで分離した階層化を持たすことができる(従って各ロガーの名前はドットで始まってはいけない)。Loggerにはparentchildlen2つの階層化のプロパティがある:

import 'dart:async';
import 'package:logging/logging.dart';
import 'package:test/test.dart';

main() {
  test('logger naming is hierarchical', () {
    var c = Logger('a.b.c');
    expect(c.name, equals('c'));
    expect(c.parent.name, equals('b'));
    expect(c.parent.parent.name, equals('a'));
    expect(c.parent.parent.parent.name, equals(''));
    expect(c.parent.parent.parent.parent, isNull);
  });
}

このコードはtestというパッケージを使用しているが、このコードを見ればLogger('a.b.c');で作ったcという名前のロガーの親はb、またその親はaという階層になっていることが判る。



Logger初期化

このパッケージをインポートしただけでは、デフォルト状態ではログ・メッセージに関してはなにもしない。従ってログ・メッセージに対するログするレベルを設定し、それに対するハンドラを付加しなければならない。次のコードは総てのログ・メッセージをprintを介してログする単純な設定の例である:

Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((LogRecord rec) {
  print('${rec.level.name}: ${rec.time}: ${rec.message}');
});
  • 最初にLogger.rootをセットしている。Loggerクラスのrootというプロパティはトップ・レベルに置かれるロガーであることを示す。Level.ALLはこのレベルまたはそれ以上のレベルの総てのメッセージはonRecordのストリームに送られる。

  • 次にLogRecordのイベントに対するonRecordのストリームをリスンする。LogRecordクラスmessageerrorlogger name、その他多くのプロパティが存在する。

  • 到来したLogRecordのイベントに対するハンドラは、Dartのトップ・レベルにあるprintメソッドでレベル名:到来時刻:到来メッセージの形式でコンソールに出力している。



メッセージのロギング

ログ・メッセージの到来元が容易に識別できるようなユニークな名前を持ったLoggerを生成する:

final log = Logger('MyClass');

以下はデバッグ・メッセージとエラーをログする一例である。この場合はある非同期処理doSomethingAsync()を監視しログを取っている:

var future = doSomethingAsync().then((result) {
  log.fine('Got the result: $result');
  processResult(result);
}).catchError((e, stackTrace) => log.severe('Oh noes!', e, stackTrace));

もっと複雑なメッセージのログをとるには、該メッセージが実際にログされたときにのみ処理されるようなクロージャを渡すことも可能である:

log.fine(() => [1, 2, 3, 4, 5].map((e) => e * 4).join("-"));



logging_test

前項にあった非同期関数doSomethingAsyncとしてテキスト・ファイルを非同期で読み出す関数を用意してみる。このサンプルは既に読者のIDE上にdart2_code_samples\utilizing_packages\logging_test\bin\logging_test.dartとして存在している筈である:

bin/logger_test.dart
//import 'dart:async';
import 'dart:io';
import 'package:logging/logging.dart';
//import 'package:test/test.dart';

// create logger
final log = Logger('MyClass');
createLogger() {
  Logger.root.level = Level.ALL;
  Logger.root.onRecord.listen((LogRecord rec) {
    print(
        '${rec.level.name}: ${rec.time.toString().substring(11)} : ${rec.message}');
  });
}

// doSomethingAsync
doSomethingAsync() async {
    var file = new File('bin/logging_test.txt');
    return file.readAsStringSync();
}

// main()
main() async {
  createLogger();
  log.fine('started processes');
  var future = doSomethingAsync().then((result) {
    log.fine('Got the result: $result');
  }).catchError((e, stackTrace) => log.severe('Oh noes!', e, stackTrace));
}
bin/logger_rest.txt
Pub 'logging' provides APIs for debugging and error logging.
This library introduces abstractions similar to those used in other languages, such as the Closure JS Logger and java.util.logging.Logger.
Pubspec.yaml
name: logging_test
description: >
  A sample code utilising Pub logging library
version: 0.0.1
author:
homepage:
environment:
  sdk: '>=2.0.0-dev <3.0.0'
#dependencies:
dependencies:
  logging: any
  test: any

ここではdoSomethingAsync()は非同期関数であるasyncが付され、中には非同期メソッドのfile.readAsStringSync()が使われている。これを実行すれば次のようなログ・メッセージが記録される:

FINE: 19:40:01.241961 : started processes
FINE: 19:40:01.274963 : Got the result:  Pub 'logging' provides APIs for debugging and error logging.
 This library introduces abstractions similar to those used in other languages, such as the Closure JS Logger and java.util.logging.Logger.

ここで存在しないファイル名を指定すると

FINE: 19:47:18.575732 : started processes
SEVERE: 19:47:18.601729 : Oh noes!

というログ・メッセージが書き込まれるが、残念ながらエラーの内容とスタック・トレースは残されない。



まとめ

Pubにはこれ以外にも多くの有用なライブラリが登録されている:

  • testDartコードの高度なテスト

  • shelf:サーバ・ミドルエア

  • httpHTTPサーバとクライアント記述の簡単化

  • dart_styleDartコードのフォーマッタ

  • json_annotationJSONコードの作成用ヘルパー

  • google_maps:ブラウザ用のGoogle MapsAPI

  • modern_charts:ブラウザ上でのチャート作成

特に最近はスマホ等のモバイル機器向けのFlutter用のライブラリが急速に増えている。

従って、Dart言語によりアプリの開発に際しては、Pubを検索して活用することが有効であろう。





前のページ

次のページ