2014年1月22日水曜日

Twitter4J で急に例外(TwitterException)が出るようになった件

タイトルの通り、開発中のアプリの Twitter 機能を久々に使ったところ
何をするにも TwitterException が発生し、全く使い物にならない状態と
なっていました。

 具体的には、
・OAuthAuthorization クラスの getOAuthRequestToken メソッド
・Twitter クラスの search メソッド
あたりを実行すると、

403:The request is understood, but it has been refused.

とか言われる始末。

わりと本気でパニックです。
何なんすか新年早々このタチの悪いドッキリは。
コレ頭にタライ落とされたほうがまだマシってもんです。

で、ネットを漁りまくったところ、

Restricting api.twitter.com to SSL/TLS traffic

要約:「2014/1/14以降、APIのアクセスはhttps限定にすっかんね。」

んな!?何コッソリ余計なことしてくれてんすか!!??



瞬間的に目の前にお花畑が広がりかけましたが、
まあ調べてみたら対応自体はそれほど難しくもないみたいで。

要は Configuration の UseSSL を true にしてやりゃいいみたいです。

参考) REST API, regarding 403 error code?

■認証オブジェクト作成(従来)
    Configuration conf = ConfigurationContext.getInstance();
    OAuthAuthorization oauth = new OAuthAuthorization(conf);

■認証オブジェクト作成(対応後)
    ConfigurationBuilder cb = new ConfigurationBuilder();
    cb.setUseSSL(true);
    OAuthAuthorization oauth = new OAuthAuthorization(cb.build());


■検索等で使用する Twitter オブジェクト作成(従来)
    Twitter tw = new TwitterFactory().getInstance();

■検索等で使用する Twitter オブジェクト作成(対応後)
    ConfigurationBuilder cb = new ConfigurationBuilder();
    cb.setUseSSL(true);
    Twitter tw = new TwitterFactory(cb.build()).getInstance();



ちなみに Twitter4J のバージョン 3.0.5 以降ならこの問題は
対応済みみたいです。

なのでライブラリ差し替え・リビルドでも対応できるんだとは思うけど、
開発中にサードパーティライブラリのバージョン変えるのって
他の挙動まで影響しないかなぁとかビビっちゃうあたりヘタレ全開。

2014年1月18日土曜日

TortoiseGit でのローカルリポジトリのブランチ削除方法

それなりの規模のソースコードを扱うようになるとバージョン管理というのは
けっこう重要になってきます。

・メジャーバージョンアップ向け修正と
・新機能追加のプロトタイプ版と
・急遽発生したバグフィックス版と

…とかいう分岐をいちいち物理的にフォルダ分けでなんかで
管理しようものならけっこうドエライことになります。


