Androidアプリ＆Kotlinでハマった点の技術的原因と対応方法（１．Androidの画面遷移（フラグメント）処理）

# １．*Android*の画面遷移（フラグメント）処理
*Android*の初期バージョンではフラグメントの概念がなく、ファットアクティビティや端末回転時の制御に苦労したそうです。
本アプリでは、メインアクティビティから画面毎のフラグメント構成を入れ替えるようにし、*AndroidManifest.xml*の設定で端末回転を禁止にすることで制御を省略しました。
端末回転を禁止することで*UX*が損なわれる可能性としては、画像を回転させる処理を組み込むことで対応しました。（後で、ハマることになるとは…）

## １．１ 画面遷移後のイメージ画像を貼り付ける処理

### ＜背景＞
本アプリでは、2つのイメージ画像をシームレス合成するために1枚目では合成（**したい**）元の画像を選択して、2枚目では合成（**させたい**）先の画像を選択します。
（2つの画像の状態を*FragmentA*、*FragmentB*で管理する）
そのため、画像選択ボタンをタッチしてギャラリーの画像もしくはカメラ撮影を実施した後、画面遷移後にイメージ画像を貼り付ける処理を行います。

### ＜課題＞
画像選択ボタンをタッチしてギャラリーの画像もしくはカメラ撮影を実施した後、画面遷移先レイアウト上の*ImageView*にイメージ画像が表示されずに例外が発生します。

### ＜原因＞
*FragmentManager*のフラグメントトランザクション処理（画面遷移用にフラグメント構成を入れ替え（*replace()*）／*commit()*）直後に*onActivityResult()*で受け取ったギャラリーの画像もしくはカメラ撮影用*Chooser*の*Intent*を引数とした（*FragmentA*もしくは*FragmentB*へ実装した）関数をコールしてもトランザクションキューに積まれた状態です。
よって、*onCreateView()*が呼ばれずレイアウトが生成されないため、画面遷移先レイアウト上の*ImageView*にイメージ画像が表示されずに例外が発生していました。

### ＜対応＞
画面遷移先レイアウト上の*ImageView*にイメージ画像を表示するためには、トランザクションキューに積まれたフラグメントを即座に実行するため、*FragmentManager*のフラグメントトランザクション処理（*commit()*）直後に*executePendingTransactions()*をコールしてあげることで、*onCreateView()*が呼ばれレイアウトが生成されます。

```Kotlin:フラグメントトランザクション処理とexecutePendingTransactions()
// ..... 略
	FragmentA.newInstance().let { fragment ->
		fragmentManager?.beginTransaction().let { trans ->
			val fragments = fragmentManager?.fragments ?: return
			for (rFragment in fragments) {
				trans?.remove(rFragment)
			}
			trans?.replace(R.id.topContainer, fragment, FragmentA.TAG)
			trans?.commit()
			fragmentManager?.executePendingTransactions()
			fragment.setImageView(intent) // トランザクションキューを全て実行した後に呼び出す
		}
	}
// ..... 略
```

## １．２ 画面遷移後のシームレス画像合成中のアニメーション画像表示処理

### ＜背景＞
本アプリでは、2つのイメージ画像（合成（**したい**）元の画像と合成（**させたい**）先の画像）を選択、画面遷移後にイメージ画像をシームレス合成します。画像サイズによっては、合成時間に差異があるため、アニメーション画像表示で時間調整します。

### ＜課題＞
アニメーション画像表示中にKotlin*コルーチンの*launch*（別スレッド）上でイメージ画像のシームレス合成が完了するまでブロッキングした後、フラグメントトランザクション処理でシームレス合成結果画面へ遷移することができません。

### ＜原因＞
シームレス合成結果画面へ遷移するまでの制御でメイン（*UI*）スレッドに対するブロッキングのタイミングに誤りがあり、フラグメントトランザクション処理でシームレス合成結果画面へ遷移することができませんでした。

### ＜対応＞
アニメーション画像表示中に*Kotlin*コルーチンの*launch*（別スレッド）上でイメージ画像のシームレス合成が完了するまでブロッキングした後、*Activity#runOnUiThread*内でシームレス合成結果画面へ遷移されます。

```Kotlin:コルーチンのlaunchブロック処理
// ..... 略
	GlobalScope.launch { // Kotlin1.3対応
		launch { // GlobalScope省略可
			asyncFunc(引数).join()
		}.join()
		activity?.runOnUiThread {
			// フラグメントトランザクション処理
		}
	}
// ..... 略
```

```Kotlin:Suspend関数
// ..... 略
	private fun asyncFunc(引数): Job {
		return GlobalScope.launch { // Kotlin1.3対応
			seamlesscollage(引数)
		}
	}
// ..... 略
```

## １．３ シームレス合成結果画面上のバックスタック処理

### ＜背景＞
本アプリでは、シームレス合成結果画面で[ホーム]ボタンをタッチするとホーム（トップ）画面に遷移します。

### ＜課題＞
シームレス合成結果画面で[ホーム]ボタンをタッチしてバックスタック後、画像選択が初期化されません。

### ＜原因＞
バックスタック用にフラグメントトランザクション処理で*addToBackStack()*をコールしていたため、シームレス合成結果画面で[ホーム]ボタンをタッチ（*popBackStack()*をコール）すると、フラグメントトランザクションを利用した回数とバックスタックにあるエントリ数が一致しないので、画面遷移の状態などのステータスが初期化されませんでした。

### ＜対応＞
*addToBackStack()/popBackStack()*では画面遷移の状態などのステータスが引き渡せないため、[ホーム]ボタンをタッチする場合は、フラグメントトランザクション処理（*replace()*）で画面遷移の状態などのステータスが初期化されます。

```Kotlin:onViewCreated()と[ホーム]ボタン処理
// ..... 略
	override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
		super.onViewCreated(view, savedInstanceState)
// ..... 略
		homeButton.setOnClickListener {
			backHome()
		}
	}

	private fun backHome() {
// ..... 略
		replaceTopFragment()
	}
// ..... 略
```

```Kotlin:フラグメントトランザクション処理（replace()）
// ..... 略
	private fun replaceTopFragment() {
		TopFragment.newInstance().let { fragment ->
			fragment.arguments?.putInt(TopFragment.FLG_VIEW, 0)
			fragmentManager?.beginTransaction().let { trans ->
				trans?.replace(R.id.topContainer, fragment, TopFragment.TAG)
				trans?.commit()
			}
		}
	}
// ..... 略
```

## リンク
[初めてのAndroidアプリ＆Kotlinでハマった点の技術的原因と対応方法](https://qiita.com/schenlybiz/items/24d97c10f62c55334f6a)
[２．Androidのカメラ機能に関する処理](https://qiita.com/schenlybiz/items/65e60ed39331e494ee23)
[３．Androidの画像に関する処理](https://qiita.com/schenlybiz/items/b72c2106d7eadf2c1582)