# 【Android】Navigationで戻ってきたことを検知する方法【Navigation】

# この記事でやること

AndroidのNavigationでFragmentの画面遷移を作ったときに、戻ってきたことを検知する方法が初見ではわからなかったので、ここにメモします。

↓ やりたいことのイメージはこんな感じになります。

Navigation

FirstFragment → SecondFragmentのときは「はじめまして。」と表示され、SecondFragment ← ThirdFragmentのときは「ただいま。」と表示されるように、戻ってきたかどうかを判定して文言を変えています。

おどけミミッミ

# 環境

以下の環境で動かしています。

  • Android Studio 4.0
  • Navigation 2.3.0

# ざっくり言うと

結論から言うと「popBackStack() を使うな。 戻る際にも navigate() を使え。」です。
戻る際に単に this.findNavController().popBackStack() で戻るのではなく、戻るとき用の遷移アクションを定義します。
そうすると、戻る際に前のFragmentを再生成させ、そのときに新たに引数を与えてやることができます。

# とりまコード見て

NavGraphのコードはこんな感じです。

my_nav_graph.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/my_nav_graph"
    app:startDestination="@id/firstFragment">

    <fragment
        android:id="@+id/firstFragment"
        android:name="net.aridai.myapp.FirstFragment"
        android:label="FirstFragment"
        tools:layout="@layout/first_fragment">

        <argument
            android:name="saishonoparam"
            app:argType="string"
            app:nullable="false" />

        <action
            android:id="@+id/action_firstFragment_to_secondFragment"
            app:destination="@id/secondFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim" />

    </fragment>

    <fragment
        android:id="@+id/secondFragment"
        android:name="net.aridai.myapp.SecondFragment"
        android:label="SecondFragment"
        tools:layout="@layout/second_fragment">

        <argument
            android:name="modottekitaka"
            android:defaultValue="false"
            app:argType="boolean"
            app:nullable="false" />

        <action
            android:id="@+id/action_secondFragment_to_thirdFragment"
            app:destination="@id/thirdFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim" />

        <action
            android:id="@+id/action_secondFragment_to_firstFragment"
            app:destination="@id/firstFragment"
            app:enterAnim="@anim/nav_default_pop_enter_anim"
            app:exitAnim="@anim/nav_default_pop_exit_anim"
            app:popUpTo="@layout/first_fragment"
            app:popUpToInclusive="true" />

    </fragment>

    <fragment
        android:id="@+id/thirdFragment"
        android:name="net.aridai.myapp.ThirdFragment"
        android:label="ThirdFragment"
        tools:layout="@layout/third_fragment">

        <action
            android:id="@+id/action_thirdFragment_to_secondFragment"
            app:destination="@id/secondFragment"
            app:enterAnim="@anim/nav_default_pop_enter_anim"
            app:exitAnim="@anim/nav_default_pop_exit_anim"
            app:popUpTo="@layout/second_fragment"
            app:popUpToInclusive="true" />

    </fragment>

</navigation>

SecondFragmentの定義の中で modottekitaka (戻ってきたか) という引数を定義しています。

<argument
    android:name="modottekitaka"
    android:defaultValue="false"
    app:argType="boolean"
    app:nullable="false" />

FirstFragmentからSecondFragmentに遷移するコードはこうです。

(FirstFragment.kt)

this.button1.setOnClickListener {
    val directions = FirstFragmentDirections.actionFirstFragmentToSecondFragment()
    this.findNavController().navigate(directions)
}

上の modottekitaka 引数の定義から分かるように、デフォルトの false の値を与えています。

ThirdFragmentからSecondFragmentに戻るコードはこうです。

(ThirdFragment.kt)

this.button1.setOnClickListener {
    val directions = ThirdFragmentDirections.actionThirdFragmentToSecondFragment(modottekitaka = true)
    this.findNavController().navigate(directions)
}

modottekitaka = true を与えて this.findNavController().navigate(directions) で遷移しています。
this.findNavController().popBackStack() じゃあないですよ。

そんでSecondFragmentでは SecondFragmentArgs#modottekitaka を見て文言を変えてます。

//  (省略)