「今時そんな原始的なことやってるヤツいんのかよw」て、周りを見渡すと
ウチの会社へたすると半分くらいそんな感じだからもう目もあてらんない(ノ∀`)


で、私はとりあえずローカルでの管理に git を使ってるワケで。

git でのバージョン管理のベストプラクティス(運用)に関しては
まだまだ模索中ですが、このあたりは非常に参考になります。

見えないチカラ: A successful Git branching model を翻訳しました


------

んでタイトルの件。

Windows 環境で git を導入する場合、GUI クライアントでは
TortoiseGit というのがけっこう有名で使いやすいのですが、
使っているうちにふと。
 
ブランチを作成して、修正して、master にマージして、
いらないブランチは削除、…
…て、どっから削除すんのコレ???

この部分だけなぜか異様にわかりにくかったりしますが、
とりあえず以下の手順で削除可能なようです。

1.ログを表示



2.左上のブランチ名部分をクリック


3.削除したいブランチ名を右クリック→削除


て、実はもっとわかりやすいとこにあったりするのかなぁ…
基本的な機能だしもっとシンプルな方法ありそうな気もするけど。。。

2013年12月7日土曜日

PullToRefreshで下からの引っ張りにも対応する(実装編)

さて、前回準備した環境で実際に「引っ張って更新」を試してみます。

いまもう27時とかめっちゃ眠いので文章で説明するより実際にコードを
見た方がわかりやすいかと思いますので、詳細はコードを貼るとして
とりあえず要点だけ。

・通常の ListView のかわりに PullToRefreshListView を使用

・上下両方の引っ張りに対応するよう Mode.BOTH を設定

・ PullToRefreshListView の setOnRefreshListener にて
 「引き下げ/上げて更新」の挙動を設定
 →OnRefreshListener2 で、引き下げ/上げ のそれぞれについて
  異なる動作を設定できます。
 →ただ、前回書いた通り、同名(PullToRefresh)の異なるライブラリを
  使用している場合、OnRefreshListener2 がなかったりしますので
  ご注意ください。


で、実際のサンプルコードはこんな感じになります。
(上から引っ張る→初期状態に戻す、下から引き上げ→項目を追加する)

■MainActivity のレイアウト(activity_main.xml)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <com.handmark.pulltorefresh.library.PullToRefreshListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>


■リストの項目用レイアウト(list_item.xml)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/listItemText"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center" />

</LinearLayout>


■コード(MainActivity.java)
public class MainActivity extends Activity {

    private ArrayList<String> mListItems;
    private PullToRefreshListView mListView;
    private SampleListAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //項目初期化
        initializeItems();

        //ListView 設定
        mListView = (PullToRefreshListView)findViewById(R.id.listView);
        mAdapter = new SampleListAdapter();
        mListView.setAdapter(mAdapter);

        //PullToRefresh 設定
        mListView.setMode(Mode.BOTH);//モード指定:上下からの引っ張りに対応
        mListView.setOnRefreshListener(new OnRefreshListener2<ListView>(){

            //上から引っ張った場合
            @Override
            public void onPullDownToRefresh(PullToRefreshBase<ListView> refreshView) {
                initializeList();
                new FinishRefresh().execute();
            }

            //下から引き上げた場合
            @Override
            public void onPullUpToRefresh(PullToRefreshBase<ListView> refreshView) {
                addItem();
                new FinishRefresh().execute();
            }

        });
    }

    //項目を初期化
    private void initializeItems(){
        mListItems = new ArrayList<String>(Arrays.asList("Item 1", "Item 2", "Item 3"));
    }

    //リストを初期状態に戻す
    private void initializeList(){
        initializeItems();
        mAdapter.notifyDataSetChanged();
    }

    //リストにアイテムを追加
    private void addItem(){
        int count = mListItems.size();
        mListItems.add("Item " + Integer.toString(++count));
        mAdapter.notifyDataSetChanged();
    }

    //アダプタクラス
    private class SampleListAdapter extends BaseAdapter{

        @Override
        public int getCount() {
            return mListItems.size();
        }

        @Override
        public Object getItem(int index) {
            return mListItems.get(index);
        }

        @Override
        public long getItemId(int index) {
            return index;
        }

        @Override
        public View getView(int index, View view, ViewGroup arg2) {
            if(view == null){
                LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                view = inflater.inflate(R.layout.list_item, null);
            }
            TextView textView = (TextView)view.findViewById(R.id.listItemText);
            textView.setText(mListItems.get(index));
            return view;
        }
    }

    //リスト更新終了
    private class FinishRefresh extends AsyncTask<Void, Void, Void>{
        @Override
        protected Void doInBackground(Void... params) {
            return null;
        }

        @Override
        protected void onPostExecute(Void result){
            mListView.onRefreshComplete();//更新アニメーション終了
        }
    }
}


で、細かいことは確認してないんですが、どうやら onPullDownToRefresh
とかの中で直接  onRefreshComplete を呼んでも効かない模様?
(「更新中」状態のままとなってしまうみたいです。)

onPullDownToRefresh とかって非メインスレッドで呼ばれてるってこと
なんですかね?あ、「自分でソース確認しろ」とか言うの禁止ね。
 とりあえず↑みたいに AsyncTask の onPostExecute で呼び出してやると
期待する挙動になるみたいですが。。。


一応こんな感じで動きます。

【上から下に引っ張る】


【下から上に引き上げる】




どうでもいいけど、最近ホント夜更かしできなくなったなぁ…
昔はゲーム買ったら31時とかまで平気でやってたのになw
コレがアラサークオリティってやつか。

PullToRefreshで下からの引っ張りにも対応する(導入編)

最近(?)流行の ListView を下に引っ張ると更新する機能を
いざ組み込むべし、てことで。

ついでに上に引き上げた時もなんか動作つけたいなーとか思ったり。

ただ、コレって Android 標準ライブラリの機能じゃないんですな。
いくつかライブラリは公開されてますが PullToRefresh ってのが
一番ポピュラーっぽい。

それにしてもよく見る機能だから簡単に実装できるかと思ったら
意外とめんどいのね。


とりあえず今回は事前準備ということでライブラリの導入まで。
※そんなん言われんでもわかっとるわ、て方は次の回へどぞ。


0.前置き
「引っ張って更新」機能を使用するプロジェクトはもちろん
どんな名前でも構いませんが、とりあえずここでは P2RSample
しておきます。 そのへんは適宜読み替えてください。


1.ライブラリダウンロード
https://github.com/chrisbanes/Android-PullToRefresh
ココからダウンロードできます。
私みたいに Git 慣れてない方は↓から ZIP 取るのが良いかと。

解凍すると Android-PullToRefresh-master とかってフォルダができます。

※ちなみに、ほかに「PullToRefresh」でググると
 https://github.com/johannilsson/android-pulltorefresh
 とかも引っかかりますが、こっちは微妙に機能が違うみたいです。
 そっちを使っちゃうと次回「実装編」で「アレェッー!??」てなりますので
 ご注意を。


2.Eclipse にライブラリ追加
Eclipse の「ファイル」→「インポート」を選択。


で、Androidの既存コード追加を選択。
(「既存プロジェクトをワークスペースへ」でも問題なさそうですが。)


 追加するのは「Library」 フォルダだけでいいみたいっす。

※管理上、「プロジェクトをワークスペースにコピー」としておいた方が
 良いかと。 デスクトップとかにリンク貼られても困るし。


3.プロジェクトのプロパティで使用ライブラリ追加
P2RSample プロジェクトのプロパティからライブラリ追加の設定。


これで準備OKっす。

次回「実装編」はこのライブラリの機能を使って実装していきます。。。

2013年11月10日日曜日

AsyncTask の onPostExecute が呼ばれない件について

Android でネットワークに接続して情報を取得したりするときは
UIスレッド(メインスレッド)とは別のスレッドで処理を行う必要があります。

メインスレッドでネットワーク接続しようとすると
例外吐いてアプリが落ちやがります

※どうでもいいけど Android ってビルドでは何も言われないのに
 実行すると落ちるみたいなパターンけっこう多いような。。。


で、非同期でのネットワーク処理には AsyncTask クラスを使用するのが
最もポピュラーなやり方かと思います。

具体的な処理はGoogle先生にでもお尋ねすればいくらでも出てくるので
ざっくり省略しますが、だいたい↓こんな感じの処理となります。

public class MainActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  Button button = (Button)findViewById(R.id.button1);
  //ボタンを押したら非同期処理実行
  button.setOnClickListener(new OnClickListener(){
   @Override
   public void onClick(View arg0) {
    //非同期処理を実行させる
    new MyAsyncTask().execute();
   }
  });
 }

 //非同期処理を行うためのクラス
 private class MyAsyncTask extends AsyncTask<Void, Void, Void>{

  @Override
  protected Void doInBackground(Void... params) {
   //別スレッドで実行される非同期処理
   return null;
  }
  
  @Override
  protected void onPostExecute(Void result){
   //メインスレッドで実行される処理
   //(非同期処理の結果をUIに反映する等)
  }
 }
}



で本題。
タイトルにある通り、特定の条件において
非同期処理が終わった後に onPostExecute が呼ばれない
とかいう状況が発生します。

参考)onPostExecute not being called in AsyncTask

要は AsyncTask クラスのスタティックイニシャライザがメインスレッド以外で
実行された場合、結果的に onPostExecute がそのスレッド(非メインスレッド)に
紐付けられるようです。
※当然といえば当然ですが onPreExecute も同様。

という書き方をすると
「そもそも非メインスレッドから AsyncTask なんか呼ばねーよ m9(^Д^)プギャー」
とか思われるかもしれませんが、

↑のリンクにもある通り、
C2DM(プッシュ通知機能)なんかではわりとポピュラーに発生する、
てかサンプルコードがそれを実際にやってやがるみたいです

てか私自身もGCM(C2DMの後継)で見事にハマったワケで。

※GCMIntentService の onRegistered で受け取った regId を Web サービスに
 登録しようとして AsyncTask 使ったら見事にこの状態になったっぽい。
 その AsyncTask 自体はUIいじってるワケでもないのに。。。


回避策としてはアクティビティの onCreate メソッドとか、
・確実にUIスレッドで実行される箇所
・なるべく早い段階
で、例えば以下のようにして AsyncTask クラスに触れてやればいいみたいです。

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  //とりあえず一度 AsyncTask に触れてやる
  try{
   Class.forName("android.os.AsyncTask");
  }catch(ClassNotFoundException e){}

  //以下略
 }



…それにしても Android って標準クラスのバグがまだまだ多いみたいで。
1つのアプリ作るのに既に4つくらい標準クラスのバグに遭遇してるんですが
何すかこの大当たりっぷり。。。



一応↑の StackOverFlow の解説もざっと折りたたみで載せときますか。


2013年10月12日土曜日

Viewにborder的な罫線をつける方法

Android の View に枠線をつけるのはそれほど難しいことではありません。

■枠線を付けたい TextView のレイアウト(layout/text_layout.xml)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="12dp"
    android:orientation="vertical" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:text="どうも、適当なTextViewです"
        android:background="@drawable/simple_frame" />

</LinearLayout>

■枠線のレイアウト(drawable/simple_frame.xml)
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="#FFFFFF"/>
    <stroke android:width="2dp" android:color="#AAAAAA" />
</shape>




が、bottom だけ、とか top 以外、とか特定の方向に限定しようとすると
急激に面倒なことになります。

てか、直接それを実現できる方法は存在しないっぽいです。

html/css なら border 指定で簡単にできるのに…困ったもんです。


んじゃどうすんのよ、というと、

通常の枠線(4方向)もしくは色付き長方形を作成
 →ちょっとずらした長方形(背景色で塗りつぶし)を重ねる

てな指定のしかたで対応方法が一般的みたいです。

参照1)[Android]View に border-bottom をつける
参照2) Is there an easy way to add a border to the top and bottom of an Android View?

やろうとしてる事に対してのまわりくどさがハンパないですな。

■TextView のレイアウト
 →さっきのやつの background を @drawable/underline に変えるだけ

■枠線のレイアウト(drawable/underline.xml)
 <?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
    <!-- 枠線付き長方形 -->
    <item>
        <shape android:shape="rectangle">
            <solid android:color="#FFFFFF"/>
            <stroke android:width="2dp" android:color="#AAAAAA" />
        </shape>
    </item>

    <!-- 2dp分の padding を設定した枠線なし長方形 -->
    <item android:bottom="2dp">
        <shape android:shape="rectangle">
            <solid android:color="#FFFFFF"/>
        </shape>
    </item>
</layer-list>




んで、実はここからが本題。

何がしたかったかというと。

ListView 的なモノを作りたい。
→でも項目は固定だし項目数も少ない。
 →わざわざ ListView と Adapter 用意すんの面倒…
  →そのレベルなら TextView 縦に並べるだけで良くね?
   →そのまま並べると各 TextView の間に区切りがない…
    →border 的なの何かないかな?

…ここまでであれば前述の方法で問題ないワケですが、さらに

     →タップしたときに他の Activity に遷移する必要がある
      →タップしたときにアクションがあるなら色とか変えたいよね

こうなるのが人情ってもんです。


で、コレを実現しようとすると、さらに selector を組み合わせることになります。

■TextView のレイアウト
 →さっきのやつの background を @drawable/selectable_underline に変える
 →タップ動作を検出するため android:clickable="true" を追加

■枠線のレイアウト(drawable/selectable_underline.xml)
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- タップされたときの設定 -->
    <item android:state_pressed="true">
        <layer-list>
            <!-- 枠線付き長方形 -->
            <item>
                <shape android:shape="rectangle">
                    <solid android:color="#FFC0CB"/>
                    <stroke android:width="2dp" android:color="#AAAAAA" />
                </shape>
            </item>

            <!-- 2dp分の padding を設定した枠線なし長方形 -->
            <item android:bottom="2dp">
                <shape android:shape="rectangle">
                    <solid android:color="#FFC0CB"/>
                </shape>
            </item>
        </layer-list>
    </item>

    <!-- 通常時の設定 -->
    <item android:state_pressed="false">
        <layer-list>
            <!-- 枠線付き長方形 -->
            <item>
                <shape android:shape="rectangle">
                    <solid android:color="#FFFFFF"/>
                    <stroke android:width="2dp" android:color="#AAAAAA" />
                </shape>
            </item>

            <!-- 2dp分の padding を設定した枠線なし長方形 -->
            <item android:bottom="2dp">
                <shape android:shape="rectangle">
                    <solid android:color="#FFFFFF"/>
                </shape>
            </item>
        </layer-list>
    </item>

</selector>



ようやくそれっぽく動くようになったけど、コレ大人しく Adapter 実装した方が
早かったんじゃ…とか思ったり。

2013年9月1日日曜日

startActivityForResult で返却された結果(Intent)が反映されなくて大ハマリした件

Android で twitter 連携しようとしたときのはなし。

twitter4j を使ってるんですが、 twitter ってヤツは初回にアカウントの
認証が必要なんすね。

んで、認証の処理の流れは大体以下のような感じになってます。

  1. 認証オブジェクトを用意
  2. 認証オブジェクトとコールバック用URIから認証ページURL生成
  3. 認証ページを startActivityForResult で開く
  4. 認証ページで「連携アプリを認証」ボタンを押すと元のアプリに戻ってくる
  5. Intent にセットされている URI を取得
  6. URI にくっついてる verifier(文字列) を取得
  7. verifier とか使ってアクセストークンを取得
  8. 以降、各種 Twitter API を使うときはアクセストークンを使用
※細かいことは↓とか見ると吉。
 Android再入門 - Twitterクライアントを作ってみよう - OAuth認証

で、問題は3~5の部分。

3で startActivityForResult を呼び出した Activity と
5で Intent を受け取る Activity が異なる場合は問題ないんですが、

3と5が同じ Activity の場合(全く同じ画面に戻ってくる場合)に、
なぜか Intent に URI がセットされていない???


で、さんざんハマったワケですが、ようやく原因がわかりまして。

既に存在する画面に対して startActivity(Intent) を実行した場合、

 onNewIntent(Intent) → onResume()

の順に呼び出されるんですが、この onNewIntent のソースを見たところ、

protected void onNewIntent(Intent intent) {
}


・・・・・・何もしてねぇ!!


要は新しい intent に更新されない=初回 onCreate 時のままなんで
Intent に URI が存在しない、と。何この超絶トラップ。


しかもそういう仕様だとかしっかりソースにもコメント書いてやがります。

 Note that {@link #getIntent} still returns the original Intent.
 You can use {@link #setIntent} to update it to this new Intent. 

 →getIntent は相変わらず元の Intent 返すかんね。
  更新したかったら setIntent 使ってね。


てことで対応策としては Intent 受け取る Activityで onNewIntent を
オーバーライドするなりして setIntent 呼んでやりゃいいだけの話みたいですが。

@Override
protected void onNewIntent(Intent intent){
    setIntent(intent);
}


それにしてもなんでまたこんな仕様になってるんですかね??

察するに、「単に画面遷移で startActivity 呼ばれた時に更新かけちゃうと
元の intent の情報が消えちゃうから」とか??


ちなみに初回画面作成時は onNewIntent 自体呼ばれないんで、
常に intent の値をチェックするなら最低2箇所に処理書かないといかん、と。

うーん、なんとも初心者には釈然としない話ですなぁ…。
なんか真っ当な事情があっての仕様なのかしらん。