0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

やらかし:AndroidのNavigationコンポーネントのSafe Argsを使っててnavArgs()がUnresolved referenceになった

Last updated at Posted at 2021-10-05

結論

結果から言うとただの勘違いでした。

実装方法は間違えてないのに解決できない方はこちら

起こったこと

DetailFragment.kt
class DetailFragment : Fragment() {
    companion object {
        private val args: DetailFragmentArgs by navArgs()  // navArgs Unresolved reference.

        fun newInstance() {
            // ...略
        }
    }
    private var _binding: DetailFragmentBinding? = null
    private val binding get() = _binding!!

    // ...略
}

何さがしてもUnresolved referrenceが解決できなかった。

解決

ただ単に、companion objectに内包していたのが原因だったって落ちです
正解はこう

DetailFragment.kt
class DetailFragment : Fragment() {
    private var _binding: DetailFragmentBinding? = null
    private val binding get() = _binding!!
    private val args: DetailFragmentArgs by navArgs()  // class直下に記入

    // ...略
}

そもそもSafe Args使う場合はnewInstance()とか必要ない場合が多いので書き残してるのに引っ張られてcompanion objectに書いてしまったのが問題でした。

実装方法

他の方が実装方法書いてるので必要ないかもしれないですが、備忘録的に書いておきます。

プロジェクト下のbuild.gradle

build.gradle(project)
buildscript {
    // ...略
    ext.nav_version = '2.3.5'
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"  // Safe Argsの有効化
        // ...略
    }
}

app下のbuild.gradle

build.gradle(app)
plugins {
    id 'kotlin-android'
    id 'kotlin-kapt'
    id 'androidx.navigation.safeargs.kotlin'  // Kotlinのみ場合はこちら
//  id 'androidx.navigation.safeargs'         // JavaとKotlin混雑の場合はこちら
    // ...略
}

// ...略

dependencies {
    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
    // ...略
}

Navigation graph

[File] > [New File] > [Android Resource File]
[File name]は任意(Android公式ではnav_graph)
[Resource type]はNavigation
パスは res/navigation/ になるけど、ビルドタイプに合わせて変更可能

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/nav_graph"
    app:startDestination="@id/startFragment">

    <fragment
        android:id="@+id/mainFragment"
        android:name="com.sampl.MainFragment"
        android:label="start_fragment"
        tools:layout="@layout/start_fragment">

        <!-- アニメーションはここに記入 -->
        <action
            android:id="@+id/action_startFragment_to_firstFragment"
            app:destination="@id/startFragment"
            app:enterAnim="@android:anim/fade_in"
            app:exitAnim="@android:anim/fade_out"
            app:popEnterAnim="@android:anim/fade_in"
            app:popExitAnim="@android:anim/fade_out" />
        <action
            android:id="@+id/action_startFragment_to_secondFragment"
            app:destination="@id/secondFragment"
            app:enterAnim="@android:anim/fade_in"
            app:exitAnim="@android:anim/fade_out"
            app:popEnterAnim="@android:anim/fade_in"
            app:popExitAnim="@android:anim/fade_out" />
        <action
            android:id="@+id/action_startFragment_to_dialogFragment"
            app:destination="@id/dialogFragment" />
    </fragment>
    <fragment
        android:id="@+id/firstFragment"
        android:name="com.sample.FirstFragment"
        android:label="first_fragment"
        tools:layout="@layout/first_fragment">
        <action
            android:id="@+id/action_firstFragment_to_startFragment"
            app:destination="@id/startFragment" />
    </fragment>
    <fragment
        android:id="@+id/secondFragment"
        android:name="com.sample.SecondFragment"
        android:label="second_fragment"
        tools:layout="@layout/second_fragment">
        <action
            android:id="@+id/action_secondFragment_to_startFragment"
            app:destination="@id/secondFragment" />
        <!-- Safe Args -->
        <!-- 引数はここで宣言 -->
        <argument
            android:name="num"
            app:argType="integer" />
    </fragment>
    <!-- DialogFragmentはdialogを記入 -->
    <dialog
        android:id="@+id/dialogFragment"
        android:name="com.sample.DialogFragment"
        android:label="DialogFragment"
        tools:layout="@layout/dialog_fragment" />
