【android】ActivityからActivityの画面遷移

こんにちは。
2021年の年末はAndroid Studioをいじくっていました。完全に素人からのスタートで、触って３日くらいです。
ただ、私は一応、javaについては足し算引き算できるくらいには書けますので、
つまづく所は主にAndroidならではの要素の部分でして、つまりは、画面の表示だったり画面遷移の話だったりするわけです。

#### 環境

android studio APIレベル32 (java)

# ActivityからActivityの画面遷移

そもそもActivityで画面遷移すべきかどうか、という話があるようです。~~調べていると情報が錯綜する！~~
おそらくはActivityは「シーン」で１つだけあれば良く「その中」での切り替えはFragmentを使うというのが、”たぶん”正しそうです。
ただ私がざっと調べた所「Fragmentはあくまで画面表示パーツであって画面そのものではない」理解に行きました。画面の一部を書き換えるイメージ。で、画面の大部分を置き換えればそれは実質画面の切り替えになる、とかそういう。（そして大画面のタブレットなら両方並べても良いじゃない。ってことかな）

今回はActivity間遷移を扱います。

## 要約

- 行ったきりでいいなら`startActivity`を使う
- 行って戻ってきたいなら`startActivityForResult`が**今の所**使える
- 引数や戻り値はIntentに入れて渡す。文字列・数値ほか、Serializableであるクラスのインスタンスが渡せる

## 行ったきりでよいActivityの遷移

MainActivityからNextActivityに移動して、戻るボタンで戻るかもしれないけど、MainActivityはそれを知らなくても良いし戻り値も不要だって場合。タイトルからメイン画面への遷移とか、あるいは閉じるときは戻るボタンで良いAbout画面とか。設定画面なら共有の設定に保存して閉じれば良いとか、そういう感じのものですね。

内容自体はそのまんまチュートリアルのコレです。ただこのサンプルでは文字列しか渡していません。

https://developer.android.com/training/basics/firstapp/starting-activity?hl=ja

**MainActivity**

~~~java
/** 別のアクティビティに遷移する */
private void MoveGamen(){
    Intent intent = new Intent(this.getApplicationContext(), NextActivity.class)
    intent.putExtra("Param", paramObject);
    startActivity(intent);
}
~~~
Intentを作成して、引数を格納したら`startActivity()`します。

Intentの`putExtra()`で引数を格納できます。文字列や数値なら大概のものが入りますが、任意のクラスのオブジェクトを渡すのであればそれはSerializable(後述)である必要があります。

* Intentは「何かを行うという意図」を表わすものと書かれていますけど、ここでは引数などの次のActivityで必要なモノを一つに梱包したもの、的な感じですかね？

**NextActibity**

~~~java
    protected void onCreate(Bundle savedInstanceState) {
        // 通常の処理がこのへんに //
        ParamData paramData = (ParamData) getIntent().getSerializableExtra("Param");
    }
~~~
渡した引数は、Intentから`getSerializableExtra`で取得できます。
どこででも出来そうですけど`onCreate()`内で取得してフィールド変数に持っておけばよいのではないかと。
ちなみに、引数の`"Param"`は文字列。MainActivityとNextActibityで同じ名称で渡しあえるなら何でも良いようです。
どこかで定義しておくほうが行儀は良いでしょう。

* ちなみに、Nextから別のActivityを呼びたいなら、同じことをすればよく、閉じたい場合は`finish`です。


## 行って戻ってきたい場合のActivityの遷移

MainActivityからSubActivityに遷移するが、その後Mainに戻ってくる。たとえば、一覧画面から詳細画面を表示して一覧画面に戻りたい場合ですね。「行ったきり」で詳細から一覧に戻ることもできますがそれだと「戻る」履歴に全部残りますし詳細で変更したものを一覧に戻すのが結構面倒くさい。

**MainActivity①**

~~~java
    private static final int REQUEST_SUB = 1;

    /** 別のアクティビティに遷移する */
    private void MoveGamen(){
        Intent intent = new Intent(this.getApplicationContext(), SubActivity.class)
        intent.putExtra("Param", paramObject);
        startActivityForResult(intent, REQUEST_SUB);
    }
~~~

Intentを作成して、引数を格納するまでは同じですが、`startActivity`の代わりに`startActivityForResult`を**今のところ**使います。が、最近`startActivityForResult`は非推奨となりました。
まだ動きますし、代替コードについてもベストプラクティスはまだ無さそう、とのことなんで旧仕様でいきます。

