安全なARGSとデータを渡す


AndroidのJetpackは、ベストプラクティスを奨励しながら開発をスピードアップするいくつかのツールとのデメリットを提供することにより、生活を容易にすることを目指しています.我々が今日焦点を合わせるつもりであるツールはナビゲーションコンポーネントで、安全なargsでデータを渡します.
セーフARGSは、宛先間のデータをナビゲートして渡すときにタイプセーフティを与えるGradleプラグインです.
我々は、含まれているフラグメントのユーザーの一覧が表示されますシンプルなアプリを作成することによって、これらの概念を探索されますRecyclerView を選択し、User 我々にUserDetailsFragment .

我々は、空の活動で新しいAndroidXプロジェクトで、ゼロから始めます.

依存関係を設定することから始めましょう.
プロジェクトでbuild.gradle ファイルを追加します.
... // dependencies {

def nav_version = "2.3.0"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"

... // dependencies closing }
モジュール内でbuild.gradle ファイルは以下のすべてを必要とします.
... // other plugins

apply plugin: "androidx.navigation.safeargs"

... // android {
このプラグインはNavArgs and NavDirections 我々のために.
... // android {

compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

kotlinOptions {
    jvmTarget = "1.8"
}

... // android closing }
Java 8は、安全なARGSで動作する最小限の要件です.
... // depedencies {

def nav_version = "2.3.0"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"

... // dependencies closing }
我々は、3つの依存関係のすべての1つの変数を使用して、同期でAndroidのJetpackナビゲーションバージョンを同期させます.
我々はプロジェクトを構築することができなければなりません、我々の依存関係の全てをプロジェクトに入れて、現在、ジューシーなものに🥩
我々のアプリは、選択したユーザーの詳細画面を開くことができるユーザーのリストを表示する必要があります.それらのスクリーンの両方を表す2つの断片を作りましょう.
を作成して起動しますUser オブジェクトを使用します.( Right click project folder > New > Kotlin File/Class > Class )
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize

@Parcelize
data class User(val id: Int, val name: String, val age: Int): Parcelable {
    val description: String
        get() = "$name is $age years old."
}
我々は、オブジェクトのようないくつかのプロパティを与えるだけで、ここで非常にシンプルにしていますid , name , and age . 我々もそれを作っているParcelable それで、我々は断片の間でそれを通過することができます.
前に進んで、我々のユーザーを示すために責任がある断片を加えましょうRight click project folder > New > Fragment > Fragment (List) )

私のプロジェクトを意味する名前でコンポーネントを設定しますUser オブジェクト.
このプロセスを通過する私たちのためのボイラープレートコードの多くを生成します.プロジェクトには次のファイルがあります.UsersFragment , UsersRecyclerViewAdapter , fragment_users.xml , and fragment_users_list.xml .
我々は多くの不要なコードを取り除くことができますUsersFragment を使ってダミーデータを使用します.
class UsersFragment : Fragment() {

    private val users = listOf<User>(
        User(1, "Kyle", 29),
        User(2, "Adri", 36),
        User(3, "Andy", 14),
        User(4, "Xayxay", 3),
        User(5, "Mya", 1)
    )

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

        if (view is RecyclerView) {
            with(view) {
                layoutManager = LinearLayoutManager(context)
                adapter = UsersRecyclerViewAdapter(users)
            }
        }
        return view
    }
}
以来UsersRecyclerViewAdapter は自動生成されたダミーオブジェクトを想定しているため、User オブジェクト.それを直しましょう.
class UsersRecyclerViewAdapter(
    private val users: List<User>
) : RecyclerView.Adapter<UsersRecyclerViewAdapter.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.fragment_users, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val user = users[position]
        holder.idView.text = user.id.toString()
        holder.contentView.text = user.name
    }

    override fun getItemCount(): Int = users.size

    inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val idView: TextView = view.findViewById(R.id.item_number)
        val contentView: TextView = view.findViewById(R.id.content)

        override fun toString(): String {
            return super.toString() + " '" + contentView.text + "'"
        }
    }
}
ここで唯一の変化はvalues プロパティusers and the type to User 同様に.また、私たちは、関連するデータをViewHolderonBindViewHolder メソッド.
今我々UsersFragment が正しく設定されていますUserDetailsFragment ( Right click project folder > New > Fragment > Fragment (Blank) )
class UserDetailsFragment : Fragment() {

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

        return view
    }
}
我々も更新しますfragment_user_details.xml 生成するTextView 画面の中央にある.
<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".UserDetailsFragment">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_blank_fragment"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
今、我々は両方の断片がレイアウトされている、我々はナビゲーショングラフでそれらを使用する準備が整いました.
新しいリソースを作成するRight click res folder > New > Android Resource File ), と呼ばれる新しいナビゲーションリソースを作成しますnav_graph
エディタは次のようになります.

