every Tech Blog

株式会社エブリーのTech Blogです。

TSKaigi 2024 に参加してきました!

TSKaigi 2024 に参加してきました!

はじめに

Dev Enableチームの羽馬( NaokiHaba) と 庄司(ktanonymous )です。

2024年5月11日(水)に開催されたTSKaigi 2024に参加してきましたので、イベントの様子や印象に残ったセッションをいくつかご紹介します。

各セッションのアーカイブも公開予定とのことですので、ぜひ公式サイト・YouTubeチャンネルなどをチェックしてみてください。

tskaigi.org

www.youtube.com

イベントの様子

TSKaigi 2024は、今年から開催された新しいイベントです。TypeScriptを中心にしたカンファレンスで、TypeScriptの最新情報や活用事例などが紹介されました。

会場には、国内外から多くのエンジニアが集まり、盛況のうちに開催されました。会場内では、様々なブースが設けられ、最新のツールやサービスの紹介が行われていました。

特に印象に残ったブースは、アセンド株式会社 様のブースで開催された 「TypeScriptコンパイルチャレンジ」です。

このコンパイルチャレンジは、以下のような内容でした。

  1. 参加者は、青色の「型カード」を引く
  2. 机に並べられた赤色の「値カード」から一つをめくる
  3. 引いた型カードと値カードを使って、TypeScriptのコンパイルにチャレンジ
  4. コンパイルに成功すると、景品が贈呈される(高難易度コンパイルに成功した方には、HHKBなどの豪華景品も)

弊社メンバーも、あと一歩のところで当選を逃してしまいましたが、楽しい体験ができました。

参加レポート

Keynote: What's New in TypeScript

