Modüler gezinme tarifi (Hilt)
Bu tarifte, bağımlılık ekleme için Navigation 3 ve Dagger/Hilt kullanarak çok modüllü bir uygulamanın nasıl yapılandırılacağı gösterilmektedir. Amaç, gezinmenin ayrı özellik modüllerinde tanımlandığı ve uygulandığı ayrıştırılmış bir mimari oluşturmaktır.
İşleyiş şekli
Uygulama birkaç modüle ayrılmıştır:
-
appmodülü: Bu, ana uygulama modülüdür. Ortak birNavigatorbaşlatır ve özellik modüllerinden bir diziEntryProviderInstallerekler. Ardından,NavDisplayiçin nihaientryProvider'yı oluşturmak üzere bu yükleyicileri kullanır. -
commonmodülü: Bu modül, aşağıdakiler dahil olmak üzere temel gezinme mantığını içerir:- Eski yığını yöneten bir
Navigatorsınıfı. EntryProviderInstallertürü, özellik modüllerinin gezinme girişlerini uygulamanınentryProvideröğesine eklemek için kullandığı bir işlevdir.
- Eski yığını yöneten bir
-
Özellik modülleri (ör.
conversation,profile): Her özellik iki alt modüle ayrılır:apimodülü: Özelliğin gezinme rotaları da dahil olmak üzere herkese açık API'sini tanımlar. Bu sayede diğer modüller, uygulama ayrıntılarını bilmelerine gerek kalmadan bu özelliğe gidebilir.implmodule: Özelliğin uygulanmasını sağlar. Bu kapsamda, composable'ları ve özelliğin rotalarını composable'larına eşleyen birEntryProviderInstallerde yer alır. Bu yükleyici daha sonra Dagger/Hilt kullanılarakappmodülüne sağlanır.
Bu modüler yaklaşım, endişelerin net bir şekilde ayrılmasına olanak tanıyarak kod tabanını daha ölçeklenebilir ve sürdürülebilir hale getirir. Her özellik kendi gezinme mantığından sorumludur ve app modülü yalnızca bu parçaları bir araya getirir.
package com.example.nav3recipes.modular.hilt import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.components.ActivityRetainedComponent import dagger.multibindings.IntoSet // API object Profile // IMPLEMENTATION @Module @InstallIn(ActivityRetainedComponent::class) object ProfileModule { @IntoSet @Provides fun provideEntryProviderInstaller() : EntryProviderInstaller = { entry<Profile>{ ProfileScreen() } } } @Composable private fun ProfileScreen() { val profileColor = MaterialTheme.colorScheme.surfaceVariant Column( modifier = Modifier .fillMaxSize() .background(profileColor) .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Text( text = "Profile Screen", style = MaterialTheme.typography.headlineMedium, color = MaterialTheme.colorScheme.onSurface ) } }
package com.example.nav3recipes.modular.hilt import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.ui.Modifier import androidx.navigation3.runtime.entryProvider import androidx.navigation3.ui.NavDisplay import com.example.nav3recipes.ui.setEdgeToEdgeConfig import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @AndroidEntryPoint class HiltModularActivity : ComponentActivity() { @Inject lateinit var navigator: Navigator @Inject lateinit var entryProviderScopes: Set<@JvmSuppressWildcards EntryProviderInstaller> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setEdgeToEdgeConfig() setContent { Scaffold { paddingValues -> NavDisplay( backStack = navigator.backStack, modifier = Modifier.padding(paddingValues), onBack = { navigator.goBack() }, entryProvider = entryProvider { entryProviderScopes.forEach { builder -> this.builder() } } ) } } } }
package com.example.nav3recipes.modular.hilt import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.Button import androidx.compose.material3.ListItem import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.dropUnlessResumed import com.example.nav3recipes.ui.theme.colors import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.components.ActivityRetainedComponent import dagger.multibindings.IntoSet // API object ConversationList data class ConversationDetail(val id: Int) { val color: Color get() = colors[id % colors.size] } // IMPL @Module @InstallIn(ActivityRetainedComponent::class) object ConversationModule { @IntoSet @Provides fun provideEntryProviderInstaller(navigator: Navigator): EntryProviderInstaller = { entry<ConversationList> { ConversationListScreen( onConversationClicked = { conversationDetail -> navigator.goTo(conversationDetail) } ) } entry<ConversationDetail> { key -> ConversationDetailScreen(key) { navigator.goTo(Profile) } } } } @Composable private fun ConversationListScreen( onConversationClicked: (ConversationDetail) -> Unit ) { LazyColumn( modifier = Modifier.fillMaxSize(), ) { items(10) { index -> val conversationId = index + 1 val conversationDetail = ConversationDetail(conversationId) val backgroundColor = conversationDetail.color ListItem( modifier = Modifier .fillMaxWidth() .clickable(onClick = dropUnlessResumed { onConversationClicked(conversationDetail) }), headlineContent = { Text( text = "Conversation $conversationId", style = MaterialTheme.typography.headlineSmall, color = MaterialTheme.colorScheme.onSurface ) }, colors = ListItemDefaults.colors( containerColor = backgroundColor // Set container color directly ) ) } } } @Composable private fun ConversationDetailScreen( conversationDetail: ConversationDetail, onProfileClicked: () -> Unit ) { Column( modifier = Modifier .fillMaxSize() .background(conversationDetail.color) .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Text( text = "Conversation Detail Screen: ${conversationDetail.id}", style = MaterialTheme.typography.headlineMedium, color = MaterialTheme.colorScheme.onSurface ) Spacer(modifier = Modifier.height(16.dp)) Button(onClick = dropUnlessResumed(block = onProfileClicked)) { Text("View Profile") } } }
package com.example.nav3recipes.modular.hilt import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.navigation3.runtime.EntryProviderScope import dagger.hilt.android.scopes.ActivityRetainedScoped typealias EntryProviderInstaller = EntryProviderScope<Any>.() -> Unit @ActivityRetainedScoped class Navigator(startDestination: Any) { val backStack : SnapshotStateList<Any> = mutableStateListOf(startDestination) fun goTo(destination: Any){ backStack.add(destination) } fun goBack(){ backStack.removeLastOrNull() } }
package com.example.nav3recipes.modular.hilt import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.components.ActivityRetainedComponent import dagger.hilt.android.scopes.ActivityRetainedScoped @Module @InstallIn(ActivityRetainedComponent::class) object AppModule { @Provides @ActivityRetainedScoped fun provideNavigator() : Navigator = Navigator(startDestination = ConversationList) }