をクリックしてグラフにフラグメントを追加しましょう.

私はusersFragment 第一に、それは私たちのためのエントリポイントになることを示す名前の隣に家のアイコンがありますnav_graph . 別の順序でそれらを追加する場合は、単にusersFragment そして、先頭にある家のアイコンをタップして、それが出発地にする.
私も、ナビゲーションアクション矢をuserDetailsFragment 私たちが移動することができることを示すためにusersFragment to userDetailsFragment .
これは我々のアプリの流れの素晴らしい視覚的な表現ですが、我々は今、アプリを実行しようとすると、我々は実際には見ていないだろうusersFragment . 我々はまだ更新する必要がありますactivity_main.xml 似合うNavHostFragment .
プリジェネレーションを置換しましょうTextView インactivity_main.xmlNavHostFragment .
<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <fragment
        android:id="@+id/fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph" />

</androidx.constraintlayout.widget.ConstraintLayout>

Note
You might receive a warning here to use FragmentContainerView instead, but the Android documentation only specifies fragment at this stage, so we will continue to use it here.


我々は、我々のもとに戻ることができますnav_graph.xml そして私たちはactivity_main 我々の目的地パネルのホスト・セクションの下で.

今我々nav_graph がレイアウトされているので、我々はUser オブジェクトusersFragment to userDetailsFragment .
のコードでnav_graph.xml , 今すぐ追加することができますargument 内部userDetailsFragment 属性
... // other userDetailsFragment attributes

<argument
    android:name="user"
    app:argType="com.kiloloco.passing_data.User" />

... // </fragment> of userDetailsFragment
ここでは、私たちが安全なargsに引数と呼ばれるものが欲しいと指定していますuser となります.User .

Note
If you would like to see the different types of arguments you can pass to Safe Args, check out the Android Docs.


我々がプロジェクトを構築するならば、我々はそうしなければなりませんUsersFragmentDirections and UserDetailsFragmentArgs 生成されます.

これらの生成されたオブジェクトは、渡された引数の値にアクセスするために必要なBoilerplateの一部を削除するのと同様に、フラグメント間の引数を渡すときに、型安全性を使用するのに役立ちます.
我々は更新する必要がありますUsersRecyclerViewAdapter and UsersRecyclerViewAdapter.ViewHolder 選択したパスの受け渡しを処理するUser .
... // override fun getItemCount(): Int = users.size

inner class ViewHolder(view: View, var user: User? = null) : RecyclerView.ViewHolder(view) {

... // val idView: TextView = view.findViewById(R.id.item_number)
... // holder.contentView.text = user.name

holder.user = user

... // onBindViewHolder closing }
ヌルブルを加えたuser 私たちの財産ViewHolder , そして、user プロパティholder 我々の中でonBindViewHolder メソッド.
更新しましょうViewHolder 実際にユーザーの選択とナビゲーションを処理するにはUserDetailsFragment .
... // val contentView: TextView = view.findViewById(R.id.content)

init {
    view.setOnClickListener {
        user?.let { user ->
            val directions = UsersFragmentDirections.actionUsersFragmentToUserDetailsFragment(user)
            view.findNavController().navigate(directions)
        }
    }
}

... // override fun toString(): String {
我々の中でonClickListenter , 私たちはuser 議論としてUsersFragmentDirections.actionUsersFragmentToUserDetailsFragmentUser オブジェクトそれは私たちが後にしたセクシーなタイプの安全です🙌🏽
The directions 生成されたアクションを含むnav_graph は、UsersFragmentUserDetailsFragment . また、我々のパスUser 我々が我々からアクセスすることができる束へのオブジェクトUserDetailsFragmentArgs .
我々は、現在のところに向かうことができますUserDetailsFragment 選択した受信を処理するにはUser .
... // class UserDetailsFragment : Fragment() {

private val args: UserDetailsFragmentArgs by navArgs()
private lateinit var user: User

... // 
我々は、委任によって我々の議論にアクセスすることができますnavArgs() を必要とするlateinit var そうすればUser .
... // val view = inflater.inflate(R.layout.fragment_user_details, container, false)

user = args.user

... // return view
以来args 型のNavArgsLazy<UserDetailsFragmentArgs> , 我々は、アクセスすることができますuser 引数から直接プロパティを取得し、this.user .
... // onCreateView closing }

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    textView.text = user.description
}

... // UserDetailsFragment closing }
With this.user 更新onCreateView , 我々はアクセスできますNonNull user UIを更新するにはonViewCreated .
私たちのアプリの実行を与えるし、それが期待通りに動作するかどうかを確認してみましょう🤞🏽

今それはいくつかのデータを渡すためにきれいなきれいな方法です!✨