Karakuri.com

Fintechではたらくアプリケーションエンジニアの技術録

Android DatabindingとFragmentの組み合わせでLayoutInflater.inflater.inflaterメソッドでアプリクラッシュする

Android Databindingを使用してアプリ開発を行っているのですが、Fragmentを使った画面遷移でアプリクラッシュする問題に直面してしまいました。原因を特定するのに時間を使ってしまったので、記録として残しておきます。

発生したエラー内容

この問題に直面して数日経過してしまったため詳細は記憶が曖昧になってきているのですが、問題はActivityのButtonが押下されたときにFragmentの表示を切り替える場面で発生したと記憶しています。1枚目のFragmentはActivity表示時に一緒に描画されていたのですが、それを2枚目に切り替えようとしたときにクラッシュした感じですかね。クラッシュした箇所はFragmentのonCreateViewの中のLayoutInflater.inflater.inflaterメソッドだったと思います。

public static class ExampleFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.example_fragment, container, false);
    }
}

はい。分かる人は分かると思うのですが、Android DevelopersのFragmentのリファレンスにも記述されている自動生成のコードです(フラグメント | Android Developers)。そして、記憶が曖昧ですが下記のようなエラーメッセージが出てました。

Duplicate ID, tag null, or parent id with another fragment for Fragment.

良く分からないですけど、LayoutInflater.inflater.inflaterメソッドの第一引数であるR.layout.example_fragmentが重複しているようなのです。そんなことってあるんですかね…。対策をググってみると重複しているなら削除してしまえという対処療法は見つかるものの、根本的な解決策は見つからぬまま。うーんどうしたものか。

Databindingをやめたら問題が起きなかった

完全に詰み始めていたのですが、ふとDatabindingを使っていることを思い出します。Fragmentについて色々調べていたのですが、Databindingはまだ普及しているとは言えません。一旦環境を同じにしてみようと思ったんですね。現状はこんな感じのコードになっていたのですが、

public class ExampleActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(this, R.layout.activity_example);

        ViewModel viewModel = new ViewModel();

        ActivityExampleBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_example);
        binding.setViewModel(viewModel);
    }
}

Databindingに関するコードをコメントアウトしてみました。

public class ExampleActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(this, R.layout.activity_example);

        //ViewModel viewModel = new ViewModel();

        //ActivityExampleBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_example);
        //binding.setViewModel(viewModel);
    }
}

そしたら動きました。あれれーDatabindingとFragmentって相性悪いのかな、、それともバグがあるとか?うーん。

原因はsetContentViewの重複でした

その後も試行錯誤してみてようやく気が付きました。先ほどのActivityのonCreateメソッドでsetContentViewを2回呼び出していたんですね。

public class ExampleActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 1回目の呼び出し
        setContentView(this, R.layout.activity_example);

        ViewModel viewModel = new ViewModel();

        // 2回目の呼び出し
        ActivityExampleBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_example);
        binding.setViewModel(viewModel);
    }
}

機械的にDataBindingUtil.setContentViewを呼び出していたので気が付きませんでした。。2回呼び出しているのでR.layout.activity_exampleが重複登録されているんでしょうね。。だからDuplicate IDというエラーメッセージが出ていたのか…。
しかし今のところFragmentを使っていないところはsetContentViewとDataBindingUtil.setContentViewをonCreateで呼び出してもアプリは正常に動作しています。きっと無駄にメモリ使ったり描画したりしているんでしょうね。。これ全部のActivityのonCreateからsetContentViewを削除しないとなー。とりあえずFragmentが使えるようになって良かったー。