Navegar por una aplicación Jetpack de varios módulos sin magia ni DI

Cuando comienzas a crear una aplicación en la que hay al menos varias pantallas, siempre surge la pregunta: cuál es la mejor manera de implementar la navegación. La pregunta se vuelve más interesante y difícil cuando se va a realizar una aplicación de varios módulos. Hace aproximadamente un año y medio, hablé sobre cómo se puede implementar la navegación usando Jetpack en un proyecto de varios módulos. Y ahora, después de un tiempo, me topé con mi implementación y me di cuenta de que es posible volar a través de los módulos en el mismo Jetpack más fácilmente: sin magia ni DI.






Arquitectura del proyecto

:





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