ボクココ

個人開発に関するテックブログ

Android における EditText のクールなバリデーション実装

Android アプリによっては結構たくさんのEditTextが出て来て、それらを一つずつ検証しないといけない場面ってのはよくある。これら一つ一つに、以下のことを書いている方は多いのではないだろうか。

if (editText.getText().toString().isEmpty()) {
    // 入力必須エラー
}
if (editText.getText().toString().length < 8) {
    // 最小文字数エラー
}

それぞれにこのような記述を書くだけで、一瞬で残念なソースコードができあがる。かつての自分もそうやって書いていて後になってこれはひどいなーと思うようになった。今回はこれを改良しよう。

ValidateManager の使用

まず使い方はこんな感じにするように作った。

        ValidateManager validateManager = new ValidateManager(context);
        Validators validators = new Validators();
        validators.add(new PresenseValidatable());
        validators.add(new MinLengthValidatable(4));
        validators.add(new MaxLengthValidatable(320));
        validateManager.add(editText, validators);

        validators = new Validators();
        validators.add(new MaxLengthValidatable(24));
        validateManager.add(editText2, validators);

         validateManager.validate()

最後のvalidate()を呼ぶと、エラーメッセージがある場合はその文字列リストが返る。これでバリデーションの使い回しが可能だ。
今回はEditText のみのバリデーションだけど、バリデーションのほとんどはEditTextだと思うので、それで良いと思う。今後他のViewにも必要になったら考え直す。

ValidateManager の実装

ここまで設計できれば後は実装するのみ。

public class ValidateManager {
    private Map<EditText, Validators> validateMap;
    private Context context;

    public ValidateManager(Context context) {
        this.context = context;
        validateMap = new HashMap<EditText, Validators>();
    }

    public void add(EditText editText, Validators validators) {
        validateMap.put(editText, validators);
    }

    public List<String> validate() {
        List<String> errorMessages = new ArrayList<String>();

        LogUtil.v(validateMap.size());
        for (Map.Entry<EditText, Validators> keyValue : validateMap.entrySet()) {
            Validators validators = keyValue.getValue();
            EditText editText = keyValue.getKey();

            for (Validatable validatable : validators.as_list()) {
                ValidateDto validateResult = validatable.isValid(editText);
                if (validateResult.isValid()) {
                    continue;
                }
                // validate error
                StringBuffer errStr = new StringBuffer();
                errStr.append(editText.getContentDescription().toString())
                        .append(context.getString(validateResult.getMessage()));
                if (validatable.getTailMessage() != null) {
                    errStr.append(validatable.getTailMessage());
                }
                LogUtil.v(errStr.toString());
                errorMessages.add(errStr.toString());
            }
        }

        return errorMessages;

    }
}
public class Validators {
    private List<Validatable> validateLists;

    public Validators() {
        validateLists = new ArrayList<Validatable>();
    }

    public void add(Validatable validatable) {
        validateLists.add(validatable);
    }

    public List<Validatable> as_list() {
        return validateLists;
    }
}
public interface Validatable {
    ValidateDto isValid(EditText editText);
    String getTailMessage();
}
public class ValidateDto {
    private boolean ret;
    private int message;

    public ValidateDto(boolean ret, int message) {
        this.ret = ret;
        this.message = message;
    }

    public int getMessage() {
        return message;
    }

    public boolean isValid() {
        return ret;
    }
}
public class MinLengthValidatable implements Validatable {
    // default length
    private int minText;

    public MinLengthValidatable(int minText) {
        this.minText = minText;
    }

    @Override
    public ValidateDto isValid(EditText editText) {
        boolean isValid = true;
        if (editText.getText().length() < minText) {
            isValid = false;
        }

        ValidateDto ret = new ValidateDto(isValid, R.string.err_length_min);
        return ret;
    }

    @Override
    public String getTailMessage() {
        return String.valueOf(minText);
    }

}

Validatable を実装したクラスをどんどん作っていけば色んなバリエーションのEditTextの検証ができる!これは超便利。
こういう Interface の使い方はめっちゃ便利だよな〜。 Java使いならInterfaceは使いこなせるようになった方がいいですよ。

Android の ViewPager で使う Fragment の注意点

Android アプリ開発で便利な ViewPager . 画面のスワイプが簡単に実装できるのでどんなアプリを作るにも役立つ。

これはFragmentを使っていて、今回そのFragment周りを中心にどう実装するのかをまとめる。

ViewPager に FragmentStatePagerAdapter を setAdapter

これがまず基本。 FragmentStatePagerAdapter を継承したクラスを作り、

   @Override
    public Fragment getItem(int i) {
    }

ここに作った各Fragmentを返すように作ってあげる。 詳細は他に任せるとして、今回はこのFragment のライフサイクル的なのをまとめたい。

ライフサイクルの使い分け

まずは典型? と思われるコードを書く。

public class BoardFragment extends Fragment {
    private static BoardFragment boardFragment;

