結論
結果から言うとただの勘違いでした。
実装方法は間違えてないのに解決できない方はこちら
起こったこと
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
に内包していたのが原因だったって落ちです
正解はこう
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
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
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/ になるけど、ビルドタイプに合わせて変更可能
<?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
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
}
}
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)
}
}
// ...略
}
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のレイアウト
<?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 reference
はClean Project
すれば治ります。
[Build] > [Clean Project]から完了後、[Build] > [Rebuild Project]すればおおよそ治ります。
これで治らなけばバグか実装方法が間違えてます。
所感
iOSのストーリーボードみたいに直感的に開発できるので便利! ただ、まだ発展途上って感じが否めない気がする。
CodeよりもDesignタブから書くほうが便利なんだけど、dioalogとかは手打ちじゃないといけないところとか、ドキュメントが少ないところとか(まぁ公式で割と事足りる部分もあるけど)。
新しいアプリを作るときにはデフォルトでNavigationコンポーネントを使用してるので今後の発展に期待したいところですが、Jetpack Composeなるものも出てきてどれを勉強していけばいいのか悩みものですが、Navigatioコンポーネントは今までのコードほぼ崩さずに実装できるので、そこはよかったかなと。
以前書いたものが気にしなくてもよくなった
Navigationコンポーネントに全部移譲しているので現在のFragmentが何かとか判定しなくてもよくなったので以下の記事が、まるまる無駄になりました。ありがたやー。
https://qiita.com/blue928sky/items/777f527ef203d0f5c3d5