なお、`REQUEST_SUB`というのは識別の数値です。後述。

**SubActibity①**

~~~java
    protected void onCreate(Bundle savedInstanceState) {
        // 通常の処理がこのへんに //
        ParamData paramData = (ParamData) getIntent().getSerializableExtra("Param");
	}
~~~

Subの方の受け取り方は「行ったきり」と同じです。重点は戻り方ですね。

**SubActivity②**

~~~java
    /** 元のアクティビティに戻る */
    private void BackGamen(){
        Intent intent = new Intent();
        intent.putExtra("Ret", resObj);
        setResult(RESULT_OK, intent);
        finish();
    }
~~~

`finish`をするとActivityが終了するわけですが、その前に呼び出し元に終了することを告げます。
Intentを作成して、戻り値を格納します。もちろん、戻り値がオブジェクトなら`Serializable`である必要があります。
（Intent作成時の引数が省略されているのはちょっと分かりません；）

**MainActivity②**

~~~java
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case (REQUEST_SUB):
                if (resultCode == RESULT_OK) {
                    Log.d("myApp","Return OK");
                    ResultData resObj = (ResultData) data.getSerializableExtra("Ret");
                      // 処理などを実装 //
                } else if (resultCode == RESULT_CANCELED) {
                    Log.d("myApp","Return Cancel");
                }
                break;
            default:
                break;
        }
    }
~~~
その後、終了が告げられたMainActivityがそれに対応します。
この仕組み自体はActivityの親クラスの方で実装されているようなので、こちらはOverrideするだけです。

`onActivityResult`です。

`requestCode`は、最初に`startActivityForResult`した時に付けた識別の数値（ここでは`REQUEST_SUB`）です。この数値、SubActibityの方ではそれを弄る機会が無かった（可能かどうかは別として）ので、基本的にMainActibityが送り出した時に預けた数値がそのままで帰ってくるわけですね。よってこれを見れば「どこのActivityが終了したのか」を判別できます。

`resultCode`はSubActibityの方が`setResult`した値です。送り出した後どうだったかを向こう側で回答を入れてきます。OKならIntentからデータを取り出して更新処理などを行います。

なお、`RESULT_OK`、`RESULT_CANCELED`はActivityの親クラスかどこかで定義されているのでMainやSubで宣言する必要はありません。同じ値で持っています。

## Serializableについて

やり方自体は簡単でデータ受け渡し用のモデルクラスの宣言に`implements Serializable`を書くだけです。

~~~java
public class ParamData implements Serializable {/*‥‥*/}

public class ResultData implements Serializable {/*‥‥*/}
~~~

それだけだとアレなんで説明しておきますと、`implements Serializable`記述を付けると、javaはこのクラスがシリアライズ可能だと判断します。判断のために必要なので、それためのメソッド実装は必要ありません。

また、シリアライズを雑に言うと、オブジェクトをバイト文字列にする機能です。これにより実体化、保存、復元が容易になります。詳細は調べてください。

で、なぜこれが必要かと言いますと、
ActivityやFragmentの移動間については参照渡しが基本的にできない、ということで実体化して複製を渡すために必要なようです。
Activityはそれぞれが別の状態を持っており、状況に応じて生きていたり・死んでいたり・再作成されたりするので、同じオブジェクトを複数のActivityで共有するのは危ないからですね。‥‥たぶん。
（よってIntentに一回入れたオブジェクトは元のオブジェクトとは独立しています）

なお、必要性があってどうしてもグローバル変数が必要なら`android.app.Application`に追加する形で持てるようです。

## startActivityForResultは非推奨

ちなみに非推奨の件ですが、

https://developer.android.com/training/basics/intents/result?hl=ja

> 基盤となる startActivityForResult() API と onActivityResult() API はあらゆる API レベルの Activity クラスで使用できますが、AndroidX Activity と Fragment で導入された Activity Result API を使用することを強くおすすめします。

とのことです。
Fragmentは正直まだ把握できていないのでまた何か見えたら書きます。

----
初心者向けの本やメンターの解説サイトを見ると、大抵が遷移することだけ、そのサンプルを作ることのみに特化した感じのことしか書かれておらず、引数や戻り値の受け渡しといった汎用的なことがあまり無かったので、調べた事を纏めておきます。

----
独学のため正確でない可能性があります。
（っ・ｘ・）っ きゅ

#### 参考
- https://developer.android.com/training/basics/firstapp/starting-activity?hl=ja
- https://developer.android.com/training/basics/intents/result?hl=ja