    public static BoardFragment getInstance() {
        if (boardFragment == null) {
            boardFragment = new BoardFragment();
        }
        return boardFragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_board, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        // 1
    }

    @Override
    public void setMenuVisibility(final boolean visible) {
        super.setMenuVisibility(visible);
        if (visible && isAdded()) {
            // 2
        }
    }
}

getInstance()

シングルトンにするケースが多いみたい。

onCreateView

ここで使うレイアウトを指定する。

onViewCreated

ここで view.findViewById で各要素が取って来れる。 この処理内での呼び出しであれば、 getActivity() が問題なく取って来れる。 自分はActivityでいうonCreate に該当する処理をここで書いている。

setMenuVisibility

今回の落とし穴。 自分はここで onResume() 的な処理を書いていた。例えば2つのFragmentA, FragmentBがあったとして、FragmentAの値を更新した後にそのままFragmentBに移ったときに FragmentAの更新した内容をFragmentBでも反映させる必要があった場合など使える。 注意点としてisAdded() は、 Activityが対象Fragment をアタッチしたかどうかを返すので、falseだったら setMenuVisibility内の getActivity() の呼び出しは null が返ってくる。ここのifは重要。

んで、 View の処理を setMenuVisibilityだけに書くと、このisAdded()がfalseを返したときに処理が何も走らない、ということが起きる場合がある。

これを回避するためにも、Viewの処理、特にgetActivity()でアクティビティやコンテキストが欲しい場合はonViewCreated にその処理を書かないといけない。

所感

Fragment は難しい。。 Fragment に関する本とかが出始めているようだ。 これからのAndroidアプリ開発にFragmentの知識は切っても切れないので頑張って学んでいくしかない。

Java の enum 再入門。enumがあると何が嬉しいのか

ども@kimihomです。 enum って案外使わなくても何とかなる場合が多くて、今ままであまり使ってこなかった方は多いのではないでしょうか? しかし、このenumはソースの可読性を向上させる上で、知らなくてはならないテクニックの1つだと思います。そこで今日はenumの使い方についておさらいしましょう。

今回は Android アプリ開発におけるよくあるパターンを紹介します。

enum は定数間の関係を定義できる

まず、enum が嬉しいのはこれが個人的に最も大きいです。 例えばアプリ内に「称号」の概念があったとします。称号には名前があり、称号の説明があり、何レベルから何レベルまでの間が、その称号であるかがわかるようにしたい、といった所です。

これをenumを使わないとなると、DTO の List を作るのが一般的な所でしょうか。 リストに詰めるときにそれぞれの順番を正しくやる必要があるのが気持ち悪いですね。

class DegreeDto {
  private String name;
  private String description;
  private int minLv;
  private int maxLv;

  /* getter and setter */
}

class Hoge {
  String[] names = {"degree1", "degree2", "degree3};
  String [] descs = {"beginner", "medium", "advance"};
  int[] minLvs = {1, 5, 10};
  int[] maxLvs = {5,10, 100};
  
// for で回して DegreeDto 作ってlist に入れる
}

enumを使わないでやるとなるとまずパッと思いつく方法かと思います。人によっては何かをKeyにしてHashMap にしたりすると思いますが、称号のケースだとvalueに入れるのがnameやminLvなど複数いるので結局DTOを作らないといけなくなります。

enum を使ってみる

さて、そうした定数間の関係をきれいさっぱり定義できるのがenumさんです。

こんな感じになります。

enum Degree {
  degree1("degree1", "beginner", 1, 5),
  degree2("degree2", "medium", 5, 10),
  degree3("degree3", "advance", 10, 100);

  private final String name;
  private final String description;
  private final int minLv;
  private final int maxLv;

  /** getter */

  private Degree(String name, String description, int minLv, int maxLv) {
    this.name = name;
    this.description = description;
    this.minLv = minLv;
    this.maxLv = maxLv;
  } 
}


for(Degree degree : Degree.values()) {
  degree.getName();
  degree.getMaxLv();
  //...
}

こんな感じです。 enum により、定数間の関係がとても簡潔に書けたことがおわかりでしょうか。 Degreeはオブジェクトとして扱えるので、イテレートも普通にリストを回すかのように簡単です。 Degree.values()でDegreeを配列として返してくれるので、定義するだけで配列の中に関連する要素が簡単に取って来れます。

終わりに

こうしたenumを使ってクリーンなコードで書いた アプリ、 ストイック 、絶賛 Google Play で公開中でございますw

Google Androidプログラミング入門 改訂2版

複数アプリ間でSharedPreferenceを使ったときのキャッシュ問題

いやーこれはやられた。

Context.MODE_WORLD_READABLEで保存したSharedPreferenceを外部から読み込むときに getSharedPreference が ShardPreference インスタンスがキャッシュしてるせいで、保存元のアプリでSharedPreferenceの中身を保存しても参照するアプリ側で変更をキャッチできなかった。

原因は、getSharedPreference の第二引数を Context.MODE_PRIVATE にしていたせいだった。 MODE_MULTI_PROCESS にすればOK。

Context exContext = mContext.createPackageContext(Musee.MUSEE_APP_PKG, Context.CONTEXT_RESTRICTED);
SharedPreferences mSharedPreferences = exContext.getSharedPreferences(Musee.MUSEE_PREF, Context.MODE_MULTI_PROCESS);

getShardPreference はこんなコードになっているらしい

