2013年5月21日火曜日

Android Eclipse ADT22へのアップグレードではまる

Android SDKをバージョン22にアップグレードしたら、Eclipse ADT(Android Development Toolkit)も同じく22にアップグレードしろというのでアップデート。すると、いきなり既存プロジェクトのgenフォルダのファイルが自動生成されないようになりR.javaが参照できないという大量のエラーが出るようになった。

非常に困っていたが以下のページに助けられる。

どうやらAndroid SDK Build-toolsというツールが新しくビルドに必要になったようである。

こんなのわかるわけない…
せめて自動でインストールしてほしい。

まるで古いPCのアドベンチャーゲームをやらされているような感覚である。 そして、後日Nexus7でもやられてしまった。

関連記事

2013年5月19日日曜日

Android Viewのカスタム属性の作り方

自作のViewにカスタム属性を定義してレイアウトXMLから設定を変えられるようにする場合は、attr.xmlのdeclare-styleableで定義をする方法についてのメモ。

背景色の属性

背景色関連の属性は以下のように色(color)や画像(drawable)で設定できるようにできるとよい。
<!-- 色(color)でmyBackgroundを指定する -->
<myapp.MyView
  android:id="@+id/myview"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  myapp:myBackground="#ffff0000"
/>

<!-- イメージ(drawable)でmyBackgroundを指定する -->
<myapp.MyView
  android:id="@+id/myview"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  myapp:myBackground="@drawable/bg"
/>

こんなときはまず attr.xmlでは以下のようにreferencecolorの属性として定義する。

<resources>
  <declare-styleable name="MyView">
    <attr name="myBackground" format="reference|color"/>
  </declare-styleable>
</resources>

自作ViewのJava側ではDrawableとして受け取り、背景色を設定したいviewにsetBackgrund()で設定する。 colorもDrawableで受けることができ、背景色として設定できるようだ。 この方法でよいのか公式なドキュメントを確認できていないが問題なく動いているようである。

public MyView(Context context, AttributeSet attrs) {
  super(context, attrs);

  Drawable myBackground = a.getDrawable(R.styleable.MyView_myBackground);
  if (myBackground != null) {
    myView.setBackgroundDrawable(myBackground);
  }
}
ちなみに、AttributeSet.getColor()で受けると以下のようなエラーが出る。 背景色を設定できるようにする場合は特に理由がなければDrawableで受けるようにしておいたほうが無難だろう。

Caused by: android.view.InflateException: Binary XML file line #35: Error inflating class myapp.MyView
    at android.view.LayoutInflater.createView(LayoutInflater.java:613)
    at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:687)
    at android.view.LayoutInflater.rInflate(LayoutInflater.java:746)
    at android.view.LayoutInflater.rInflate(LayoutInflater.java:749)
    at android.view.LayoutInflater.rInflate(LayoutInflater.java:749)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:489)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:396)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:352)
    at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:270)
    at android.app.Activity.setContentView(Activity.java:1881)
    at myapp.MyActivity.onCreate(MyActivity.java:58)
    at android.app.Activity.performCreate(Activity.java:5104)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1080)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2144)
    ... 11 more
Caused by: java.lang.reflect.InvocationTargetException
    at java.lang.reflect.Constructor.constructNative(Native Method)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
    at android.view.LayoutInflater.createView(LayoutInflater.java:587)
    ... 24 more
Caused by: android.content.res.Resources$NotFoundException: File res/drawable-hdpi/bg.png from drawable
resource ID #0x7f020011: .xml extension required
    at android.content.res.Resources.loadColorStateList(Resources.java:2094)
    at android.content.res.TypedArray.getColor(TypedArray.java:319)
    at myapp.SampleQueryView.(SampleQueryView.java:54)
    ... 27 more

2013年5月16日木曜日

Android Twitter4JでOAuth認証する

Twitter4JでOAuth認証するための方法を調査した時のメモ。

Callback URLを使わずお手軽にすませる方法を使った。 この場合はTwitterログインの後にPIN番号が画面に表示されるので、それを手で入力して認証を完了する。 大まかな手順は次のようである。

  1. TwitterにRequestTokenトークン要求
  2. Twitterから認証のためのログインページURLが返されるのでブラウザで表示
  3. ユーザーがログインしてアプリを許可するとTwitterがPIN番号を発行
  4. TwitterにPINを渡してAccessトークン取得

今回紹介するコードはAndroidアプリつぶマップで使用しているので、 どのように動くか興味ある方はインストールしてもらえるとよいかと思う。

「つぶマップ」ダウンロードサイトへ

Twitter4Jラッパークラス

RequestTokenトークン要求とAccessトークン取得の際、素のままのTwitter4J APIを呼び出してもよいが、 それぞれの処理間での値の保持のためラップしたクラスを作成し呼び出すようにした。
public class TwitterTest {

