Navigieren in einer Jetpack-Anwendung mit mehreren Modulen ohne Magie und DI

Wenn Sie eine Anwendung erstellen, in der mindestens mehrere Bildschirme angezeigt werden, stellt sich immer die Frage, wie die Navigation am besten implementiert werden kann. Die Frage wird interessanter und schwieriger, wenn Sie eine Anwendung mit mehreren Modulen erstellen. Vor ungefähr anderthalb Jahren habe ich darüber gesprochen, wie Sie die Navigation mit Jetpack in einem Projekt mit mehreren Modulen implementieren können. Und jetzt, nach einer Weile, stolperte ich über meine Implementierung und erkannte, dass es einfacher ist, Module auf demselben Jetpack einfacher zu durchfliegen: ohne Magie und DI.






Projektarchitektur

:





Android : feature- c shared- . app , feature shared.





Single Activity, Activity ,





shared:navigation . .





fun Fragment.navigate(actionId: Int, hostId: Int? = null, data: Serializable? = null) {
	val navController = if (hostId == null) {
		findNavController()
	} else {
		Navigation.findNavController(requireActivity(), hostId)
	}

	val bundle = Bundle().apply { putSerializable("navigation data", data) }

	navController.navigate(actionId, bundle)
}
      
      



:





  • actionId - id





  • hostId - id . ,





  • data - Serializable





, .





val Fragment.navigationData: Serializable?
	get() = arguments?.getSerializable("navigation data")
      
      



id , feature . res/value/ids.xml





<?xml version="1.0" encoding="utf-8"?>
<resources>
	<item name="host_global" type="id"/>
	<item name="host_main" type="id"/>
</resources>
      
      



! , .





feature-

splash. , , . : splash .





id : res/value/ids.xml splash





<?xml version="1.0" encoding="utf-8"?>
<resources>
	<item name="action_splashFragment_to_mainFragment" type="id"/>
	<item name="action_splashFragment_to_onboardingFragment" type="id"/>
</resources>
      
      



Id , , shared:navigation. .





id .





import com.example.smmn.shared.navigation.navigate

class SplashFragment : Fragment(R.layout.fragment_splash) {

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

        buttonToOnboarding.setOnClickListener {
            navigate(R.id.action_splashFragment_to_onboardingFragment)
        }

        buttonToMain.setOnClickListener {
            navigate(R.id.action_splashFragment_to_mainFragment)
        }
    }
}
      
      



, shared:navigation.





.





Activity. . Activity.





class MainActivity : AppCompatActivity(R.layout.activity_main)
      
      



activity_main.xml





<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView 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/host_global"
    android:name="androidx.navigation.fragment.NavHostFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:defaultNavHost="true"
    app:navGraph="@navigation/navigation_global"
    tools:ignore="FragmentTagUsage" />
      
      



, . app res/navigation/navigation_global.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"
    android:id="@+id/navigation_global"
    app:startDestination="@id/splashFragment">

    <fragment
        android:id="@+id/splashFragment"
        android:name="com.example.smmn.feature.splash.SplashFragment"
        android:label="SplashFragment">
        <action
            android:id="@id/action_splashFragment_to_mainFragment"
            app:destination="@id/mainFragment"
            app:popUpTo="@id/navigation_global" />
        <action
            android:id="@id/action_splashFragment_to_onboardingFragment"
            app:destination="@id/onboardingFragment"
            app:popUpTo="@id/navigation_global" />
    </fragment>
    <fragment
        android:id="@+id/mainFragment"
        android:name="com.example.smmn.feature.main.MainFragment"
        android:label="MainFragment" >
        <action
            android:id="@id/action_mainFragment_to_splashFragment"
            app:popUpTo="@id/navigation_global"
            app:destination="@id/splashFragment" />
    </fragment>
    <fragment
        android:id="@+id/onboardingFragment"
        android:name="com.example.smmn.feature.onboarding.OnboardingFragment"
        android:label="OnboardingFragment">
        <action
            android:id="@id/action_onboardingFragment_to_mainFragment"
            app:destination="@id/mainFragment"
            app:popUpTo="@id/navigation_global" />
    </fragment>
</navigation>
      
      



, action () . , , "Back".





, id +, id , id, feature .





id splash





<item name="action_splashFragment_to_mainFragment" type="id"/>
<item name="action_splashFragment_to_onboardingFragment" type="id"/>
      
      







        <action
            android:id="@id/action_splashFragment_to_mainFragment"
            app:destination="@id/mainFragment"
            app:popUpTo="@id/navigation_global" />
        <action
            android:id="@id/action_splashFragment_to_onboardingFragment"
            app:destination="@id/onboardingFragment"
            app:popUpTo="@id/navigation_global" />
      
      



Jetpack . , BottomNavigation .





.





navigation-ui, .





main BottomNavigation res/menu/menu_main.xml





<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/profileFragment"
        android:icon="@drawable/ic_baseline_account_circle_24"
        android:title="@string/main_menu_title_profile" />
    <item
        android:id="@+id/settingsFragment"
        android:icon="@drawable/ic_baseline_settings_24"
        android:title="@string/main_menu_title_settings" />
</menu>
      
      



res/navigation/navigation_main.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"
    android:id="@+id/navigation_main"
    app:startDestination="@id/profileFragment">

    <fragment
        android:id="@+id/profileFragment"
        android:name="com.example.smmn.feature.profile.ProfileFragment"
        android:label="ProfileFragment">
        <action
            android:id="@id/action_profileFragment_to_infoFragment"
            app:destination="@id/infoFragment" />
    </fragment>
    <fragment
        android:id="@+id/settingsFragment"
        android:name="com.example.smmn.feature.settings.SettingsFragment"
        android:label="SettingsFragment" />
    <fragment
        android:id="@+id/infoFragment"
        android:name="com.example.smmn.feature.info.InfoFragment"
        android:label="InfoFragment" />
</navigation>
      
      



id res/menu/menu_main.xml. , id .





res/layout/fragment_main.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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@id/host_main"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@+id/bottomNavigationView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/navigation_main" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottomNavigationView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/white"
        app:elevation="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:menu="@menu/menu_main" />
</androidx.constraintlayout.widget.ConstraintLayout>
      
      



bottomNavigationView





class MainFragment : Fragment(R.layout.fragment_main) {

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

        NavigationUI.setupWithNavController(
            bottomNavigationView,
            Navigation.findNavController(requireActivity(), R.id.host_main)
        )
    }
}
      
      



, , , . , c : .





, ( , ) , . , .





, id . , shared:navigation.





class SettingsFragment : Fragment(R.layout.fragment_settings) {

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

        buttonToSplash.setOnClickListener {
            navigate(R.id.action_mainFragment_to_splashFragment, R.id.host_global)
        }
    }
}
      
      



Id res/values/ids.xml





<?xml version="1.0" encoding="utf-8"?>
<resources>
	<item name="action_mainFragment_to_splashFragment" type="id"/>
</resources>
      
      



, bundle. - Serializable .





, Serializable . .





Serializable , , , , . shared:model Serializable Info.





data class Info(
    val name: String,
    val surname: String
) : Serializable
      
      



profile info. Info .





class ProfileFragment : Fragment(R.layout.fragment_profile) {

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

        buttonToInfo.setOnClickListener {
            navigate(R.id.action_profileFragment_to_infoFragment, data = Info("name", "surname"))
        }
    }
}
      
      



, .





class InfoFragment : Fragment(R.layout.fragment_info) {

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

        val info = navigationData as? Info ?: return
        textView.text = info.toString()
    }
}
      
      







, , .





, Jetpack . , , .





.





!








All Articles