  @Override
    public SharedPreferences getSharedPreferences(String name, int mode) {
        SharedPreferencesImpl sp;
        synchronized (sSharedPrefs) {
            sp = sSharedPrefs.get(name);
            if (sp == null) {
                File prefsFile = getSharedPrefsFile(name);
                sp = new SharedPreferencesImpl(prefsFile, mode);
                sSharedPrefs.put(name, sp);
                return sp;
            }
        }
        if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
            getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
            sp.startReloadIfChangedUnexpectedly();
        }
        return sp;
    }

んで、下のほうのstartReloadIfChangedUnexpectedly、これがキャッシュの更新をしてくれるメソッドのようで、MODE_MULTI_PROCESSが大事だった。

Android 2系でもサポートライブラリのおかげか、ちゃんと動いた。良かった良かった。

Androidアプリ開発者が実践すべきエミュレータの高速化

久々にサービスで感動したので投稿。

今回紹介するのは Genymotion というもの。

genymotion

どんな仕組みかというと、AndroidLinuxベースでできたOSなので、それをVirtualBox仮想マシンとして起動する、というもの。おそらく内部的にVagrantのようなものでプログラムで自動で起動したり停止したりしている模様。無料で普通に使えるので、入れてみる価値ありです。 起動したVirtualBoxは何故か普通にadbコマンドで接続できる。なんでかわからないが、すごい!

Genymotion を使うメリット

個人的には3つ。この3つ全てが素晴らしい!!

Android apk のアップロードが超高速

今までのAVDのエミュレータで、Eclipse なり AndroidStudio なりでできたapkを入れて確認する訳ですが、ビルドしてアプリが起動するまで、めちゃめちゃ時間がかかったのですよ。アプリ起動するだけで最大1分くらい待たないと起動しない時とかあって、机投げ出したくなったりそれほどまで困っていたのです。テストを起動するときとかは顕著であまりの起動の遅さにテストを起動するたびにトイレに行ったり何か買いにいったりしてたくらいです。 それがなんと、Genymotionだと10秒以内で開始されます!今までの時間は何だったのだろうか!?

エミュレータ自体が高速

サクサク動きます。まぁこれは前のエミュレータでも他アプリほとんど入っていないので高速だったのですが、それをしのぐ爆速具合です。まぁそもそも提供しているAndroidバージョンが最新のものしかなくて、それを使っているからという理由もありますが。

キーボードでテキストが打てる!

これ、入れてみて知ったのですが、なんと普通にキーボードで文字が入力できます。 これがどれほどまで素晴らしいかは今までAndroidエミュレータを触ったことのある人しかわからないと思いますが、一気に効率が上がります。 さらにコピペもできる!これだけで今までの苦労がどれだけ報われることか・・。

インストール方法

感動した話はこれくらいにしておいて、入れ方を簡単に。 といっても入れること自体はとても楽です。

Genymotion のサイトからまずはSignUp で会員登録し、その流れにそって自身のOSに沿ってFree バージョンのGenymotion をDL. install するだけです。インストーラが勝手に立ち上がります。

Genymotion の最初の画面はこんな感じ。

Proxy でいきなり色々失敗!って言われたが、Settings のネットワークでProxy指定して余裕で解決。

+Add を押して好きなAndroidエミュレータをDLしよう。 自分は4.2.2系のを選択。

こっからが詰まった所。

起動しても The Genymotion Virtual device could not obtain an IP address.For an unknown reason, VirtualBox DHCP has not assigned an IP address to virtual device. Run the VirtualBox software to check for issues. と怒られて失敗する

たぶん前からVirtualBox使ってるとこうなる。 Host-Only のIPアドレスがGenymotionの指定したIPAdressと違うためエラーとなる模様。 Windowsの場合だが、コントロールパネル-> ネットワークの接続でHost-Only ネットワークを一度無効にし、再度起動する。そうすると新しいHost Onlyネットワークが立ち上がって無事起動した。

APKを入れようとすると、 INSTALL_FAILED_CPU_ABI_INCOMPATIBLE と言われて失敗する

これはarmeabi でネイティブビルドをした場合に出た。CPUが違うから入れられないよ、的な感じだと思ってる。 これに対してはStackOverFlow の神回答を参照のこと。 エミュレータにDLしたzipをドラッグアンドドロップするだけでarmeabiで動いた。

まとめ

速いって素晴らしい。 Jenkins にもこれ導入したいなー。プラグインが早く出てくれることを期待。

Android におけるバックグラウンド処理の使い分け

