findFragmentById が必ず null を返す

Androidでプログラミングをすることになったので、「初歩からわかるAndroid最新プログラミング・増補改訂版」の電子書籍をを達人出版会から購入して勉強中。この本、昨年の12月頃に改訂されて出たものだけど、既に情報が古くなってしまっているようす。とはいえ、ネットを検索しても最新の日本語の情報を簡単には見つけられなかったので、致し方ないよね。Eclipseというか、ADTのバージョンは本で解説しているバージョンと合わせないとつまずきやすいかも。自分は ADT の v22.6.2-1085508 をダウンロードしてしまったので、つまずいてしまった。この本がターゲットとしているのは v22.3 らしい。

最初につまずいたのは新規にプロジェクトを作成したところ。編集エリアには activity_main.xml ではなくて fragment_main.xml が表示されるんだよね。どうやら新しい ADT では activity_main.xml の他に fragment_main.xml というレイアウト XML ファイルが生成されて、ユーザインタフェースなどはここに配置するようになったらしい。まあここら辺はちょっと読み替えればいいだけなのだけれども。「2-2 カウンターアプリケーションの作成」のところで説明されている自動生成された MainActivity.java が、本の内容とは微妙に違っている。
ただ、ADT の仕組みの方も fragment_main.xml の扱いに迷いがあるのではないかとも思う。fragment_main.xml にボタンを配置して、そのプロパティにイベントを受け取るメソッドの名称を書いたところ、fragment_main.xml に対応するクラスではなくて、activity_main.xml に対応するクラスのメソッドが呼ばれる。なんか直感とは違うんですけど。fragment_main.xml に対応するクラスは、UI 要素を持つだけということか。もっとも、フラグメントは複数のアクティビティで共有できるようなので、クリックイベントがアクティビティの方に飛ぶのもわからないでもない。
それはさておき、この章ではボタンがクリックされた時に、テキストビューの内容を変更するようなプログラムを作っている。ということは、fragment_main に対応するためには、そのクラスからテキストビューのオブジェクトを取り出さなければならない。それにはまず findFragmentById() でフラグメントを取り出し、getView() で取り出したルートビューから findViewById() でテキストビューを探し出せばよいということになる。
ところが、なぜか findFragmentById(R.layout.fragment_main) がどうやっても null しか返さない。
よくよく調べると、xml ファイルのどこにも fragment_main の id を定義している所がないのである。
そこで、FragmentManager#add() しているところで、タグを定義するようにして、 findFragmentByTag() で検索するようにしてみた。findFragmentByTag() するところも onCreate() ではうまくいかず、onStart() で呼び出すようにしたところうまくいった。

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

        if (savedInstanceState == null) {
            getFragmentManager().beginTransaction()
                    .add(R.id.container, new PlaceholderFragment(), "fragment_main")
                    .commit();
        }
        counter = 0;
    }
    
    @Override
    protected void onStart()
    {
         super.onStart();
         if (counterTextView == null) {
             counterTextView = (TextView)getFragmentManager()
                  .findFragmentByTag("fragment_main")
                  .getView()
                  .findViewById(R.id.counterTextView);
            counterTextView.setText(String.format("%d", counter));
        }
    }

しかし、xml でフラグメントの id を定義する方法はないものか。ためしに、activity_main.xml のプレビュー画面に Pallete の Layouts/Fragment をドロップしてみたら、MainActivity.java に自動生成された PlaceholderFragment クラスを選択できた。このプロパティを @id/fragment_main に変更して、以下のコードで動作するもよう。

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

        if (savedInstanceState == null) {
//            getFragmentManager().beginTransaction()
//                    .add(R.id.container, new PlaceholderFragment())
//                    .commit();
             counter = 0;
             counterTextView = (TextView)getFragmentManager()
                  .findFragmentById(R.id.fragment_main)
                  .getView()
                  .findViewById(R.id.counterTextView);
            counterTextView.setText(String.format("%d", counter));
        }
    }

// @Override
// protected void onStart() { ... }

ちなみに、コードを書けば fragment_main.xml に対応するクラスの方でクリックイベントを受け取るようにすることも出来るようだ。