【Android】Fragment内で同一Fragmentを表示する

近年まれに見るハマり方をしてしまったのでメモしておきます。

やろうとしていたこと

MainActivityでTimelineFragmentを表示→TimelineFragmentの要素をクリックするとTimelineMenuDialogFragmentを表示→TimelineMenuDialogFragmentのConversationをクリックするとin-reply-toを辿るConversationDialogFragmentを表示→in-reply-toの流れをまとめた要素をConversationDialogFragment内にTimelineFragmentとして表示させたい

わかりにくいですね。こんな感じです。

MainActivity
  └TimelineFragment
    └TimelineMenuDialogFragment
      └ConversationDialogFragment←イマココ!
        └TimelineFragment

色々あがいてみたこと

最初はConversationDialogFragmentでinflateするxmlにfragmentを持たせていました。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <fragment
        android:name="inujini_.nuechin.test.TimelineFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/frgTimeline"/>
</LinearLayout>

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

しかし、inflateする瞬間にIllegalArgumentExceptionが出てしまいました。

E/AndroidRuntime(3605): Caused by: java.lang.IllegalArgumentException: Binary XML file line #7: Duplicate id 0x7f0a001c, tag null, or parent id 0x0 with another fragment for inujini_.nuechin.test.ConversationDialogFragment

タグを入れてみりゃいいのか?とか、リソースIDが変わればいいのか?とか、色々やってみたんですが、どうにもなりませんでした

次にFragmentManagerによる動的生成を試みました。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:id="@+id/lnrConversation">
</LinearLayout>

@Override
public void onActivityCreated(Bundle arg) {
	super.onActivityCreated(arg);
	getDialog().setTitle("Conversation");
	FragmentManager m = getFragmentManager();
	FragmentTransaction t = m.beginTransaction();
	TimelineFragment tf = new TimelineFragment();
	t.add(R.id.lnrConversation, tf);

	t.commit();
}

が、これもダメ。

E/AndroidRuntime(24084): java.lang.IllegalArgumentException: No view found for id 0x7f0a001c (inujini_.nuechin.test:id/lnrConversation) for fragment TimelineFragment{4205ecf0 #6 id=0x7f0a001c}

なんか見つかってないみたいです。onActivityCreatedなので既にinflateされたViewが渡っているはずなんですが…。

getChildFragmentManagerを使う

仕方ないので色々調べてみたらStack Overflowで全く同じような事象に悩んでいる人がいました。こんだけ投票されてるし何かベストな解決法があるんでしょう。

AFAIK, fragments cannot hold other fragments.
(私の知る限り、Fragmentは他のFragmentを持つことは出来ない)

ないのかよ!

と思ったらAndroid 4.2からちゃんとそれ用のメソッドが追加されたことが書かれています。Support Packageにも導入されているようです。やったぜ。

結論としては、getFragmentManagerではなくgetChildFragmentManagerを使えばOKとのこと。っつーかリファレンスにもまんまのことが書いてあるわ。一次文献は大事ですね…。ほんとにね…。

そんなわけで先ほどの動的生成部分を書き換えます。

@Override
public void onActivityCreated(Bundle arg) {
	super.onActivityCreated(arg);
	getDialog().setTitle("Conversation");
	FragmentManager m = getChildFragmentManager();
	FragmentTransaction t = m.beginTransaction();
	TimelineFragment tf = new TimelineFragment();
	t.add(R.id.lnrConversation, tf);

	t.commit();
}

わかってしまえば単純で、たったこれだけの修正で思い通りに動いてくれました。うぐぐ。

まとめ

リファレンスはちゃんと読みましょうと言うお話でした。