個人的なバックグラウンド処理における見解をまとめる。

Service の使い時

アプリを終了しても裏側で何かをさせたい時に使う。これは Service しかできない。(Service は別プロセスで動くため)

一時的に裏側で処理をさせたい時は、 AsyncTask や Thread-Hanlder を使う。

IntentService の使い時

IntentService というクラスがあるが、これは裏側の処理をキューとして順次実行してくれるので、重い処理を裏側で実行させたい場合、かつそれらのスレッドが同時に複数起動されないようにしたい場合はこちらを使う。これは処理が終わった瞬間にサービスが停止されるのでプロセスが残り続ける心配がない。 ここら辺は AsyncTask や Thread-Handler でやるとたびたび問題になることがある。

今のところの自分のバックグラウンド処理使い分け基準

  • アプリを終了しても裏側で処理をさせたい。 → Service
  • 連続した重い処理を実行させたい → IntentService
  • Activity内でちょっとした時間のかかる処理をさせて終わったらUIいじる。お決まり処理が多い → AsyncTask
  • Activity内でちょっとした時間のかかる処理をさせて終わったらUIいじる。柔軟性が欲しい → Thread-Handler

ちなみにServiceが終わった時に何かしたい場合は Notification を使った方がいい。

IntentService で Unable to instantiate service

IntentService のサンプルを作っていて、上記エラーが出たのでメモ。 具体的なエラーはこんな感じ java.lang.RuntimeException: Unable to instantiate service com.sample.SampleService: java.lang.InstantiationException: com.sample.SampleService

原因はEclipse のコード生成を使って IntentService のコンストラクタにname引数があったためでだった。

public class SampleService extends IntentService {

    public SampleService(String name) {
        super(name);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Log.v("test", "called sample service");
    }

}

コンストラクタの引数を消して

public class SampleService extends IntentService {

