#SafeArgsとは
画面間のデータの受け渡しをタイプセーフに行うためのものです。
#対象者
既存のアプリにNavigationを使用しての画面遷移の導入は難しいけどタイプセーフに画面間でデータの受け渡しがしたい!
##環境
MacBookPro Catalina
Android Studio ver.4.1.2
Kotlin ver.1.4.30
#導入
project配下のbuild.gradleのdependenciesに以下を追加します。
def nav_version = "2.3.3"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
続いてapp/build.gradleに以下を追加します。
id 'androidx.navigation.safeargs.kotlin'
implementation "android.arch.navigation:navigation-fragment-ktx:1.0.0"
implementation "android.arch.navigation:navigation-ui-ktx:1.0.0"
#実際に使ってみる
###リソースの追加
まずはリソースにnavigation用ディレクトリを追加します。
次に作成したディレクトリにNavigationResourceFileを適当な名前をつけて追加します(MainActivityで使用するのでmainにした)。
##Fragmentにデータを渡してみる
###準備
main.xmlの中身を以下のようにして文字列を渡してみます。
構成はActivity+Fragmentです。
argumentの箇所でnameで変数名とargTypeで型を指定します。
<?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">
<fragment
android:name="com.godslew.navigationsample.ui.main.MainFragment">
<argument
android:name="title"
app:argType="string" />
</fragment>
</navigation>
ビルドをするとMainFragmentArgsというものが自動生成されます。
main.xmlで指定したものを格納できるようになっていますね。
###使用する
まずはActivityでMainFragmentを生成する時に文字列を渡します。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, MainFragment.newInstance("Game"))
.commitNow()
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" />
Activityから渡された文字列をargumentsに入れておきます。
取り出すには 以下のコードを書けばOKです。
private val args: MainFragmentArgs by navArgs()
class MainFragment : Fragment() {
companion object {
fun newInstance(title: String) = MainFragment().apply {
arguments = MainFragmentArgs(title).toBundle()
}
}
private val args: MainFragmentArgs by navArgs()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.main_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<TextView>(R.id.message).text = args.title
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.main.MainFragment">
<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
#独自クラスを使用する
実際は単純なデータじゃないものを渡したい場合が多いと思うので紹介します。
selaed classを渡してみたいと思います。
他にも渡せるデータの種類はあります。1
##準備
Parcelableを使いたいのでapp/build.gradleに以下を追加します。
id 'kotlin-parcelize'
渡すためのsealed classを作ります。
sealed class Title : Parcelable {
abstract val name: String
@Parcelize
data class Game(
override val name: String = "Game"
) : Title()
@Parcelize
data class Tv(
override val name: String = "TV Show"
) : Title()
@Parcelize
data class Movie(
override val name: String = "Movie"
) : Title()
}
##渡してみる
main.xmlを以下のように書き換えます。
argTypeに先ほど作成したsealed classを指定します。
<?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">
<fragment
android:name="com.godslew.navigationsample.ui.main.MainFragment">
<argument
android:name="title"
app:argType="com.godslew.navigationsample.ui.main.Title" />
</fragment>
</navigation>
MainAcivityとMainFragmentを以下のように書き換えます
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, MainFragment.newInstance(Title.Game()))
.commitNow()
}
}
}
class MainFragment : Fragment() {
companion object {
fun newInstance(title: Title) = MainFragment().apply {
arguments = MainFragmentArgs(title).toBundle()
}
}
private val args: MainFragmentArgs by navArgs()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.main_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<TextView>(R.id.message).text = args.title.name
}
}
先ほどと同じ画面が表示されればOKです。
#まとめ
この記事ではSafeArgsを使ってタイプセーフに画面間でデータを受け渡すための方法を解説しました。
新規でアプリを作る場合にはNavigationComponent全体を導入することは容易だとは思いますが既存の大きなアプリでは導入コストが大きいと思うのでまずはSafeArgsを導入して恩恵を受けてみるのはありなのかなと思います。
#サンプル
NavigationSample