発表者: Daniel Rosenwasser さん(https://twitter.com/drosenwasser) レポート: 庄司

Microsoft / TypeScript Principal Product Manager の Daniel Rosenwasser さんによる Keynote では、TypeScript の最新情報が紹介されました。

TypeScript 5.4 および TypeScript 5.5 Beta の新機能について、 ライブコーディングを交えながら、各機能の使い方や利点が丁寧に解説されていました。

主な新機能は以下の通りです:

  • TypeScript 5.4
    • The NoInfer Utility Type
    • Preserved Narrowing in Closures Following Last Assignments
  • TypeScript 5.5 Beta
    • Type Imports in JSDoc
    • Regular Expression Syntax Checking
    • Inferred Type Predicates
    • Isolated Declarations

特に NoInfer は型推論を制御する上で強力な機能だと感じました。 型の絞り込み (Narrowing) の改善や JSDoc での型インポートのサポートなど、日々の開発で嬉しい機能が多数含まれていました。 Regular Expression の構文チェックは地味ながら実用的な機能追加だと思います。 TypeScript は着実に進化を続けており、次のバージョンが今から楽しみです。

TypeScript の抽象構文木を用いた、数百を超える API の大規模リファクタリング戦略

発表者: やなえもん さん(https://twitter.com/yanaemon169) レポート: 庄司

speakerdeck.com

こちらのセッションでは、数百のAPIを抱える Express コードを、AST(抽象構文木, Abstract Syntax Tree) を利用して Nest.js コードに大規模移行するという取り組みが紹介されました。

コードのリプレイスと言えば、正規表現を利用したスクリプトによる変換やIDEによる一括置換などが一般的ですが、こちらは AST を利用しているという点が新鮮でした。

AST を利用することで、微妙な表記揺れなどを気にせず、コードの構造に則したリプレイスが可能になるという話には説得力がありました。

TypeScriptのコンパイラでもASTが利用されているように、ASTとTypeScriptの相性の良さを感じました。また、近年の生成AI技術の発展によって、ASTの取り扱いもより容易になるのではないかと期待が持てます。

一方で、レビュアーの負担が大きいという課題にも触れられていましたが、全体としてとてもチャレンジングで興味深い取り組みだと感じました。

AST については、HireRoo さん(https://twitter.com/hirerooinc) が発表された TypeScript ASTを利用したコードジェネレーターの実装入門 でも詳しく解説されていましたので、興味のある方はそちらもチェックしてみてください。

TypeScriptから始めるVR生活

発表者: TamaG さん(https://twitter.com/TAMAGOKAKE_G_) レポート:羽馬

speakerdeck.com

Resonite上でビジュアルプログラミング言語「ProtoFlux」を使った開発の様子が紹介されました。

ProtoFluxは「ノード」と「ノード」をつなぐことでプログラミングができる言語ですが、バージョン管理や関数化ができないという問題点がありました。

これらの問題を解決すべく生まれたのが「MirageX」です。MirageXは、TypeScriptとReactを使ってResoniteの開発ができるフレームワークです。

コードベースの開発になるため、バージョン管理やAIの力を借りることができるようになりました。また、ライブラリを使うこともできるため、本格的なシューティングゲームなども作成可能です。

VRプラットフォームでのTypeScriptを使った開発事例が紹介され、その可能性と課題について理解を深めることができました。VRという新しい領域でのTypeScriptの活用法について学べる貴重な機会でした。

興味を持った方は、ぜひ MirageXのGitHubリポジトリをチェックしてみてください!

サービス開発におけるVue3とTypeScriptの親和性について

発表者: からころ / karacoro さん(https://twitter.com/karan_corons) レポート:羽馬

speakerdeck.com

Vue3ではComposition APIの登場により、コンポーネントのロジックを外部ファイルに切り出しやすくなり、型付けも改善されました。

また、コンポーネントランタイムの型付け強化により、Props、Emit、Provide/Injectなどでも型の恩恵を受けられるようになっています。

さらに、Volar.jsとvuejs/language-toolsの貢献により、テンプレートへの型の反映などエディタ連携の問題も解決されました。

講演の丁寧な解説と豊富なコード例は、Vue3とTypeScriptを活用したサービス開発のベストプラクティスを学ぶ上で非常に参考になる内容だと感じました。

まとめ

TSKaigi 2024は、TypeScriptを中心にしたカンファレンスとして、多くのエンジニアにとって有益な情報が得られるイベントでした。

TypeScriptの最新情報や活用事例を学ぶことができ、新しい技術やアイデアに触れることができました。

今後も、TypeScriptコミュニティの発展と、エンジニアのスキルアップに貢献するイベントとして、TSKaigiが続けられていくことを期待しています。

また、今回の参加レポートが、TypeScriptを学びたい方や、TypeScriptを活用したい方の参考になれば幸いです。

最後に

エブリーでは、ともに働く仲間を募集しています。

テックブログを読んで少しでもエブリーに興味を持っていただけた方は、ぜひ一度カジュアル面談にお越しください!

corp.every.tv

最後までお読みいただき、ありがとうございました!

fluentd/fluent-bitでTreasure Dataにログを送信するときにハマった話

はじめに

DelishKitchenヘルシカでインフラをやったりバックエンドをやったりしているyoshikenです。

今回は、Treasure Dataにログを送信しようとfluentdとfluent-bitを使っていたときにハマった話を書きます。

fluentdからfluent-bitへ

もともと弊社では歴史的背景でfluentdを使っていました。が、

  1. 大々的なlogの加工が必要なものはTreasure Dataなど別サービスで行う
  2. リソースの消費がやや気になる
  3. FireLensはじめ、Fargateでfluentbitのほうが相性が良い

などの理由より、新規サービスはすべてfluent-bitを使用することになりました。

移行から数ヶ月~数年経っていますが、flunetdに比べ軽量であるため期待した通りのパフォーマンスを発揮してくれています。

fluent-bitでTreasure Dataにログを送信できない現象

ヘルシカではアクセスログをTreasure Dataに送信する要件がありましたので、fluent-bitでTreasure Dataにログを送信する設定を行いました。

confを公式ドキュメント通りに記述しましたが、ログが送信されず、logを漁ってみると以下のようなエラーが出ていました。

[202x/xx/xx xx:xx:xx] [ warn] [output:td:td.0] HTTP status 404
{"status_code":404,"message":"Resource not found","severity":"error","error":"Resource not found","text":"Resource not found"}

同じような設定でflunetdを動かしてみると問題なく送信/挿入できたので、fluentbit固有の問題と考えdebugしていきます。

fluent-bitとfluentdではTreasure Dataプラグインの挙動が違う件

結論からいうと、fluent-bitのTreasure Dataプラグインはfluentdの同名のプラグインと挙動が微妙に異なります。

fluentdではテーブルが存在しない場合、正確に記すと「upload時に 404 not found httpステータスコードが帰ってきた場合」はテーブルを作成する処理を行います

https://github.com/treasure-data/fluent-plugin-td/blob/master/lib/fluent/plugin/out_tdlog.rb#L209-L224

      begin
        begin
          @client.import(database, table, UPLOAD_EXT, io, size, unique_str)
        rescue TreasureData::NotFoundError
          unless @auto_create_table
            raise
          end
          ensure_database_and_table(database, table)
          io.pos = 0
          retry
        end

対してfluent-bitでは、テーブルが存在しない場合でも特に追加処理などせずにそのままエラーを返却する形になっています

https://github.com/fluent/fluent-bit/blob/master/plugins/out_td/td.c#L188-L207

/* Validate HTTP status */
    if (ret == 0) {
        /* We expect a HTTP 200 OK */
        if (c->resp.status != 200) {
            if (c->resp.payload_size > 0) {
                flb_plg_warn(ctx->ins, "HTTP status %i\n%s",
                             c->resp.status, c->resp.payload);
            }
            else {
                flb_plg_warn(ctx->ins, "HTTP status %i", c->resp.status);
            }
            goto retry;
        }
        else {
            flb_plg_info(ctx->ins, "HTTP status 200 OK");
        }
    }
    else {
        flb_plg_error(ctx->ins, "http_do=%i", ret);
        goto retry;
    }

理由ついてはissueなどを漁ってみましたが、特に言及はなかったです。 一応歴史的にはfluentdも昔はflunet-bit同様にエラーをそのままエラーで返していたましたが、途中でリトライ処理が追加された形になります。

Prevent retrying unretriable errors by cyberdelia · Pull Request #35 · treasure-data/fluent-plugin-td

まとめ

まとめると以下の表になります。

fluentd fluent-bit
テーブルが存在する 送信可能 送信可能
テーブルが存在しない 自動生成 404 not found

弊チームではデータチームと話し合い、"エラーが出続けるのは健全ではない"・"fluentdと同じ仕様と勘違いし、Treasure Data側のテーブル作成を忘れてしまう"などの懸念が生じ、即座のリアルタイム性が必要なログではないため、"一度S3にoutput。その後、Treasure Dataのbatch importで挿入する。"という形で対応することとなりました。

同じようなプラグインでも挙動が異なるというレアケースを引いてしまったため、後世に同じような人がハマらないように記事に残しておきます。

エブリーは Go Conference 2024 にプラチナGoルドスポンサーとして協賛いたします

はじめに

Dev Enableチームの羽馬(@NaokiHaba)です。

この度、エブリーは2024年6月8日(土)に開催される『Go Conference 2024』に、プラチナGoルドスポンサーとして協賛することになりました!

gocon.jp

エブリーでは、Go言語を積極的に採用し、様々なプロジェクトでその力を発揮しています。今回の協賛を通して、さらなるGo言語コミュニティの発展に貢献できればと考えております。

今年のGo Conference 2024のテーマは「一期一会」です。Go言語に関する情報交換や交流を通じて、新たな出会いや気づきを得ることができるでしょう。

ぜひ、タイムテーブルをご覧いただき、気になるセッションに参加してみてください。

https://sessionize.com/api/v2/7zlcfd7c/view/GridSmart

弊社も、17時50分からのスポンサーセッションでGo言語を活用したプロダクトやサービスの開発事例をご紹介いたします。ぜひご期待ください!

また、私たちのブースでは、Go言語の最新技術情報や活用事例をご紹介する予定です。エブリーのエンジニアが直接皆様からのご質問にお答えしますので、ぜひお立ち寄りください。

エブリーにおけるGo言語の活用

ここでは、これまでのエブリーのテックブログで公開してきたGo言語関連の記事をいくつかご紹介します。

Go testにおける可読性を保つ方法を考える

tech.every.tv

テストコードの複雑化や保守性の低下といった問題に直面した際に、テストコードの可読性を維持するための方法について紹介しています。

ネットスーパーアプリ GraphQL から REST へ移行始めました

tech.every.tv

ネットスーパーアプリでGraphQLからRESTへ移行した経緯と、その過程で得られた知見について紹介しています。

PythonからGoへのリプレイスを行っている事例として参考になる内容となっています。

sqlboilerとoapi-codegenの活用事例

tech.every.tv

APIサーバー開発にsqlboilerとoapi-codegenを導入した事例を紹介しています。

これらのツールを活用することで開発の生産性を向上させた経験について説明しています。

WebSocket APIを用いたリアルタイム通知の実装

tech.every.tv

Next.jsとGoを組み合わせ、AWS API GatewayのWebSocket APIを用いてAPIサーバーからフロントエンドにリアルタイム通知を送る方法について解説しています。

WebSocketを用いたリアルタイム性の高いアプリケーション開発の参考になる内容です。

その他にも、Go言語を活用したプロダクトやサービスの開発に関する情報を今後も随時公開していきますので、ぜひご確認ください。

tech.every.tv

皆様とお会いできることを楽しみにしています!

Go Conference 2024では、当社がどのようにGo言語を活用しているのか、具体的な事例を交えながらご紹介できることを楽しみにしています。また、皆様と直接お話しできる機会を大切にしたいと思っておりますので、ご質問やご意見があればお気軽にお寄せください。

みなさまとお会いできることを心より楽しみにしております。6月8日、Go Conference 2024でお会いしましょう!

最後に

エブリーでは、ともに働く仲間を募集しています。

テックブログを読んで少しでもエブリーに興味を持っていただけた方は、ぜひ一度カジュアル面談にお越しください!

corp.every.tv

エブリー初のエンジニア新卒研修を開催しました!

はじめに

こんにちは。DELISH KITCHEN 開発部 SERS グループ兼、CTO 室 DevEnable グループ所属の池です。

SERS グループでは主に小売向けプロダクトの開発を行なっており、DevEnable グループでは社内開発組織活性化に向けた活動を行なっています。

DevEnable グループについては以下の記事で紹介しているので、よければご参照ください。

tech.every.tv

本記事では今年度から初開催となるエンジニア新卒研修の取り組みについてご紹介します!

エンジニア新卒研修を開催するに至った背景

エブリーでは、新卒社員全員を対象に内定者研修からはじまり、定期的な研修を行い、事業理解や業務におけるスキル獲得など早期の成長をサポートしています。

昨年までの研修ではエンジニアリングに特化した研修は行っておらず、スキル指導は配属後の OJT に依る部分が多いようなオンボーディング体制となっていました。これにより、配属後の実務において以下のような課題が生じていました。

  • マインドセット
    • エブリーのエンジニアとして働く上で期待されるマインド・スタンスがわからない
  • 領域外の自分ごと化
    • 全体像を把握できずに専門領域外のことを自分ごと化できない
  • 実務における前提知識の学習
    • インフラを体系的に学ぶ機会がない
    • テストやアーキテクチャに関して馴染みがない

そこで、今年度からエンジニア領域におけるオンボーディング体制を強化し、エンジニアとしての早期成長をサポートすべく、エンジニアを対象とした新卒研修を開催することにしました。

エンジニア新卒研修の目的と方針

目的

エンジニア新卒研修の主な目的は次の通りです。

『オーナシップを持ってプロダクト課題の解決に動けるエンジニアへの土台を作る』

上述した課題感を解消するとともに、この目的を達成するための施策を検討しました。

方針

目的に基づき、研修中と研修後に分けて次のような方針を決めました。

  • 研修中
    • エブリーのエンジニアとして求められるマインドを理解する
    • 専門領域を超えてエブリー全体で使われている技術スタックを理解する
  • 研修後
    • 内部のオリジナルコンテンツによる研修での支援が難しい領域については、配属後のスキル支援環境を提供する

エンジニア新卒研修のカリキュラム

DevEnable グループと開発部の役員・部長・マネージャーを中心にカリキュラムを策定しました。

今年からの取り組みであるため、コンテンツも役員・部長・マネージャーを中心にゼロから全て作成したものとなっています。

全体で実施期間 5 日です。

  • マインド研修
    • CTO からのメッセージ(1 時間)
    • インシデントへの向き合い方(30 分)
  • 技術スタックの把握(講義 + ハンズオン)
    • バックエンド/インフラ(1.5 日)
    • モバイル(1 日)
    • Web(1 日)
    • データ(1 日)
  • ランチ会
  • (研修後) AWS JumpStart 2024 for NewGrads

ここからは各講義について概要を説明します。

CTO からのメッセージ

この講義では、CTO が今までのキャリアを通じて大切にしているマインドセットを中心講義しました。

CTO の成功談や失敗談、どういう行動が評価されてきたかなど、エブリーの CTO ならではの経験談がふんだんに盛り込まれた内容となっており、配属に向けて大きな刺激となる講義でした。

CTO講義の様子

インシデントへの向き合い方

以前から、インシデント対応は新卒社員にとって精神的なハードルが高く、入っても何をやっていいかわからず、主体的に取り組みにくいという声が多く上がっていました。 また、新卒社員に限らず会社全体としても同様の課題感を持っていたということもあり、インシデント対応におけるマインド理解を研修の題材として選定しました。

この講義は、そのような課題を解消すべく、エブリー開発部におけるインシデントに対する向き合い方・マインドを教える講義です。 内容は次の通りで、インシデントにおける行動指針を学べる内容となっています。

  • インシデントが起きたらまずどうすればよいか
    • 関係ありそうな人間を巻き込む
  • インシデントが起こってそう
    • 野次馬でも参加しましょう
  • 別の部署でインシデントが発生している
    • 関係なくても参加しましょう
  • 小さくてもインシデントはインシデント
    • インシデントかどうかは上長が判定するのでとりあえず報告しましょう(割れ窓理論
  • インシデントは終わってからも大事
    • ポストモーテム

バックエンド/インフラ

バックエンド/インフラ講義では、次のような目標を設定しました。

  • エブリーで共通的に用いられる技術や知識について、一通り触れて理解する
  • 自分たちが開発するシステムが具体的にどのような環境で動いているかを理解する
  • なぜ今の構成になっているかを理解する
  • パフォーマンス観点で取り組み方について理解する

ハンズオンを通して上記目標の内容を理解できる形式となっています。

ハンズオンはいくつかのパートに分かれています。

  • Go を利用した簡易的な API サーバをもとにシステム開発を体験
    • API を操作・改修
    • テストコード実装
    • パフォーマンス改善
  • デプロイ
    • 手動でプログラムを AWS 上のサーバに配置し、インターネット上に公開された状態を構築する手動デプロイ
    • ECS を用いた半手動デプロイ
    • terraform や CI/CD を用いた自動デプロイ

モバイル

モバイル講義は座学とハンズオンを通して学べる形式となっており、次のような内容を行いました。

  • 環境構築およびパッケージ構成、マルチターゲットの説明
  • 画面遷移
    • UIKit, SwiftUI
    • ViewController, ViewModel, View を作成し、画面遷移できるようにする
  • API 接続
    • Network と Model、非同期処理
    • API に接続して情報を取得し、Model 変換を行う
  • View 作成
    • Figma を参考に View を作成。ViewModel と接続し、Model を View に反映
  • 分析
    • アプリログ収集、データフロー、ログ設計、Crashlytics

モバイル講義の様子

Web

Web 講義では、次のような目標を設定しました。

『フレームワークによらない web の基礎知識を理解し、今後の web 開発のベースにする』

  • (座学)web 開発でベースとなる知識を身につける
  • (座学)web 開発で意識するポイントを理解する
  • (ハンズオン)実際の web 開発のイメージをつける

以下のような講義内容となっています。

  • エブリーでの web 開発
  • エブリーの web 開発で利用される技術スタック
  • Web 開発の歴史
  • Web 開発で知っておきたい基礎知識
  • Web 開発で意識されるポイント
  • ハンズオン:仮想の簡易的な DELISH KITCHEN アプリを用いて、デザインをもとに画面を作成

Web講義の様子

データ

データ講義では、データ領域の各分野毎に講義を行うようなカリキュラムとなっています。

  • データエンジニア
    • データエンジニアとは
    • エブリーで扱うデータ
    • 一般のデータベースとの違い
  • データサイエンティスト
    • [業務理解]エブリーのデータサイエンティストが何をやっているか知る
    • [協業の視点]エンジニアリングとデータサイエンスの違いを知る
  • データストラテジスト
    • データストラテジストとは
    • データストラテジストの具体的な業務内容
  • ハンズオン
    • Databricks
    • SQL

ランチ会

研修期間中は、以下の目的のもと、毎日各領域ごとのエンジニア社員とのランチ会を行いました。

  • 人的ネットワークの構築
  • 実務イメージを深める

各領域の社員とのランチ会を行うことで、全ての領域において気軽に話せるようなネットワークを作ることができました。

受講者の声

今後のエンジニア新卒研修の改善に向けて、受講後のアンケートを通じて、受講者からのフィードバックを収集しました。

全体の満足度に関する質問項目では、受講者全員から最も高い評価を得ることができました。

続いてポジティブな声の一例を紹介します。

  • マインドセット
    • 新卒でもインシデントを発見した場合は報告する
    • 自分が苦手だと思う部分があったので、配属してから実際その技術を使用するまでに、しっかりと自習しておく
  • 領域外の自分ごと化
    • クライアントチームやデータチームが何をしているか理解できたことはかなり良かった。今後一緒に仕事するときに相手のことを考えながら業務に取り組めるので、よりスムーズに業務が進められると思う。
  • 実務に必要な前提知識の学習
    • tfstate の知識などが早速タスクで役に立った
    • 利用している技術・ツールについての全体像が掴めた

研修の目的としていた課題感の解消に関連するようなコメントがあり、大枠の目的は達成できたと思っています。

しかし、一方で次のようなネガティブな声もあり、改善点も見つかりました。

  • 最低限必要な知識などを事前に共有することで、もう一段階踏み込んだ講義内容になると感じた。
  • 研修で使用する各種ツールの使い方についての研修か資料があると取り組みやすいと思いました。

今回の研修は、受講者の専門領域外を含めて全ての領域を学ぶような研修だったこともあり、初めて扱う技術やツールが多く出てきます。 また、運営側で受講者の前提知識の基準を高く設定していた部分もあったため、講義に必要な前提知識を持っていないと理解が困難な内容が一部ありました。

次回開催時にはアンケート結果を踏まえてより新卒社員にとって学びの多い研修になるように改善していく予定です。

おわりに

本記事では初開催となったエンジニア新卒研修について紹介しました。

初めての取り組みで改善すべき点も多くありましたが、全体的には開催して意義のある取り組みだったと思います。 プロダクト開発全体に関する解像度を高める機会になったことや、新卒自身が技術スキルにおける課題を把握できたこと、ランチ会で各領域のエンジニアとの接点を構築できたことなど、配属に向けたエンジニアとしての早期成長の土台作りに繋がったと感じています。

エンジニア新卒研修を含め、今後もスキルアップのためのり組みや体制を整えていく予定です。

他の取り組みを開催した際には同様にレポートをお届けできればと思うので、ご期待ください。

簡単に高品質なドキュメントスキャナーが実装できる、ML Kit Document Scanner API がリリースされたので使用してみた。

はじめに

トモニテでAndroid開発を担当している岡田です。

先日、ML Kit Document Scanner API のベータ版がリリースされました。
公式ドキュメントやサンプルアプリを参考に、今回はAndroidでの実装方法・内容をご紹介したいと思います。
以下に参考にしたサイトのリンクを示します。是非覗いてみてください。

android-developers.googleblog.com

developers.google.com

github.com

ML Kit とは

Googleの機械学習の機能を、Android/iOSアプリとして提供するモバイルSDKです。
例えば顔検出やバーコードスキャンなどの機能を簡単に実装することができます。

ML Kit Document Scanner API とは

紙の資料をカメラでスキャンして、デジタル資料として読み込むことができる、ドキュメントスキャナーSDKのAPIです。
用意されたスキャナーは自動キャプチャ・切り抜き・自動回転検出機能だけでなく、フィルター機能など編集もできます。
つまり、すごいリッチなスキャナーを簡単に実装できるということです。
スキャナーの使用感については公式のサンプルアプリでご確認ください。

実装の簡単な流れは以下の通りです。

  1. スキャナーのオプションを決める
  2. スキャナーを呼び出す
  3. スキャナーから結果を取得する

主なクラスについて

主に以下の3つのクラスで構成されています。

  • GmsDocumentScannerOptions
  • GmsDocumentScanning
  • GmsDocumentScanningResult

それぞれ名前の通り、スキャナーのオプション、スキャン開始、スキャン結果に関するクラスになっています。
これらについて、実際に確認してみたいと思います。

GmsDocumentScannerOptions (オプション)

GmsDocumentScannerOptions でスキャナーに関してのオプションを設定できます。
公式ドキュメントのコードでは、以下のように紹介されています。

val options = GmsDocumentScannerOptions.Builder()
    .setGalleryImportAllowed(false) // フォトギャラリーからのインポートの可否
    .setPageLimit(2)                // 最大ページ枚
    .setResultFormats(RESULT_FORMAT_JPEG, RESULT_FORMAT_PDF)    // 結果のフォーマット
    .setScannerMode(SCANNER_MODE_FULL)  // スキャナのモード
    .build()

設定できるオプションは以下の通りです。

  • スキャナーのモード
  • 最大ページ枚
  • 結果のフォーマット
  • フォトギャラリーからのインポートの可否

それぞれ見ていきます。

スキャナーのモード

setScannerMode() を用いて、スキャナーの設定ができます。 現段階では3種類のモードが要されています。

public static final int SCANNER_MODE_BASE = 3;
public static final int SCANNER_MODE_BASE_WITH_FILTER = 2;
public static final int SCANNER_MODE_FULL = 1;

SCANNER_MODE_BASE
基本的な編集機能(ページの切り抜き、回転、並べ替えなど)が使用できます。

SCANNER_MODE_BASEのプレビュー画像

SCANNER_MODE_BASE_WITH_FILTER
SCANNER_MODE_BASEモードに画像フィルタ(グレースケール、自動画像補正など)が追加されます。

SCANNER_MODE_BASE_WITH_FILTERのプレビュー画像

SCANNER_MODE_FULL(デフォルト)
SCANNER_MODE_BASE_WITH_FILTERモードの機能に加えて画像クリーニング機能(汚れや指の消去など)が追加されます。

SCANNER_MODE_FULLのプレビュー画像

最大ページ数

setPageLimit() を用いて、最大のページ数を指定できます。int型で指定します。
以下は最大ページ数を2とした場合のスクリーンショットです。最大数に達すると、ページを追加する "+" アイコンが出ないことがわかると思います。

最大ページ数のプレビュー画像

フォトギャラリーからのインポートの可否

フォトギャラリーからのインポートの可否を設定できます。
setGalleryImportAllowed() を用い、Booleanで指定します。

結果のフォーマット

setResultFormats() を用いて、出力結果のフォーマットを指定できます。
現段階では、JPEGかPDF、またはその両方を選択できるようです。

public static final int RESULT_FORMAT_JPEG = 101;
public static final int RESULT_FORMAT_PDF = 102;

GmsDocumentScanning (スキャナーの開始)

スキャナーはGmsDocumentScanningを用いて、以下のように記述できます。
コードは公式のドキュメントとサンプルを参考にしました。

GmsDocumentScanning.getClient(options)  // optionsは先ほど紹介したGmsDocumentScannerOptions
  .getStartScanIntent(activity)
  .addOnSuccessListener { intentSender ->
     scannerLauncher.launch(IntentSenderRequest.Builder(intentSender).build())
   }
  .addOnFailureListener {
    // 失敗した際の処理
  }

メソッドチェーンでわかりにくいので、順を追って説明します。

はじめに、GmsDocumentScanninggetClient()を呼び出します。 getClient()GmsDocumentScannerOptionsを引数にとり、GmsDocumentScannerを返します。

public final class GmsDocumentScanning {
    @androidx.annotation.NonNull
    public static com.google.mlkit.vision.documentscanner.GmsDocumentScanner getClient(@androidx.annotation.NonNull com.google.mlkit.vision.documentscanner.GmsDocumentScannerOptions options) { /* compiled code */ }

    private GmsDocumentScanning() { /* compiled code */ }
}

返ってくるGmsDocumentScannerはInterfaceです。getStartScanIntent()というメソッドが用意されています。
こちらはActivityを引数にとり、Task<IntentSender>を返します。

Taskが返されるので、addOnSuccessListeneraddOnFailureListenerが使えます。
成功時にIntentSenderが返ってきます。IntentSenderはスキャナーを起動するために使用します。

public interface GmsDocumentScanner extends com.google.android.gms.common.api.OptionalModuleApi {
    @androidx.annotation.NonNull
    com.google.android.gms.tasks.Task<android.content.IntentSender> getStartScanIntent(@androidx.annotation.NonNull android.app.Activity activity);
}

scannerLauncherは後述する、ActivityResultLauncher<IntentSenderRequest>型の変数です。 こちらは終了したActivityの結果を受け取り、処理します。

GmsDocumentScanningResult (結果処理)

GmsDocumentScanningResultを用いて、結果を処理できます。
公式ドキュメントにて、以下のように記述されています。

val scannerLauncher = registerForActivityResult(StartIntentSenderForResult()) {
  result -> {
    if (result.resultCode == RESULT_OK) {
      val result =
        GmsDocumentScanningResult.fromActivityResultIntent(result.data) // ここで結果を受け取る
      result.getPages()?.let { pages ->
        for (page in pages) {
          val imageUri = pages.get(0).getImageUri()
          // imageUriを用いた処理
        }
      }
      result.getPdf()?.let { pdf ->
        val pdfUri = pdf.getUri()
        val pageCount = pdf.getPageCount()
        // pdfUriやpageCountを用いた処理
      }
    }
  }
}

GmsDocumentScanningResultfromActivityResultIntent()を用いて、結果を受け取ります。 Intentを引数に取り、GmsDocumentScanningResultとして返してくれます。

@androidx.annotation.Nullable
public static com.google.mlkit.vision.documentscanner.GmsDocumentScanningResult fromActivityResultIntent(@androidx.annotation.Nullable android.content.Intent data) { /* compiled code */ }

返されるGmsDocumentScanningResultですが、PagePdfを持っています。

public abstract class GmsDocumentScanningResult implements android.os.Parcelable {   
    ...
    public static abstract class Page implements android.os.Parcelable {
        @androidx.annotation.NonNull
        public abstract android.net.Uri getImageUri();

        public Page() { /* compiled code */ }
    }

    public static abstract class Pdf implements android.os.Parcelable {
        public abstract int getPageCount();

        @androidx.annotation.NonNull
        public abstract android.net.Uri getUri();

        public Pdf() { /* compiled code */ }
    }
}

現時点でpageは画像のUriPdfはページ数とUriを取得できるようです。
それぞれ結果に合わせて、処理を記述できます。

説明については、以上になります。

まとめ

ML Kit Document Scanner API を用いると、簡単に高品質なドキュメントスキャナーが実装できました。
実装の簡単な流れは以下の通りです。

  1. スキャナーのオプションを決める
  2. スキャナーを呼び出す
  3. スキャナーから結果を取得する

余談

未実装のキャプチャーモード

com.google.mlkit.vision.documentscanner.GmsDocumentScannerOptions のコードには CaptureMode なるものが存在しました。

public static final int CAPTURE_MODE_AUTO = 1;
public static final int CAPTURE_MODE_MANUAL = 2;

現在はオートのみだが、今後はマニュアルで選択できるような機能が追加されるかもしれない……?
アップデートが楽しみです。今後も追っていきたいと思います!