    public SampleService() {
        super("SampleService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Log.v("test", "called sample service");
    }

}

これでOK。 

SQLiteOpenHelper の execSQL で2文まとめて実行してはいけない


久々のAndroidエントリー。


SQLiteOpenHelper#execSQL(str) は strのSQLをそのまま実行してくれる便利なメソッドだが、これに create table や alter table add column などをまとめて ; でつなげて実行すると、テーブルやカラムは出来上がるが、プログラムからその追加したカラムを呼びにいけないというなんとも謎の現象に見舞われる。


本当の原因はAndroidのソースを見に行かないとわからないが、とりあえずexecSQLを実行するときはSQL1文、という決まりを作ればまず問題は起きないだろう。

GreenDAOを使ってみた

GreenDaoはいわばAndroidSQLiteのORM。
デフォルトのSQLiteの操作は、使いにくいしお決まりの作法をコピペしたりと色々ひどいので、こういうのはフレームワークに任せたいところ。

環境作り

jarの用意

greendaogreendao-generatorfreemarker.jarをそれぞれ取ってくる

generator プロジェクト作成

新規→Javaプロジェクト
外部ライブラリーで先ほどとってきたgreendao-generatorとfreemarker.jarをライブラリーとして追加。
パッケージとクラスを適当に作る。ここにテーブルスキーマを記述する。


public static void main(String[] args) throws Exception {
Schema schema = new Schema(3, "{com.aaaa.dao}");

addNote(schema);
addCustomerOrder(schema);

new DaoGenerator().generateAll(schema, "../DaoExample/src-gen");
}

private static void addNote(Schema schema) {
Entity note = schema.addEntity("Note");
note.addIdProperty();
note.addStringProperty("text").notNull();
note.addStringProperty("comment");
note.addDateProperty("date");
}

private static void addCustomerOrder(Schema schema) {
Entity customer = schema.addEntity("Customer");
customer.addIdProperty();
customer.addStringProperty("name").notNull();

Entity order = schema.addEntity("Order");
order.setTableName("ORDERS"); // "ORDER" is a reserved keyword
order.addIdProperty();
Property orderDate = order.addDateProperty("date").getProperty();
Property customerId = order.addLongProperty("customerId").notNull().getProperty();
order.addToOne(customer, customerId);

ToMany customerToOrders = customer.addToMany(order, customerId);
customerToOrders.setName("orders");
customerToOrders.orderAsc(orderDate);
}

みたいに作る。
addNote()はシンプルなテーブル。
addCustomerOrder()はcustomer と order が1対多の関係になっていて、それを2つ一緒に定義している。idは指定しなくても勝手に作られる。

このGeneratorJavaプロジェクトは1個だけ作っておけば、あとは次からAndroidアプリ作るときもパッケージ名変えればいいだけだから使いまわせる。

作ったエンティティに対して、スーパークラスやインタフェースをインプリメントさせたい場合もあるよね。その時はentity#setSuperclass()とかentity#implementsInterface()とかも用意されているので使えそう。

generateAllをする前に、対象プロジェクトにソースフォルダーを追加しておく必要がある。今回の例ではDaoExampleプロジェクトにsrc-genを追加。

そんじゃジェネレート!

さっきの.javaを実行。

無事できました。うひゃひゃ
パスとかで躓いている方は、generateAllの引数にフルパス指定してみてください。

プログラム書いてみる

ちょっとね、、初期化が面倒。DevOpenHelperはアップデート時にテーブルドロップするから注意。


SQLiteDatabase db = new DaoMaster.DevOpenHelper(this, "notes-db", null).getWritableDatabase();
DaoSession daoSession = new DaoMaster(db).newSession();

NoteDao noteDao = daoSession.getNoteDao();

//インサート
Note note = new Note(null, noteText, comment, new Date());
noteDao.insert(note);
//デリート
long key = 1;
noteDao.deleteByKey(id)

// リード
List notes = noteDao.loadAll();
Note note = noteDao.loadByRowId(1);

//アップデート
Note note = new Note(1, noteText, comment, new Date());
noteDao.update(note);

//こんな見たことあるようなやり方もできる
List joes = noteDao.queryBuilder()
.where(Properties.text.eq("Hoge"))
.orderAsc(Properties.date)
.list();

 
list()以外にもrazyList()ってのもあるみたい。
「エンティティがメモリの要望に応じてロードされる。一回リストの中に要素がアクセスされたら、今後の為にキャッシュされる。必ず閉じないといけない。」って書いてあった。
Androidでスレッドを使っていると、SQLiteに対して複数のスレッドから同時にアクセスすると例外が発生して落ちる、みたいなのに悩まされることがよくある。これもGreenDAOは考慮されているみたいで、forCurrentThread()ってのをうまく使えば、利用側はスレッドとか心配しなくてバンバンアクセスできるらしい。内部読んでみたら、ThreadLocalを継承しているみたい。ここら辺ちょっと気になる。

Memo

Proguard


-keep public class my.dao.package.models.** {
public static ;
}



興味持ったら、
http://greendao-orm.com/
まで。
「道」のファビコンがいい味出してます。

RoboGuiceテストの仕方の翻訳をしてみた

https://code.google.com/p/roboguice/wiki/Testing
こちらを勉強を兼ねて翻訳します。

Testing

RoboGuiceアプリのテストの仕方

始める前に

ユニットテストをするための完全な説明はこのドキュメントの範囲を超えるが、これはより多くの共通のミスを防ぐのを助ける価値がある。

まず始めに、テストプロジェクトを作ろう。このプロジェクトはあなたのメインアプリのプロジェクトに明確に依存させておく必要がある。roboguice かguiceに依存を持つべきではない
次にそれを利用するどんなライブラリもエクスポートするためにメインアプリプロジェクトを修正しよう。特に、roboguice and guice librariesがエクスポートしていることを忘れずに!

最後にテストプロジェクトのAndroidManifest.xmlに以下の and スニペットがあることを確認しよう。






RoboUnitTestCaseでユニットテスト

ユニットテストはテストの種類において最も直接的で、それらの単純なユニットテストはRoboUnitTestCase で使える。-それはResourceやActivity,ContentProvider、Service、Applicationなどの依存を持っていない。

RoboUnitTestCase はランダムなビットコードやユーティリティクラス、ドメインモデルなどをテストするのによい。以下は一例だ。


public class MyTest extends RoboUnitTestCase {

// @*Test アノテーションを置くのをわすれずに。そして
// テストケース名の先頭が"test"で始めるのにも注意。
@MediumTest
public void test01() {
// Make sure you're using com.mydomain.R, not com.mydomain.test.R
assertEquals("Hello World, Lop!", getContext().getString(com.mydomain.R.string.hello));
}

}

もしInstrumentationTestCaseを知っているのなら、RoboUnitTestCase はあなたのApplicationで指定した設定を元にしたGuice インジェクションを追加しただけだ。
※テストを動かすための注意として、以下のようなApplicationクラスのコンストラクタを追加する必要がある。

public MyApplication( Instrumentation instrumentation ) {
super();
attachBaseContext(instrumentation.getTargetContext());
}

RoboActivityUnitTestCaseを用いたユニットテスト

RoboUnitTestCase のユニットテストは単純だったが、あなたのユーティリティクラスはもっとアクティビティに対するユニットテストが必要であるとわかるだろう。アクティビティのユニットテストはRoboActivityUnitTestCaseでほとんどが可能だ。
アクティビティの新規インスタンスはRoboActivityUnitTestCaseにあるそれぞれのテストメソッドで作成される。そのため、アクティビティのたくさんのテストを孤立させるのにとても良い手段である。詳細は ActivityUnitTestCaseまで。
下記のコードがそのサンプルコードである。


public class MyActivityUnitTest extends RoboActivityUnitTestCase {

protected Intent intent = new Intent(Intent.ACTION_MAIN);

public MyActivityTest() {
super(MyActivity.class);
}


// @*Test アノテーションを置くのをわすれずに。そして
// テストケース名の先頭が"test"で始めるのにも注意。
@MediumTest
public void test01() {
setApplication( new MyApplication( getInstrumentation().getTargetContext() ) );
final Activity activity = startActivity(intent, null, null);

// 何らかのテスト
assertNotNull(activity);
assertEquals( *1.getText(), "Hello World, Lop!");
}
}

RoboUnitTestCaseの場合は、あなたのApplicationクラスに下記のコンストラクタを追加する必要がある。

public MyApplication( Context context ) {
super();
attachBaseContext(context);
}

ActivityInstrumentationTestCase2での機能テスト

ActivityUnitTestCase2 はアプリが端から端まで全ての実行をしたい場合に使われるべきである。そのテストはアプリと実行環境をすべて実行し、それぞれに境界がない。Seleniumテストに近い: テストとしてユーザストーリーを実装し、スクリーン上で実際に実行する。ActivityUnitTestCase、それはアクティビティがスクリーン上に表示されないものだが、それはは動かない。そしてコンテキストを他のアクティビティに切り替えることは禁止されている。
・ActivityUnitTestCase はアクティビティのユニットテストを切り離す。(早いけど制限有)
・ActivityInstrTestCase2 はフルの端から端までのテスト(制限なし)


public class MyActivityFunctionalTest extends ActivityInstrumentationTestCase2 {

public MyActivityFunctionalTest() {
super("com.mydomain",MyActivity.class);
}


// @*Test アノテーションを置くのをわすれずに。そして
// テストケース名の先頭が"test"で始めるのにも注意。
@MediumTest
public void test01() {
final Activity activity = getActivity();

// 何らかのテスト
assertNotNull(activity);
assertEquals( *2.getText(), "Hello World, Lop!");
}
}

よくある落とし穴

バックグラウンドタスクのテスト

もしあなたがバックグラウンドスレッドを利用しているなら、バックグラウンドスレッドが終わった後にメインのUIスレッドの更新を実行しなければならくなるだろう。このパターンのクラスのとしてはAsyncTask, UserTaskや Handlerなどがある.
普通はバックグラウンドが発生するテストケースを作って、そのバックグラウンドの実行結果が終わったことをメインスレッドがチェックするようにするだろう。しかしながら、これはいくつかのケースで動かない場合がある。
問題は、AsyncTaskなどの関連は、バックグラウンドで実行し、それらが終わった後にメインのUIスレッドのアクションを投げることにある(例えばViewを更新するためなど)。この特定のケースではメインUIスレッドはテストスレッドをブロックするが、それはAsyncTaskがonPostExecute()メソッドを実行するまで永遠に待つことを意味する。
これは、新しいスレッドを作って、偽のUIスレッドをLooper.prepare()を呼ぶことで作りだし、Looper.loop()によって得ることができる。 RoboLooperThreadはこれをあなたのために管理してくれる。


public class MyBackgroundTaskTest extends RoboUnitTestCase {

@MediumTest
public void test01() throws InterruptedException {
// java finalであることを必要とする。
final String[] result = {null};
final CountDownLatch latch = new CountDownLatch(1);

// このスレッドは偽のUIスレッドである。
new RoboLooperThread() {
public void run() {

new MyBackgroundTask() {

@Override
protected void onPostExecute(String s) {
result[0] = s;
}

@Override
protected void onFinally() {
latch.countDown();
}

}.execute();

}
}.start();

latch.await();
assertTrue(result[0], result[0].contains("Search"));
}
}

バックグラウンドスレッドなしでバックグラウンドタスクのテストをする

Asynctaskを使っているなら、フォアグラウンドスレッドを全体的に実行することによって全てのバックグラウンドタスクをスキップできる。


public class MyBackgroundTaskTest extends RoboUnitTestCase {

@MediumTest
public void test01() throws InterruptedException {
final String result = new MyAsyncTask().get();
assertTrue(result.contains("Search"));
}
}
注意:AsyncTaskがonPostExecuteの実行を必要としないコードなら、適切に実行されないから、このテクニックはdoInBackgroundの結果をテストするためだけに有効である。
不幸にもまだSafeAsyncTask に相当する簡単なものはない。

AndroidUnitTestCase は動作しない

AndroidUnitTestCase の利用をオススメしない。なぜならテストされるアプリのContextや Resourceのアクセスを許可しないからだ。これはいくつかのテストなら問題ないが、他のケースで難しさや混乱を生む原因となる。AndroidUnitTestCaseを利用する利点があんまないので、代わりに(InstrumentationTestCase の代わりにベースとなっている)RoboUnitTestCase を使うことを提案する。

*1:TextView)activity.findViewById(R.id.hello

*2:TextView)activity.findViewById(R.id.hello

Webの人がAndroidアプリを作ってみて大事だな、と思ったことのまとめ

ちょっと前までの自分は、プライベートでRails, 仕事でPHPというWeb屋でしたが、最近になってプライベートも仕事もAndroidをいじるようになりました。
そこで感じたWebとの違い、今後のアプリ開発で押さえておきたいポイントを軽くまとめてみます。題して「Androidのココは気をつけろ!」

ListView をナメてかかるな!

Webアプリを作っていると必ずは出てくる同じ項目をリストで表示する機能。大抵のHTMLレンダリングエンジンでfor文みたいなので回して出力するだけです。
ここでWeb屋はリストビューを甘く見て、早く実装できる。と勘違いします。これに注意。

Android の場合、1行のレイアウトのファイルを作り、それをメモリに入れて使いまわすという方法を取ります(取り組めばわかると思いますがconvertView辺り)。 単純なリストビューなら確かにすぐ終わりますが、チェックボックスや一部の領域でタップ可能なリストなど、ちょっと凝った機能になるだけでこのレイアウトの使いまわしおよびイベントの管理がとても難しくなります。 私の場合、行をタップしてその行の中にあるチェックボックスにチェックを入れる、という一見とても簡単そうな機能を実装するのに丸2日ドハマりしました。。

画像を表示するだけでめちゃめちゃ重くなって落ちる、ということもよくあります。
具体的にこう解決しろ、とはここでは言いません。
ただリストビューの工数見積もりには気をつけろ、ということを伝えたいです。

画像を作ってもらう人にはこれは伝えておけ!

けっこう画像を作ってもらう人もAndroidアプリ用の画像は初めて、という人は多いです。
そんな人に伝えなきゃならないと思っているのは以下のようなものです。

  • 画像はhdpi, mdpi, ldip, xdpi などサイズ毎の画像が必要である
  • 画像の文字を埋め込まない!(国際化対応ができなくなる)
  • サイズごとの画像名は必ず同じにする
  • 画像命名規約を守る
  • ボタンとかはできるだけ自分たちで作る

命名規約として、最初は小文字、それ以降はアンダーバー、小文字、大文字、数字のいずれかのみが許されています。これを守ってくれないと、それぞれのサイズごとの画像をリネームしなきゃいけない作業が待ってるので、予め伝えておきましょう。
ボタンとかはstyleを用いてグラデーションとか角丸とかXMLで作れます。そういうのは自分で頑張って作った方がリソースの節約になります。

サクサク動くアプリの実現の為に、バックグラウンドの使い方を習得しよう

Web方面から来た人は意外と「スレッド」とか使ったことすらない、という人は多いのではないでしょうか?私もあまり使った経験がなかったです。androidアプリを使い始めて本格的にスレッドを使いはじめたのですが、意外と普通に使えました。
Androidでのバックグラウンドの実行は思いつく限りでは4つくらいあります。
・AsyncTaskを利用する
・Threadを作ってからのHandler
・Serviceを起動
・BroadcastReceiver

Web屋がAndroidを作るという場合は、たいていはWebのアプリと連携したAndroidアプリを作ると思うので、そのケースに対して有効なバックグラウンドの使い方は前者2つだと思います。
色々なAndroidのサンプルではAsyncTaskが用いられることが多いです。が、個人的にはあまり好きじゃないです。理由としてはオーバーライドしなきゃいけないメソッドの引数や戻り値が他のメソッドの戻り値などが読みにくいのと、preとpostのメソッドも特に用がなくても作る必要があるためです。まぁ好みの問題です。
個人的にはThreadを作ってからのHandlerをお勧めしたいです。
軽く説明します。


Handler handler = new Handler();
Thread t = new Thread(new Runnable() {
@Override
public void run() {

// 重い処理をここに書く

handler.post(new Runnable() {
@Override
public void run() {

// バックグラウンド処理完了後で、
// UIを更新したい場合ここに書く

}
});
}
});
t.start();

けっこう見た感じ、汚いソースになりそうですね。。そういうのを解決してくれるのがAsyncTaskなのかも。私はコメント部分は必ず一つのメソッドを実行するにとどめています。

このHandlerが最初ちょっとわかりにくかったので、自分の理解を説明します。
Androidには"UIスレッド"というのがメインのスレッドになってます。 UI更新の処理のまとまり(Runnableオブジェクト)は、それぞれスタックに積まれていて、Looperが一個一個実行してくれます。 handler.post で、そのUI更新のスタックに指定の処理を積み上げてくれます。これによりバックグラウンドスレッドで完了した後にUI更新が可能になるそうです。(図がないからわかりにくさMAXですな・・)


重い処理をどうにかしたい場合はAsyncTaskのほかにこの方法も検討してみてください。

"コールバック"を使おう

これはAndroidアプリ作るうえで便利なテクニックだなと思ってます。これを使うとソースがダイブ綺麗になるのではないでしょうか。
具体的にはインタフェースを使います。てかインタフェース便利すぎw


public interface Callback {
public void onFinished();
}

public class Hoge implements Callback {
public void doSomething() {
Fuga.doInBackground(this);
}

@Override
publc void onFinished() {
//処理完了後の処理
}
}

public class Fuga {
public static void doInBackground(Callback callback) {
//バックグラウンドで何かする

callback.onFinished();
}
}

基本構造はこれ!何かの処理を他のクラスに任せて、それが終わった後の処理が書きたいなーって時にぜひ使ってみてください。

LinearLayout と RelativeLayoutをうまく使い分けよう

Android難しい、と思われるところでレイアウトを構成するのが難しいという意見があります。確かに私も最初は全然思う通りのレイアウトを作れませんでした。そして環境依存でまた色々修正しないといけなかったり・・。レイアウトはAndroidアプリ開発するときには必ず苦戦すると思います。
よく一番最初に読む本ではLinearLayoutがよく紹介されると思います。これがよくないと思ってるんですよねー・・。私はRelativeLayoutの方をよく使います。
RelativeLayoutは画面内の一番下とか上とかのレイアウトを指定して、それを元に他のレイアウトはその基準に比べてどの位置にいるかをセットします。
これの良い所は、画面サイズに応じて特定の部分の拡大縮小も結構カンタンにやってくれるところです。この文字はどんな端末でも絶対出して、ここはちっちゃな端末の場合はスクロールしたりで対応しようみたいなことができます。
確かにLinearLayoutでも同様のことがLayoutWeightを使えばできます。でもこれにはとても大きな問題があって、TextViewをLayoutWeightで調整すると、一部の端末で文字が途切れたりするような現象が発生します。端末依存が出やすいです。。前はLinearLayoutのLayoutWeightで頑張ってたんですが、この問題が発覚しだしてからRelativeLayoutを積極的に使うようにしています。
LinearLayoutは、何かの要素を均等に配置したい、って場合に使ってます。

FrameLayoutってのもたまに使います。何かを重ねて表示したいといった場合くらいかな。


色々あるけど、Androidアプリ開発楽しいです!Webアプリ作ってる方も是非始めてみましょう〜。

Androidアプリ公開しました

今までちょくちょく開発を進めていたAndroidアプリですが、一区切りついたので公開しました。

シェアカメラ
https://play.google.com/store/apps/details?id=com.fiveearth.cloudcamera


詳しくは↑の説明をちらっと見てくれればわかるかと。

以下のようなことが解決できると思っています。
・いちいちFacebookに共有するときにアップロードしなければならないことの解決
・スマホの容量を気にすることなく撮りまくれる



技術的に大変だったことは、やはりFacebookSDKのAndroid版の理解。
大量のコールバックの関連を読んで、カメラと連携する部分をどこに書けばいいのかを色々考えました。
おかげでandroidのFacebookSDKの使い方は一通り理解。
次何かAndroidFacebookと連携したアプリを作る場合に大いに役に立ちました。

https://github.com/facebook/facebook-android-sdk

基本はソース読むのが一番!
色んな解説サイトあると思うけど、とりあえずインタフェースを実装したアクティビティを書いて、addListener の引数に this を指定して、その実装メソッド内でコールバックを書く。
この流れはWebアプリと連携したアプリであればよくある話みたい。


是非使ってみてください!

AlarmClock 動かすまで

OSSのAlarmClockのソースをみて勉強しようと思い、最初動かすまでのメモを残しておく。


Github:
https://github.com/android/platform_packages_apps_alarmclock


AlarmClock修正メモ

AlarmLlaxon.java
L89
//mVibrator = new Vibrator();
mVibrator = (Vibrator)getSystemService(VIBRATOR_SERVICE);

L207,8
//setDataSourceFromResource(getResources(), mMediaPlayer,
// com.android.internal.R.raw.fallbackring);
setDataSourceFromResource(getResources(), mMediaPlayer,
R.raw.fallbackring);
R.raw.fallbackring
としてfallbackring.oggをファイルを保存


Alarms.java
L462
//Intent alarmChanged = new Intent(Intent.ACTION_ALARM_CHANGED);
final String ACTION_ALARM_CHANGED = "android.intent.action.ALARM_CHANGED";
Intent alarmChanged = new Intent(ACTION_ALARM_CHANGED);

L512,521,538
指定APIに対応していないAPIを呼び出そうとしている。
Eclipseクイックフィックス
Add target api to (メソッド名)で修正


DigitalClock.java
L52 追加
private Context mContext;

L104, 110 追加
this.mContext = context;

L187
Alarms.java 同様アノテーション付与


SetAlarm.java
L116
//.findViewById(com.android.internal.R.id.content);
.findViewById(android.R.id.content);

L165
Alarms.java 同様アノテーション付与


AndroidManifest.xml
追加
要素の次

Please execute 'adb uninstall com.android.alarmclock' in a shell.
と出たら素直にCMDで実行

fallbackring.oggはDropboxにおいたので持って行っちゃってください。ApacheLicenseだから大丈夫だよね・・?ダメだったら即刻消します。
https://dl.dropbox.com/u/24440717/android/fallbackring.ogg