Closed34

Navigation Components を使ってみる

tkttkt
dependencies {
  def nav_version = "2.3.3"

  // Java language implementation
  implementation "androidx.navigation:navigation-fragment:$nav_version"
  implementation "androidx.navigation:navigation-ui:$nav_version"

  // Kotlin
  implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
  implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

  // Feature module Support
  implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"

  // Testing Navigation
  androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"

  // Jetpack Compose Integration
  implementation "androidx.navigation:navigation-compose:1.0.0-alpha07"
}

これは全部書かないとだめなんだろうか

tkttkt
dependencies {
    def nav_version = "【任意のバージョン】"

    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
}

まずこれだけ

tkttkt

fragmentTransaction とか intent とか使ってたところから同移行するのかわからぬ

tkttkt

ここクリックしたら、既存のが選択肢にでてきた

tkttkt

遷移の矢印が出るらしいが出し方がわからない

tkttkt


矢印クリックしたところからdestination指定で追加できた

tkttkt

activity -> activity はこれではあんまり扱わんのかな

tkttkt

fragment の遷移を定義しておいて、それを root の activity で呼び出して、セットすると、fragment の遷移ができるようになる、みたいなことだろうか

tkttkt

初期 fragment はどのタイミングで入るのか
空っぽのフラグメントに

    private fun replaceFragment(fragment: Fragment) {
        val fragmentManager = supportFragmentManager
        val fragmentTransaction = fragmentManager.beginTransaction()
        fragmentTransaction.replace(R.id.fragment_container, fragment)
        fragmentTransaction.commit()
    }

こんな感じで入れていたところ

tkttkt

setupActionBarWithNavController でセットされるのか、その後になんか呼ぶ必要があるのか

tkttkt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)

        val navController = findNavController(R.id.main_nav_host)
        val appBarConfiguration = AppBarConfiguration(navController.graph)
        setupActionBarWithNavController(navController, appBarConfiguration)
    }

    override fun onSupportNavigateUp()
            = findNavController(R.id.main_nav_host).navigateUp()
}

ここ書いたけど
java.lang.RuntimeException: Unable to start activity ComponentInfo{}: java.lang.IllegalArgumentException: ID does not reference a View inside this Activity で落ちる

tkttkt

画面サイズごとに呼び出し先の activity を変えてるので、同じ名前のが複数あって、全部に適用できてなかった

tkttkt
val navController = findNavController(R.id.top_up_nav_host)
val appBarConfiguration = AppBarConfiguration(navController.graph)
setupActionBarWithNavController(navController, appBarConfiguration)

これで does not have a NavController set on みたいなエラーが出た

tkttkt
val navController = supportFragmentManager.findFragmentById(R.id.top_up_nav_host) as NavHostFragment
setupActionBarWithNavController(navController.navController)

これもだめ

java.lang.RuntimeException: Unable to start activity ComponentInfo{}: java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.appcompat.app.ActionBar.setTitle(java.lang.CharSequence)' on a null object reference
tkttkt
val navController = supportFragmentManager.findFragmentById(R.id.top_up_nav_host) as NavHostFragment
setupActionBarWithNavController(navController.navController)

これもだめ

java.lang.RuntimeException: Unable to start activity ComponentInfo{}: java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.appcompat.app.ActionBar.setTitle(java.lang.CharSequence)' on a null object reference
tkttkt

github で探してみたけど良いのがでてこない

tkttkt
class MainFragment() : ContainerBaseFragment(R.layout.fragment_main) {
    private lateinit var binding: FragmentTopBinding
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentMainBinding.inflate(inflater)
        return binding.root
    }

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

        binding.nextButton.setOnClickListener {
            findNavController().navigate(R.id.{action_id})
        }
    }
}

これだけでいけた
findNavController().navigate(R.id.{action_id}) これかいただけ
fragment に書かないといけなかった?

{action_id} は、navigation.xml で定義したものの action の id

tkttkt

最小構成で動いたので、Safe Args をいれてみる

tkttkt
class MainFragment() : ContainerBaseFragment(R.layout.fragment_main) {
    private lateinit var binding: FragmentTopBinding
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentMainBinding.inflate(inflater)
        return binding.root
    }

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

        binding.nextButton.setOnClickListener {
            findNavController().navigate(MainFragmentDirections.actionMainFragmentToSecondFragment())
        }
    }
}

こんな感じで動くようになった

tkttkt

あとは fragment 間で値渡しがやりたい

tkttkt
findNavController().navigate({Fragment名}Directions.action())

Fragment名が自クラスと異なっていると、遷移発火時にエラーで落ちる

java.lang.IllegalArgumentException: Navigation action/destination

(ビルド時に検出できないものなのか)

tkttkt
navigation.xml
<action
            android:id="@+id/action_mainFragment_to_secondFragment"
            app:destination="@id/secondFragment"
            app:popUpToInclusive="false" />

app:popUpToInclusive="false" にしても backstack 積まれてる
どうして

tkttkt
用語 説明
Pop To Backボタンを押したとき、どこの画面まで戻るか指定できる
Inclusive Pop Upで指定した画面の1つ前に戻るようにする
app:popUpToInclusive="false"

これは backstack を積まないとかそういうことではないっぽい

app:popUpTo={Root}

これすると、アプリ終了まで戻る

tkttkt
app:popUpTo="@id/MainFragment"

で複数遷移した後のどの位置にいても back すると Main までもどる
他の指定方法がありそうな気もするけど

tkttkt

activity の fragment -> activity はできても
activity 単体 -> activity はできなさそう

このスクラップは2021/03/20にクローズされました