少なくとも複数の画面が表示されるアプリケーションの作成を開始すると、常に疑問が生じます。ナビゲーションを実装するのに最適な方法です。マルチモジュールアプリケーションを作成しようとすると、質問はより面白くて難しくなります。約1年半前、マルチモジュールプロジェクトでJetpackを使用してナビゲーションを実装する方法について話しました。そして今、しばらくして、私は自分の実装に出くわし、同じJetpack上のモジュールをより簡単に飛ばすことが可能であることに気付きました:魔法とDIなしで。
プロジェクトアーキテクチャ
:
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 . , , .
!