[Android]FragmentFactoryで引数付きコンストラクタを安全に使用する

![test.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3639815/9549a545-1dc0-4a78-47f4-3a22b7bfde9d.gif)


Android開発において、`Fragment`は画面の構成要素を分離し、再利用性を高めるために重要な役割を果たします。通常、`Fragment`はデフォルトコンストラクタを使用する必要がありますが、開発プロセス中にデータをFragmentに渡すために引数付きのコンストラクタを使用する場合があります。

しかし、引数付きのコンストラクタを使用すると、`構成変更（Configuration Change）`やシステム再生成時に問題が発生することがあります。本記事では、`FragmentFactory`を活用してこれらの問題を解決し、安定的に引数付きコンストラクタを使用する方法を解説します。



## 📌 問題の定義


#### 引数付きコンストラクタを使用するFragmentの問題点

##### 1. Androidシステムの基本動作:

- システムはFragmentを再生成する際、デフォルトコンストラクタを呼び出します。
- 引数付きコンストラクタが存在する場合、システムがこれを認識できず、`InstantiationException`が発生します。

##### 2. エラーログ
  
    ```
    java.lang.RuntimeException: Unable to start activity ComponentInfo
    Caused by: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment
    ```
	
- このエラーはデフォルトコンストラクタがない場合に発生し、フォントサイズの変更や画面回転などの構成変更時にアプリがクラッシュします。

##### 3.なぜデフォルトコンストラクタを使用しなければならないのか?

- Androidシステムは`構成変更（Configuration Changes）`が発生した際、Fragmentを再生成します。この時、必ず引数のないデフォルトコンストラクタが必要となります。しかし現在の実装には引数付きコンストラクタしか存在せず、システムがFragmentを再生成できない状況です。

- Android公式ドキュメントにもその理由が記載されています。 
以下の内容が該当します。
https://developer.android.com/reference/android/app/Fragment

  > All subclasses of Fragment must include a public no-argument constructor. The framework will often re-instantiate a fragment class when needed, in particular during state restore, and needs to be able to find this constructor to instantiate it. If the no-argument constructor is not available, a runtime exception will occur in some cases during state restore.
  > 



## 📌 解決策: FragmentFactoryの使用

### 1. FragmentFactoryとは？
- `FragmentFactory`は、`Fragment`の生成プロセスをカスタマイズできるクラスです。
- 引数付きコンストラクタを安全に使用できるようサポートし、依存性注入やデータの受け渡しを簡単に実現できます。


### 2. 問題点

```kotlin
class HomeFragment(private val message: String) : Fragment() {
    // エラー発生: デフォルトコンストラクタがないためシステム再生成不可
}
```

### 3. FragmentFactoryの設定

```kotlin
class HomeFragmentFactory(private val message: String) : FragmentFactory() {
    override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
        return when (className) {
            HomeFragment::class.java.name -> HomeFragment(message)
            else -> super.instantiate(classLoader, className)
        }
    }
}
```

### 4. Fragmentの設定

```kotlin
class HomeFragment(private val message: String) : Fragment() {

    private lateinit var binding: FragmentHomeBinding

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentHomeBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // メッセージ表示
        binding.textView.text = message
    }

}
```

### 5. MainActivityでFragmentFactoryを設定

```kotlin
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        supportFragmentManager.fragmentFactory = HomeFragmentFactory("Test")
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}
```

## 📌 FragmentFactoryと従来の方法の比較


| 項目             | Bundle使用方法                       | FragmentFactory方式                       |
|------------------|-------------------------------------|---------------------------------------|
| データの受け渡し      | argumentsを使用しシリアライズ可能なデータを渡す     | コンストラクタを通じてシリアライズ不要なデータを渡す       |
| 構成変更への対応    | 状態の保存と復元が必要                  | コンストラクタを通じたデータ受け渡しで状態を復元可能          |
| 依存性注入      | 自己実装が必要                           | 簡単に実装可能                          |
| 使用ケース        | 単純なデータの受け渡し                         | 複雑なデータや依存性の受け渡し                  |


## 📌 FragmentFactory使用のメリットとデメリット

### メリット
1. **引数付きコンストラクタのサポート**:
   - システム再生成時にも引数付きコンストラクタを使用可能

2. **柔軟なデータの受け渡し**:
   - シリアライズ不要なオブジェクトや依存性を注入可能

3. **テストの容易性**:
   - テスト時、様々なデータを持つFragmentを簡単に生成可能


### デメリット
1. **複雑性の増加**:
   - `FragmentFactory`を追加で作成するため、コードが多少複雑になる可能性あり。

2. **既存コードとの互換性の問題**:
   - 既存プロジェクトのFragment管理方法を変更する必要がある場合がある。


## 📌 まとめ

`FragmentFactory`を使用することで、Androidシステムのデフォルトコンストラクタ要件を回避し、引数を持つコンストラクタを安全に使用できます。 これは構成変更やシステム再生成時に発生し得るクラッシュ問題を効果的に解決します。

#### 使用すべきケース:

- データの受け渡しが簡単な場合、`arguments`を使用する方が簡単かつ適切です。
- しかし、複雑なデータの受け渡し、依存性注入、またはテスト環境の構築が必要な場合、`FragmentFactory`を使用することが強力な選択肢となります。

---

GitHub : https://github.com/GEUN-TAE-KIM/Constructors_With_FragmentFactory_Sample

---

参考
https://developer.android.com/reference/android/app/Fragment
https://developer.android.com/guide/topics/resources/runtime-changes?hl=ja