2017/04/27

DataBindingでViewのtagにenumを設定する

DataBindingを使えばViewのtagフィールドに好きなオブジェクトを差し込めるので↓のような実装を試してみました.
固定長リストをlayout.xmlで定義する際にenumを設定すれば, onClickリスナーでそれを取り出して使うことができます.

キャストする箇所がアレですが,,

<layout>
<data>
 <import type="hoge.foo.Type"/>
</data>

<LinearLayout
  ...
  >
    <TextView
      ...
      android:tag="@{Type.A}" />

    <TextView
      ...
      android:tag="@{Type.B}" />

    <TextView
      ...
      android:tag="@{Type.C}" />

    <TextView
      ...
      android:tag="@{Type.D}" />
</LinearLayout>
</layou>
@Override public void onClick(View v) {
  Object tag = v.getTag();
  if (tag == null || !(tag instanceof Type)) return;

  Type type = Type.class.cast(tag);
  ...
}

以上です.

2017/04/25

レイアウトのサイズ指定で足し算する

Viewのサイズを指定する時に, (A)のサイズと(B)のサイズの和を指定したい場合があります.
AとB, どちらもアプリで定義しているサイズであれば, その和を新たなdimensとして定義することもできますが,
例えば “アクションバーの高さ + 8dp” など, 片方がアプリの管理下にない場合はdimensで定義することができなくなります.
コード上で指定することもできますが, レイアウトの問題はレイアウトXMLで完結させたいところ.
理想としては下記のような指定ができれば良いのですが, Androidではこれができません.

<View
  android:paddingTop="?attr/actionBarSize + 8dp" />

そこで, DataBindingの”式”を使えばそれっぽく書くことができます.

<layout>
 <data>
  <import type="hoge.foo.Dimens"/>
  <import type="hoge.foo.Dimens.ActionBar"/>
 </data>

 <View
   android:paddingTop="@{ActionBar.height(context) + Dimens.dpToPx(context, 8)}"
public final class Dimens {
  @Px public static int dpToPx(Context c, int dp) {
    DisplayMetrics metrics = c.getResources().getDisplayMetrics();
    return (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics) + 0.5f);
  }

  public static class  ActionBar  {
    @Px public static int height(Context c) {
      TypedValue tv = new TypedValue();
      if (c.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
        return TypedValue.complexToDimensionPixelSize(tv.data,
            c.getResources().getDisplayMetrics());
      }
      return 0;
    }
  }

以上です.

2017/04/16

モバイルアプリ開発エキスパート養成読本

この度「モバイルアプリ開発エキスパート養成読本」をご恵贈頂きました. ありがとうございます.

ご恵贈頂いたことに加えて, この本の執筆メンバーでもある@shihochanさん, @ogaclejapanさんからの頼みとあっては, 何も書かないというわけにはいきませんので, 拙文ではありますが色々と本稿で書かせて頂きます.
*この本はAndroidとiOSをカバーしていますが, 私はAndroiderなので本稿ではAndroidに関する部分に絞っています.

この本ではリアクティブプログラミング, ビルドバリアント, DI, ユニットテストや運用に役立つツールが紹介されており, 流行り廃りの早いライブラリの類ではなく, 息の長いアプリを開発する上で押さえておくべきポイントがほどよくまとまっています. 本稿ではこの本で取り上げられている内容をいくつかピックアップし, この本を執筆されていたであろう時点から今時点までの間で動きのあった技術情報などを加味して色々と書いていきます.

リアクティブプログラミング

この本では, リアクティブプログラミングについての概念的な部分やRxJavaと, RxJava2の変更点について書かれています.
Android Studio 2.4 Preview 6でJava8構文サポートのアップデートがリリースされ, RxJavaを使う際には是非とも導入したいラムダ式が標準でサポートされるようになるのももうすぐです.

ビルドバリアント

この本では, アプリケーションの開発版・リリース版, 無料版・課金版など, apkのビルドモードや複数バージョンを効率よく作成できるビルドバリアントの仕組みが紹介されています.
ビルドバリアントを使い始めると様々なビルドタイプとプロダクトフレーバーの組み合わせを作りたくなります.
その時はフレーバーディメンションが便利です.

DI

この本では, DIフレームワークのDagger2が紹介されています.
新しいDagger2.10ではdagger.androidパッケージが追加され, AndroidでよりシンプルにDIできるようになりました.

ユニットテスト

この本では, ユニットテストやUIテストの方法についても紹介されています.
リアクティブプログラミングの章でも紹介されていますが, RxJavaをプロジェクトで使っている場合, ObservableのスケジューラをTestSchedulerImmediateSchedulerに差し替えたくなるケースがよくあります.
次のTestRuleを用意することでテストメソッド毎のスケジューラ切り替えを楽にできます.

package hoge.foo;

import com.android.annotations.NonNull;
import java.util.Objects;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import rx.Scheduler;
import rx.android.plugins.RxAndroidPlugins;
import rx.android.plugins.RxAndroidSchedulersHook;
import rx.android.schedulers.AndroidSchedulers;
import rx.plugins.RxJavaHooks;
import rx.schedulers.Schedulers;
import rx.schedulers.TestScheduler;

