あぼぼノート

頭の中空っぽ

Flutterで自作パッケージをpub.devに公開する

基本的にDeveloping packages & pluginsを参考に進めればOK。

パッケージを生成する

テンプレートには二種類ある。今回はDart packagesとして作成するので以下のコマンドで生成。

flutter create --template=package {package_name}

生成されたら、lib/{package_name}.dartを編集する。

サンプルプロジェクトを追加する

--template=packageで生成したプロジェクトにはtestしか含まれてないので、必要に応じてサンプルプロジェクトを追加する。

パッケージのカレントディレクトリで以下のコマンドでexampleというプロジェクトを生成する。

# in {package_name}
flutter create example

生成されたら、example/pubspec.yamlのdependenciesに自作パッケージを追加。

dependencies:
  flutter:
    sdk: flutter

  {package_name}:
    path: ../

その後example/main.dartに自作パッケージのサンプル実装を記述する。

README.md, CHANGELOG.md , pubspec.yaml, LICENSEの編集

pub.devに公開するにあたってこれらのファイルを正しく編集する。

基本的にそれぞれデフォルトで例が書かれてあるので参考に。LICENSEは他のパッケージを参考に。

CHANGELOG.md

## [0.0.1] - 2019-11-04

* first release

pub.devに公開

以下のコマンドでpub.devにpublishできる。

flutter packages pub publish

途中認証のためにURLをひらく必要がある。認証に成功すればこんな感じ。

f:id:aboy_perry:20191104165801p:plain
pub.dev認証完了

認証完了後、pub.devにアップロードが成功すると晴れて自作パッケージが公開される。

pub.dev

Flutterで1文字目だけスタイルを変える

Text.richTextSpanを使う。TextSpanで設定したstyleはchildrenに引き継がれるので、childrenのTextSpanで上書きする。

final String text = "Flutter";
Text.rich(
    TextSpan(
        text: text.substring(0, 1),
        style: TextStyle(
            fontSize: 36,
            color: Colors.blue,
            letterSpacing: 1,
        ),
        children: <TextSpan>[
            TextSpan(
                text: text.substring(1),
                style: TextStyle(
                    fontSize: 18,
                    color: Colors.black87,
                ),
            ),
        ],
    ),
),

f:id:aboy_perry:20191103183918p:plain:w300
Text.richを使って1文字目のTextStyleを変える

Flutter for Webで404ページを表示させる

MaterialAppのonGenerateRouteプロパティを使うとURL直打ちでも指定のページを表示させられる。同様に未定義のURLが指定された場合に呼ばれるonUnknownRouteを使えばOK。

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,
      ),
      onGenerateRoute: (settings) {
        switch (settings.name) {
          case '/':
            return MaterialPageRoute(
              builder: (context) => MyHomePage(),
            );
        }
      },
      initialRoute: '/',
      onUnknownRoute: (RouteSettings settings) {
        return MaterialPageRoute(
          builder: (context) => MyNotFoundPage(),
        );
      },
    );
  }
}

ルーティングの処理順序はこのへんを参照。

# flutter/lib/src/material/app.dart#L57
/// The [MaterialApp] configures the top-level [Navigator] to search for routes
/// in the following order:
///
///  1. For the `/` route, the [home] property, if non-null, is used.
///
///  2. Otherwise, the [routes] table is used, if it has an entry for the route.
///
///  3. Otherwise, [onGenerateRoute] is called, if provided. It should return a
///     non-null value for any _valid_ route not handled by [home] and [routes].
///
///  4. Finally if all else fails [onUnknownRoute] is called.

FlutterでAppBarを透過する

AppBarのbackgroundColorを透明にしただけだとAppBarは透明にならない。

ScaffoldのextendBodyBehindAppBarプロパティにtrueを設定すればOK。

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        backgroundColor: Colors.white.withOpacity(0.5),
      ),
      extendBodyBehindAppBar: true,
      body: ListView.builder(
        padding: const EdgeInsets.all(24),
        itemCount: 1,
        itemBuilder: (BuildContext context, int index) {
          return Container(
            height: 1000,
            color: Colors.blue,
          );
        },
      ),
    );
  }
}

f:id:aboy_perry:20191027011036p:plainf:id:aboy_perry:20191027005311p:plain
左:extendBodyBehindAppBar: false 右:extendBodyBehindAppBar: true

ちなみに、scaffoldの仕様で、extendBodyBehindAppBarがtrueかfalseかでpaddingのtopが変わる。

f:id:aboy_perry:20191027005430p:plainf:id:aboy_perry:20191027005311p:plain
左:extendBodyBehindAppBar: false 右:extendBodyBehindAppBar: true

// flutter/lib/src/material/scaffold.dart#L366
final double top = extendBodyBehindAppBar
          ? math.max(metrics.padding.top, bodyConstraints.appBarHeight)
          : metrics.padding.top;

https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/scaffold.dart#L366

Flutter for Webで突然main.ddc.dill作成エラーが起きてビルドできなくなった

現象

flutter run -d chrome 実行時、見たことのない怖いエラーが突然発生するように...。

Error creating <project_name>|lib/main.ddc.dill Error creating kernel summary for module:<project_name>|lib/main.ddc.dill

解決

PCを再起動したあと普通に flutter run -d chrome を実行すると、以下の情報を吐きながらビルドに成功した。

