Android で複数非同期処理のシーケンス制御をライブラリに依存せず Pure Java で実装しなければならない場合の一案

もし、 Android 開発において、ライブラリに依存せず Pure Java を前提とし、複数の非同期処理をシーケンス制御する必要が出てきた場合に、どう解決すればよいか、の問題に対する一案を残した記録文章です。

背景

直近の仕事で、 Android 向け社内ライブラリの開発に携わっていたのですが、いくつかの社内事情があり、そのライブラリが依存するライブラリは appcompat-v7 のみという制約がありました。

f:id:sho5nn:20170506202536j:plain

あまり詳しくは書けないですが、そのライブラリは SSO を実現する機能を備えていて、いくつかのログインシーケンス制御があり、そのシーケンス中にあるそれぞれのステップが非同期処理で実行され、前の非同期処理の結果を元に次の非同期処理を行うような処理を実装する必要がありました。

最近のトレンドに習えば、 RxJava ライブラリなどを使用して精神的安定を維持しつつ開発・保守したいところですが、先述した制約のため Pure Java で実装するしかない状態でした。

その状態のまま開発した場合は言わずもがな、先行き怪しく最悪死人も待ったなしなのでどうにかしないとその先は地獄だぞという気持ちがあり、他のメンバーと議論したところ、 JavaScript の Promise からヒントを得てみてはどうだろうかということになり、 Promise like なインターフェースをまず実装し、そのインターフェースを用いてシーケンス制御の実装を進めました。

以下、そのときに実装した Promise like なインターフェースのコード(実際は、そのときに書いたコードを元に更に修正した)と、それを使用したサンプルコードを雑に残します。

参考

コード

基本的には Rx と似ていて、何らかのデータがどのようにして伝達していってその結果をどのように扱うか、のデータフローを先にコードで表現し、実行時に何らかのデータがそのデータフローに則って実行結果を取得します。

// sample
ExecutorService executor = Executors.newCachedThreadPool();

Promise
  .when(executor, Promise.single(new FooTask()))
  .then(new FulfillCallbackThenAll<String>() {
    @Override
    public PromiseTask.All onFulfilled(String value) {
      return Promise.all(new BarTask(), new BazTask());
    }
  })
  .then(new FulfillCallbackThenSingle<Object[], Void>() {
    @Override
    public PromiseTask.Single<Void> onFulfilled(Object[] value) {
      return Promise.single(new QuxTask());
    }
  }, new RejectCallbackDone<Throwable[]>() {
    @Override
    public void onRejected(Throwable[] value) {
      Toast.makeText(SimpleActivity.this, "failure", Toast.LENGTH_SHORT).show();
      progress.setVisibility(View.GONE);
    }
  }).atMain()
  .done(new FulfillCallbackDone<Void>() {
    @Override
    public void onFulfilled(Void value) {
      progress.setVisibility(View.GONE);
      updateViewWhenSuccess();
    }
  }, new RejectCallbackDone<Throwable>() {
    @Override
    public void onRejected(Throwable value) {
      progress.setVisibility(View.GONE);
      updateViewWhenFailure();
    }
  }).atMain();


// with lambda
Promise
  .when(executor, Promise.single(new FooTask()))
  .then((FulfillCallbackThenAll<String>) value -> Promise.all(new BarTask(), new BazTask()))
  .then(
    (FulfillCallbackThenSingle<Object[], Void>) value -> {
      return Promise.single(new QuxTask());
    },
    (RejectCallbackDone<Throwable[]>) value -> {
      Toast.makeText(SimpleActivity.this, "failure", Toast.LENGTH_SHORT).show();
      progress.setVisibility(View.GONE);
    }).atMain()
  .done(
    value -> {
      progress.setVisibility(View.GONE);
      updateViewWhenSuccess();
    },
    value -> {
      progress.setVisibility(View.GONE);
      updateViewWhenFailure();
    }).atMain();

非同期処理は Callable もしくは Runnable インターフェースで実装し、 Promise オブジェクトに渡します。

Promise.when() を呼び出して Promise オブジェクトを取得し、 Promise.then() でチェーンしつつ次の Promise オブジェクトを取得し、最後に Promise.done() を呼び出して最初の Promise オブジェクトの非同期処理を実行します。

チェーンする場合、 Promise.then() に渡すコールバックインターフェースの返り値として、次に実行したい非同期処理を表す Callable もしくは Runnable インターフェースオブジェクトを渡すことで、その次の Promise.then() に渡したコールバックインターフェースが呼ばれます。