</navigation>

各Fragment

StartFragment.kt
class StartFragment: Fragment() {
    private var _binding: StartFragmentBinding? = null
    private val binding get() = _binding!!

    // ...略

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.button_first1.setOnClickListener {
            // オーバーロードがたくさんあるのでお好みで
            // val action = StartFragmentDirections.actionStartFragmentToFirstFragment()
            // findNavController().navigate(action)

            // Shared elementsが必要な場合は
            val extras = FragmentNavigatorExtras(
                view to "imageView"  // Pair<View, String>
            )
            val action = StartFragmentDirections.actionStartFragmentToFirstFragment()
            findNavController().navigate(action, extras)
        }
        binding.button_first2.setOnClickListener {
            // 引数はNavDirectionsの引数として詰める
            val action = StartFragmentDirections.actionStartFragmentToSecondFragment(1)
            findNavController().navigate(action)
        }
        binding.buttonfirst3.setOnClickListener {
            // DialogFragmentも他と変わらず操作できる
            val action = StartFragmentDirections.actionStartFragmentToDialogFragment()
            findNavController().navigation(action)
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()

        // bindingはここでリリース
        _binding = null
    }
}
FirstFragment.kt
class FirstFragment: Fragment() {
    private var _binding: FirstFragmentBinding? = null
    private val binding get() = _binding!!

    // ...略

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        // shared transition
        val sharedTransition = TransitionInflater.from(context)
            .inflateTransition(R.transition.image_transition)
        sharedElementEnterTransition = sharedTransition
        sharedElementReturnTransition = sharedTransition

        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.button_first.onClick.setOnClickListener{
            val action = FirstFragmentDirections.actionFirstFragmentToStartFragment()
            findNavController().navigate(action)
        }
    }

    // ...略
}
SecondFragment.kt
class SecondFragment: Fragment() {
    private var _binding: SecondFragmentBinding? = null
    private val binding get() = _binding!!
    private val args: SecondFragmentArgs by navArgs()

    // ...略

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        // shared transition
        val sharedTransition = TransitionInflater.from(context)
            .inflateTransition(R.transition.image_transition)
        sharedElementEnterTransition = sharedTransition
        sharedElementReturnTransition = sharedTransition

        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val num = args.num
        Log.d("SecondFragment", "num: $num")

        binding.button_second.onClick.setOnClickListener{
            val action = SecondFragmentDirections.actionSecondFragmentToStartFragment()
            findNavController().navigate(action)
        }
    }

    // ...略
}

DialogFragmentは変更せずに実装可

注入するmain_activityのレイアウト

MainActivity.xml
<?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">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fragmentContainerView"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>

実装は正しいのに解決できない場合

だいたいのUnresolved referenceClean Projectすれば治ります。
[Build] > [Clean Project]から完了後、[Build] > [Rebuild Project]すればおおよそ治ります。
これで治らなけばバグか実装方法が間違えてます。

所感

iOSのストーリーボードみたいに直感的に開発できるので便利! ただ、まだ発展途上って感じが否めない気がする。
CodeよりもDesignタブから書くほうが便利なんだけど、dioalogとかは手打ちじゃないといけないところとか、ドキュメントが少ないところとか(まぁ公式で割と事足りる部分もあるけど)。
新しいアプリを作るときにはデフォルトでNavigationコンポーネントを使用してるので今後の発展に期待したいところですが、Jetpack Composeなるものも出てきてどれを勉強していけばいいのか悩みものですが、Navigatioコンポーネントは今までのコードほぼ崩さずに実装できるので、そこはよかったかなと。

以前書いたものが気にしなくてもよくなった

Navigationコンポーネントに全部移譲しているので現在のFragmentが何かとか判定しなくてもよくなったので以下の記事が、まるまる無駄になりました。ありがたやー。
https://qiita.com/blue928sky/items/777f527ef203d0f5c3d5

参考

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?