private val args: SecondFragmentArgs by this.navArgs()

//  (省略)

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    this.text.text = when (this.args.modottekitaka) {
        true -> "「ただいま。」"
        false -> "「はじめまして。」"
    }
    //  (省略)
}

以上です。

# 他の部分のコードも置いておくよ

# MainActivityのコード

main_activity.xml

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

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appBarLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:title="@string/app_name" />

    </com.google.android.material.appbar.AppBarLayout>

    <fragment
        android:id="@+id/navHostFragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="false"
        tools:ignore="FragmentTagUsage" />

</LinearLayout>

MainActivity.kt

package net.aridai.myapp

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController

internal class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        this.setContentView(R.layout.main_activity)

        val args = FirstFragmentArgs(saishonoparam = "お腹すいたなぁ〜")
        this.findNavController(R.id.navHostFragment).setGraph(R.navigation.my_nav_graph, args.toBundle())
    }
}

# 1つ目のFragmentのコード

first_fragment.xml

<?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:gravity="center"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="FirstFragment"
        android:textColor="#000000"
        android:textSize="20sp"
        tools:ignore="HardcodedText" />

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="テキスト"
        android:textColor="#000000"
        android:textSize="18sp"
        tools:ignore="HardcodedText" />

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text=""
        android:textColor="#000000"
        android:textSize="20sp"
        tools:ignore="HardcodedText" />

</LinearLayout>

FirstFragment.kt

package net.aridai.myapp

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import kotlinx.android.synthetic.main.first_fragment.*

internal class FirstFragment : Fragment() {

    private val args: FirstFragmentArgs by this.navArgs()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
        inflater.inflate(R.layout.first_fragment, container, false)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        this.text.text = this.args.saishonoparam

        this.button1.setOnClickListener {
            val directions = FirstFragmentDirections.actionFirstFragmentToSecondFragment()
            this.findNavController().navigate(directions)
        }
    }
}

# 2つ目のFragmentのコード

second_fragment.xml

<?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:gravity="center"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="SecondFragment"
        android:textColor="#000000"
        android:textSize="20sp"
        tools:ignore="HardcodedText" />

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="テキスト"
        android:textColor="#000000"
        android:textSize="18sp"
        tools:ignore="HardcodedText" />

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text=""
        android:textColor="#000000"
        android:textSize="20sp"
        tools:ignore="HardcodedText" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text=""
        android:textColor="#000000"
        android:textSize="20sp"
        tools:ignore="HardcodedText" />

</LinearLayout>

SecondFragment.kt

package net.aridai.myapp

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import kotlinx.android.synthetic.main.second_fragment.*

internal class SecondFragment : Fragment() {

    private val args: SecondFragmentArgs by this.navArgs()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
        inflater.inflate(R.layout.second_fragment, container, false)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        this.text.text = when (this.args.modottekitaka) {
            true -> "「ただいま。」"
            false -> "「はじめまして。」"
        }

        this.button1.setOnClickListener {
            val directions = SecondFragmentDirections.actionSecondFragmentToThirdFragment()
            this.findNavController().navigate(directions)
        }

        this.button2.setOnClickListener {
            val directions = SecondFragmentDirections.actionSecondFragmentToFirstFragment(saishonoparam = "唐揚げ食べたくない?")
            this.findNavController().navigate(directions)
        }
    }
}

# 3つ目のFragmentのコード

third_fragment.xml

<?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:gravity="center"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="ThirdFragment"
        android:textColor="#000000"
        android:textSize="20sp"
        tools:ignore="HardcodedText" />

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="←"
        android:textColor="#000000"
        android:textSize="20sp"
        tools:ignore="HardcodedText" />

</LinearLayout>

ThirdFragment.kt

package net.aridai.myapp

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import kotlinx.android.synthetic.main.third_fragment.*

internal class ThirdFragment : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
        inflater.inflate(R.layout.third_fragment, container, false)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        this.button1.setOnClickListener {
            val directions = ThirdFragmentDirections.actionThirdFragmentToSecondFragment(modottekitaka = true)
            this.findNavController().navigate(directions)
        }
    }
}