Promise.single()/all()/race()

Callable もしくは Runnable を Promise オブジェクトに渡す際に、実行方法を指定します。

  • Promise.single(Callable/Runnable)
    • 1 つの非同期処理を実行する
  • Promise.all(Callable[]/Runnable[])
    • 全て成功した場合のみ成功時コールバックインターフェースを呼ぶ
    • どれか 1 つでも失敗した場合には失敗時コールバックインターフェースを呼ぶ
  • Promise.race(Callable[]/Runnable[])
    • どれか 1 つの非同期処理が成功/失敗に関係なく終了した時点で、成功時/失敗時コールバックインターフェースを呼ぶ

Promise.atMain()/at(Handler)

成功時/失敗時コールバックインターフェースの呼び出されるスレッドを指定します。 Promise オブジェクトごとに指定するので、 View 更新等、ステップごとに細かく制御が可能。

雑感

余程のことがないかぎりは、世に存在する便利なライブラリを使うべきなのは言うまでもなく、しかし、個人的にはまあまあ楽しめたとしとこうというのが本音だった。


Photo by Jamison McAndie | Unsplash

DroidKaigi 2017 で良いキッカケを

@sho5nn です。 DroidKaigi 2017 運営メンバーの一人として、協賛企業さんへの連絡や調整などを担当しています。

DroidKaigi は今回で 3 回目の開催。

僕は 1 回目は一般参加者として、2, 3 回目は運営メンバーとして参加しています。この記事では、運営メンバーとして DroidKaigi から得てほしいものと、運営メンバーとしてこういうことしてるよー的な内容を書いてみます。

DroidKaigi で良いキッカケを

f:id:sho5nn:20170215030124j:plain

DroidKaigi は Android の技術に特化した、エンジニアが主役のカンファレンスです。

登壇者、一般参加者はほぼエンジニア、運営スタッフもほぼエンジニアです。企業関係者さんもエンジニアさんが多いことでしょう。

そんな方たちが 1 年に 1 度、わいわいと集まってしまいます。それも 1000 人規模で。すごーい!

これだけの規模になれば、

  • 今まで知らなかった Android エンジニアと話すキッカケ
  • TwitterGitHub のアイコンだけは知ってるけど話したことない方と交流できるキッカケ
  • 聴いたセッションの内容をさっそく実践に導入してみようかなというキッカケ
  • 普段抱えているあの問題、他の方はどう解決してるんだろう?と色んな人に直接聞いて答えを探せるキッカケ
  • 最近流行ってるアレ、どうですか?と会話してトレンドの感触を掴めるキッカケ
  • 公式アプリ 、ここ修正したらもっと良くなるんじゃ?というコントリビュートのキッカケ

github.com

などなど、何かしらプラスになる良いキッカケがあることでしょう。あります。

是非 DroidKaigi に参加していただき、なんでもいいので何かの “キッカケ” を得て、

次なるステップへの糧にしてほしい、と思います。

スポンサー担当こういうことしてるよ

f:id:sho5nn:20170215030159j:plain

イベントがこれだけの規模となると、会場準備などでまとまったお金が必要になってきます。

DroidKaigi 2017 の準備に必要なお金は、チケット販売代金の他に、協賛企業さんからいただいた協賛金も合わせてやりくりしています。

スポンサー担当としては、協賛金含むスポンサープランの計画立案と各企業さんとのやり取りが主な運営作業です。やり取りの内容は、例えば、公式サイトに掲載する企業ロゴの提出依頼、企業ブース出展にともなう会場の準備とそのご案内、請求書の発行・送付などです。

それらのやり取りはだいたいメールで行っています。が、一応僕も Android エンジニア、普段メールとはあまり縁がないためか、文章を考えるだけで数十分かかったりしていました。

最近は、慣れもあってかそんなにかからなくなり、少し格好付けた言葉…例えば「承りました」の使いドコロをなんとなく掴めてきた気がします。社会人として圧倒的成長です。

今回は zendesk というカスタマーサポートツールを導入してみて、各企業さんからのご質問への回答や依頼メールの返信を行なっています。まだまだうまいこと回せず、知らないうちにチケットが溜まって アワワワ となってたりしますが、普通に生活していたら経験できない貴重な体験をしているんだなーという漠然とした気持ちで頑張っています、ハイ。

さいごに

参加チケットはまだ数十枚ほど余っています。まだ登録に迷っている方は是非この機会を逃さないように。 :pray:

droidkaigi.connpass.com