/**
 * RobolectricおよびJUnitテストにおけるScheduler各種をImmediateSchedulerとするルール.
 * スケジューラを差し替えたい場合はコンストラクタに指定する.
 *
 * @see SchedulerWith
 * @see SuppressSchedulerRule
 */
public class RxSchedulerRule implements TestRule {

  private final Scheduler defaultScheduler;
  private Scheduler scheduler;

  public static RxSchedulerRule immediate() {
    return new RxSchedulerRule(Schedulers.immediate());
  }

  public static RxSchedulerRule test() {
    return new RxSchedulerRule(Schedulers.test());
  }

  public RxSchedulerRule(@NonNull Scheduler scheduler) {
    this.defaultScheduler = Objects.requireNonNull(scheduler);
    this.scheduler = defaultScheduler;
  }

  /**
   * 現在設定されているスケジューラを取得
   */
  public Scheduler scheduler() {
    return scheduler;
  }

  /**
   * 現在設定されているスケジューラを{@link TestScheduler}にキャストして取得
   *
   * @throws ClassCastException 現在設定されているスケジューラが{@link TestScheduler}にキャストできない場合
   */
  public TestScheduler testScheduler() {
    return (TestScheduler) scheduler;
  }

  @Override public Statement apply(Statement base, Description description) {
    return new Statement() {
      @Override public void evaluate() throws Throwable {
        resetSchedulers();

        // スケジューラの上書き抑止
        if (description.getAnnotation(SuppressSchedulerRule.class) != null) {
          base.evaluate();
          return;
        }

        // 個別に指定されたスケジューラがあればそちらを優先
        SchedulerWith annotation = description.getAnnotation(SchedulerWith.class);
        scheduler = annotation != null ? annotation.value().scheduler : defaultScheduler;

        RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
          @Override public Scheduler getMainThreadScheduler() {
            return scheduler;
          }
        });
        RxJavaHooks.setOnComputationScheduler(s -> scheduler);
        RxJavaHooks.setOnIOScheduler(s -> scheduler);
        RxJavaHooks.setOnNewThreadScheduler(s -> scheduler);

        try {
          base.evaluate();
        } finally {
          resetSchedulers();
        }
      }

      private void resetSchedulers() {
        RxJavaHooks.reset();
        AndroidSchedulers.reset();
        RxAndroidPlugins.getInstance().reset();
      }
    };
  }
}
package hoge.foo;

import rx.Scheduler;
import rx.schedulers.Schedulers;

/**
 * テストケースで実行するスケジューラの種類.
 *
 * @see SchedulerWith
 */
public enum SchedulerType {

  /**
   * {@link Schedulers#immediate()}
   */
  IMMEDIATE(Schedulers.immediate()),

  /**
   * {@link Schedulers#io()}
   */
  IO(Schedulers.io()),

  /**
   * {@link Schedulers#computation()} ()}
   */
  COMPUTATION(Schedulers.computation()),

  /**
   * {@link Schedulers#newThread()}
   */
  NEW_THREAD(Schedulers.newThread()),

  /**
   * {@link Schedulers#test()}
   */
  TEST(Schedulers.test());

  public final Scheduler scheduler;

  SchedulerType(Scheduler scheduler) {
    this.scheduler = scheduler;
  }
}
package hoge.foo;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import rx.schedulers.Schedulers;

/**
 * {@link RxSchedulerRule}で指定したスケジューラとは異なるスケジューラをテストケースで指定できる.
 * これは, テストケース全体としては{@link Schedulers#immediate()}を使用するテストルールを採用しているものの,
 * 一部のテストケースでのみ{@link Schedulers#test()}を使用したい場合などに使用される.
 *
 * <code>
 *
 * @Rule public RxSchedulerRule rxSchedulerRule = new RxSchedulerRule();
 * @SchedulerWith(SchedulerType.TEST)
 * @Test public void testcase() throws Exception { ... }
 * </code>
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SchedulerWith {

  /**
   * このテストケースでのスケジューラを指定.
   * 指定がない場合は{@link SchedulerType#IMMEDIATE}が指定される.
   */
  SchedulerType value() default SchedulerType.IMMEDIATE;
}
package hoge.foo;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * {@link RxSchedulerRule}によるスケジューラの上書きを抑止できる.
 * このアノテーションが付与されたテストケースではスケジューラがテストルールによって上書きされない.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SuppressSchedulerRule {
}

本稿では, モバイルアプリ開発エキスパート養成読本で取り上げられている技術のごく一部をピックアップしてみました.
この本ではiOSも扱っており, Androidと同様にアプリを開発する上で押さえておきたいポイントがまとめられています.

「”とりあえず動くAndroidアプリ”を作ってきたけれど, 最新の技術を取り入れてワンランク上のアプリ開発がしたい」「いまどきのAndroidアプリ開発事情を知りたい」そんな方に向けて, 私はこの本をおすすめします.

以上.