$ flutter run -d chrome
 
Launching lib/main.dart on Chrome in debug mode...
Throwing away cached asset graph because the build phases have changed. This most commonly
would happen as a result of adding a new dependency or updating your dependencies.
Building application for the web...

坂道グループの中からもうすぐ誕生日を迎えるメンバーを教えてくれるLINE Clovaスキルをつくった

『さかたん』というLINE Clovaスキルをだいぶ前に公開しました。

clova.line.me

さかたんは、乃木坂46欅坂46、日向坂46のメンバーの中で、次に誕生日を迎えるメンバーを教えてくれます。

※noteに書いた記事の移植 坂道グループの中からもうすぐ誕生日を迎えるメンバーを教えてくれるLINE Clovaスキルをつくった|あぼぼ|note

使い方

😲「ねぇClova、さかたんを起動して」
🤖「グループ名か、坂道と言ってください」
😲「乃木坂」
🤖「8月20日に、乃木坂の秋元真夏さんが26歳、乃木坂の白石麻衣さんが27歳を迎えます。」

グループ名は「坂」を省略することが可能です。グループを絞らない場合は「全部」とか「坂道」と言ってください。該当するメンバーが複数いる場合は↑のように全員喋ります。

あとは、当日なら

🤖「本日8月10日に、乃木坂の齋藤飛鳥さんが21歳になりました。」

と喋ります。

つくった理由

・特定のメンバーの誕生日ならググればすぐ知れるけど、つぎ誰が何歳になるかはすぐにはわからないなーと思ったから ・Clovaスキル(と乃木坂46が昔コラボしてたので)つくりたかった

技術について

言語はSwiftを使いました。LINEがSwift用のSDKを用意してくれてるのと、ぼくが一番書き慣れている言語なので。Swift以外にNode.jsとかPythonとかKotlinとかgoもありますよ👶

github.com

あとは動かすためにDockerとHerokuを使いました。SDKに組み込まれてるので、楽チン!Herokuのssl対応は別途必要です。

Swiftは型名や変数名に日本語が使えます。

enum 坂道: String {
    case 乃木坂 = "のぎざか"
    case 欅坂 = "けやきざか"
    case 日向坂 = "ひなたざか"
}

struct Member {
    let name: String
    let kana: String
    let birthday: Date
    let group: 坂道
}

今回一番時間がかかったところは、メンバーのデータを作るところですかね。欅坂46のサイトはコピペできないようになってて大変でした🙄

// MARK: - 乃木坂46
let nogizakaMembers: [Member] = [
    // MARK: 1期生
    Member("秋元 真夏", "あきもと まなつ", "1993年8月20日", .乃木坂),
    Member("生田 絵梨花", "いくた えりか", "1997年1月22日", .乃木坂),
    Member("井上 小百合", "いのうえ さゆり", "1994年12月14日", .乃木坂),
    Member("齋藤 飛鳥", "さいとう あすか", "1998年8月10日", .乃木坂),
    ...
]

ほかには、ローカルとheroku上とでタイムゾーンが違うことによるDateFormatterの扱いとかは少し悩みました。

そしてClovaの発話のための処理と、汎用的な坂道グループのデータを分離したので、今後何かしらに応用できるかもしれないし、できないかもしれません。

Clovaスキルの審査について

LINE Clovaスキルストアに自作スキルを公開するには、LINEによる審査に合格する必要があります。さかたんは1回落ちました。

指摘箇所は6箇所で、パッと見結構多いな・・・と思ったんですが、指摘理由が明確で、且つ修正例も載せてくれたりと、どう直せば良いか分かりやすくて助かりました。

審査のスピード感ですが、さかたんの場合、初回の審査結果は申請してから38時間後、2回目は40時間後に返ってきました。つまりそれぞれ2営業日内で審査していただきました。

Clova Developer Centerでスキルごとに統計も見れます。今回の審査員はClova DeskとClova Friends miniの2台でテストしてた、というのがログ見ると分かったりします。

今後の懸念

メンバーが卒業するたびに泣きながらメンテする必要があり、メンタルがもつか心配です。

メンバーの更新だけならLINEの再審査なしで行えるので

『10歳でもわかる問題解決の授業』を読んで

解決策に急がない、というのは大事だな〜。

問題解決の意識を変えること

  • 問題解決を一発ですますことは不可能であるということを理解する
  • 問題解決はサイクルである。一度意思決定してそれで終わりではない
  • テストは複数の選択肢があれば、その中に必ず「正解」がある。実際の問題には、いくら案があろうがその中に"最高の"効果をあげるものがあるという確信は誰も持てない。「この選択肢はこういう効果があった」という実験結果に価値がある

現象ではなく論点を考える

  • 現象は表立って目に見えている問題、論点は問題解決に繋がる打ち手を導くもの
  • 「算数の成績が良くない」は現象、「計算ミスが多い」「割合の理解不足による間違いが多い」「一度解いたことのある問題が定着していない」は論点
  • 「解決策」に急ぐのではなく、「問題設定」を疑ってみること。正しい問題=論点が設定されていないと効果的な打ち手に繋がらない

感想

決定することが苦手だけど、繰り返すことを前提に意思決定を怖がらないようにしていきたい。それから筋のいい仮説をたてたり、検証結果を正しく読み解くために、「解決」を急がないようにしたい。