  private static final String CONSUMER_KEY = "アプリに発行されたConsumer Key";
  private static final String CONSUMER_SECRET = "アプリに発行されたConsumer Secret";
  
  private Twitter twitter;
  private RequestToken requestToken;
  
  public String requestToken() {
    // インスタンスの初期化
    twitter = TwitterFactory.getSingleton();

    // Twitterに登録したアプリのConsumer key と Consumer secretを設定
    twitter.setOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);

    // RequestTokenトークン要求
    // 認証ページのURLを返すのでWebViewで表示しユーザーにログインしてもらう
    requestToken = twitter.getOAuthRequestToken();
    return requestToken.getAuthorizationURL();
  }
  
  // Accessトークン取得。引数にWebで表示されたPINを入れる。
  // 認証済みのTwitterインスタンスを返す
  public Twitter getAccessToken(String pin) throws TwitterException {
    if (requestToken == null) return false;
    
    // RequestTokenとPINよりAccessトークン取得
    AccessToken _accessToken = twitter.getOAuthAccessToken(requestToken, pin);
    return twitter;
  }
}

アプリ側

アプリ側ではラッパークラスを使い次のようにコードを書く。

まず、TwitterにRequestTokenトークン要求する。ラッパークラスのrequestToken()メソッドを呼び出す。

TwitterTest t = new TwitterTest();
String url = t.requestToken();

メソッド呼び出しの結果、認証のためのTwitterへのログインページURLが返されるのでこのURLをWebViewに表示する。 以下のような画面が開く。同じ画面の上部にはPIN番号を入力するためのEditTextを配置した。

ログインページでユーザーがログインしてアプリを許可するとTwitterがPIN番号を発行する。 画面表示されたPIN番号をユーザーに入力してもらう。

入力されたPINを引数にラッパークラスのgetAccessToken()メソッドを呼び出すことで認証が完了する。

String pin = "ユーザー入力のPIN番号";
Twitter twitter = t.getAccessToken(pin);

メソッドの返り値である認証済みTwitterインスタンスを使ってツイート取得や検索ができる。 以下は検索の例である。

Query q = new Query();
q.setQuery("キーワード");
QueryResult qr = twitter.search(q);

皆さんもぜひ試してみてください。

2013年5月2日木曜日

MySQL 行の作成日と更新日を自動でつける その2

「MySQL 行の作成日と更新日を自動でつける」の記事で、 行の作成日と更新日を自動でつけるDDL(テーブル定義)の方法を紹介した。 この方法では自動でつけられるのは作成日と更新日どちらか1テーブルに1つの制約があった。

その後の調査で分かったのだが、MySQLではNOT NULLなTIMESTAMP型の列にNULLを書き込むと現在時刻に置き換えられるという仕様がありそれを利用することで、一応自動で付与するということもできるようだ。

以下のようなテーブル定義として、ts1に行の作成日時、ts2に行の更新日時を入れることとする。

CREATE TABLE t3 (
  rid INTEGER,
  txt VARCHAR(20),
  ts1 TIMESTAMP DEFAULT 0,
  ts2 TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

INSERT時にts1にはNULL値を書き込むことで書き込みの発生した現在時刻が自動で入る。

mysql> INSERT INTO t3 (rid,txt,ts1) VALUES(1,'aaa',NULL);
Query OK, 1 row affected (0.08 sec)

mysql> INSERT INTO t3 (rid,txt,ts1) VALUES(2,'bbb',NULL);
Query OK, 1 row affected (0.09 sec)

mysql> SELECT * FROM t3;
+------+------+---------------------+---------------------+
| rid  | txt  | ts1                 | ts2                 |
+------+------+---------------------+---------------------+
|    1 | aaa  | 2013-04-23 16:21:44 | 2013-04-23 16:21:44 |
|    2 | bbb  | 2013-04-23 16:21:44 | 2013-04-23 16:21:44 |
+------+------+---------------------+---------------------+
2 rows in set (0.00 sec)

UPDATE時にはts1には値を書き込まないようにする。このようにすることでts1の日時は更新されず、行が作成された日時を示すようになる。

mysql> UPDATE t3 SET txt='zzz' WHERE rid=1;
Query OK, 1 row affected (0.10 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT * FROM t3;
+------+------+---------------------+---------------------+
| rid  | txt  | ts1                 | ts2                 |
+------+------+---------------------+---------------------+
|    1 | zzz  | 2013-04-23 16:21:44 | 2013-04-23 16:21:49 |
|    2 | bbb  | 2013-04-23 16:21:44 | 2013-04-23 16:21:44 |
+------+------+---------------------+---------------------+
2 rows in set (0.00 sec)

まあ、でもINSERT時にNULLを指定して書き込まないといけないので、ここに現在時刻を指定するようにしてもプログラムの質としてはあまり変わらないような気がする。 むしろその方がトリッキーでなくわかりやすいかも。