Kotlin DSL ve Navigation Compose'da güvenlik türü yazın

Bu sayfada, Gezinme Kotlin DSL ve Gezinme Yazma'ya çalışma zamanı türü güvenliği sağlamaya yönelik en iyi uygulamalar yer alır. Özetle, uygulamanızın veya gezinme grafiğinizin her ekranını modül bazında bir Gezinme dosyasıyla eşleştirmeniz gerekir. Sonuçta oluşturulan dosyaların her biri, ilgili hedef için Navigasyon ile ilgili tüm bilgileri içermelidir. Bu gezinme dosyalarında Kotlin görünürlük değiştiricileri de çalışma zamanı türü güvenliği sağlar:

  • Tür güvenli işlevleri, kod tabanının geri kalanına herkese açık olarak gösterilir.
  • Belirli bir ekran veya gezinme grafiğiyle ilgili gezinmeye özel kavramlar, kod tabanının geri kalanına erişilmemesi için aynı dosyada aynı konumda yer alır ve gizli tutulur.

Gezinme grafiğinizi bölme

Gezinme grafiğinizi ekrana göre bölmelisiniz. Bu, ekranları farklı oluşturulabilen fonksiyonlara bölmekle aynı yaklaşımdır. Her ekranda bir NavGraphBuilder uzantı işlevi olmalıdır.

Bu uzantı işlevi, durum bilgisiz ve ekran düzeyinde oluşturulabilir işlev ile Gezinmeye özgü mantık arasındaki köprüdür. Bu katman, durumun nereden geldiğini ve etkinliklerin nasıl işlendiğini de tanımlayabilir.

Diğer modüllerin erişememesi için kendi modülü için internal olabilecek tipik bir ConversationScreen öğesini burada görebilirsiniz:

// ConversationScreen.kt

@Composable
internal fun ConversationScreen(
  uiState: ConversationUiState,
  onPinConversation: () -> Unit,
  onNavigateToParticipantList: (conversationId: String) -> Unit
) { ... }

Aşağıdaki NavGraphBuilder uzantı işlevi, ConversationScreen composable özelliğini bu NavGraph hedefi olarak ekler. Ayrıca ekranı, ekran kullanıcı arayüzü durumunu sağlayan ve ekranla ilgili iş mantığını işleyen bir ViewModel ile bağlar. ViewModel tarafından işlenemeyen navigasyon etkinlikleri, arayana gösterilir.

// ConversationNavigation.kt

private const val conversationIdArg = "conversationId"

// Adds conversation screen to `this` NavGraphBuilder
fun NavGraphBuilder.conversationScreen(
  // Navigation events are exposed to the caller to be handled at a higher level
  onNavigateToParticipantList: (conversationId: String) -> Unit
) {
  composable("conversation/{$conversationIdArg}") {
    // The ViewModel as a screen level state holder produces the screen
    // UI state and handles business logic for the ConversationScreen
    val viewModel: ConversationViewModel = hiltViewModel()
    val uiState = viewModel.uiState.collectAsStateWithLifecycle()
    ConversationScreen(
      uiState,
      ::viewModel.pinConversation,
      onNavigateToParticipantList
    )
  }
}

ConversationNavigation.kt dosyası, Navigasyon kitaplığındaki kodu hedefin kendisinden ayırır. Ayrıca, hiçbir zaman bu dosyanın dışına sızmamaları gerektiği için gizli tutulan rotalar veya bağımsız değişken kimlikleri gibi Navigasyon kavramlarıyla ilgili bir özet de sunar. Bu katmanda işlenemeyen navigasyon etkinliklerinin doğru düzeyde işlenebilmesi için çağrıyı yapan kişiye açık tutulması gerekir. onNavigateToParticipantList içeren bu tür bir etkinliğin örneğini yukarıdaki kod snippet'inde bulabilirsiniz.

Güvenli gezinmeyi yazın

Gezinme Yazma'nın temel aldığı Gezinme Kotlin DSL, Güvenli Args'in gezinme XML kaynak dosyalarında oluşturulan gezinme grafikleri için sağladığı türde derleme zamanı türü güvenliğini şu anda sunmamaktadır. Safe Args, Navigasyon hedefleri ve işlemleri için tür güvenli sınıflar ve yöntemler içeren kod oluşturur. Ancak, Gezinme kodunuzu çalışma zamanında yazarken güvenli olacak şekilde yapılandırabilirsiniz. Bu özellik sayesinde kilitlenmelerden kaçınabilir ve şunlardan emin olabilirsiniz:

  • Bir hedefe veya gezinme grafiğine giderken sağladığınız bağımsız değişkenler doğru türlerdedir ve gerekli tüm bağımsız değişkenlerin mevcut olduğunu gösterir.
  • SavedStateHandle öğesinden aldığınız bağımsız değişkenler doğru türlerdir.

Her hedef, diğer hedeflerin güvenli bir şekilde gitmesine olanak tanımak için bir NavController uzantı işlevi de göstermelidir.

// ConversationNavigation.kt

fun NavController.navigateToConversation(conversationId: String) {
    this.navigate("conversation/$conversationId")
}

Uygulamanızın farklı NavOptions içeren bir ekranına (ör. popUpTo, savedState, restoreState veya singleTop) gitmek istiyorsanız NavController uzantı işlevine isteğe bağlı bir parametre iletin.

// HomeNavigation.kt

const val HomeRoute = "home"

fun NavController.navigateToHome(navOptions: NavOptions? = null) {
    this.navigate(HomeRoute, navOptions)
}

Güvenli bağımsız değişken sarmalayıcı yazın

Bu bölümün girişinde bahsedilen avantajları elde etmek amacıyla, isteğe bağlı olarak bir hedefin içeriğindeki SavedStateHandle bağımsız değişkenlerini ve bir hedefin içeriğindeki NavBackStackEntry bağımsız değişkenlerini çıkarmak için tür güvenli sarmalayıcı oluşturabilirsiniz.

// ConversationNavigation.kt

private const val conversationIdArg = "conversationId"

internal class ConversationArgs(val conversationId: String) {
  constructor(savedStateHandle: SavedStateHandle) :
    this(checkNotNull(savedStateHandle[conversationIdArg]) as String)
}

// ConversationViewModel.kt

internal class ConversationViewModel(...,
  savedStateHandle: SavedStateHandle
) : ViewModel() {
  private val conversationArgs = ConversationArgs(savedStateHandle)
}

Gezinme grafiğini oluşturma

Gezinme grafikleri, varış noktaları eklemek ve bunlara gitmek için yukarıda açıklanan tür güvenli uzantı işlevlerini kullanır.

Aşağıdaki örnekte görüşme hedefi, diğer iki hedefle (home ve katılımcı listesi) birlikte aşağıdaki şekilde uygulama düzeyine NavHost dahil edilmiştir:

// MyApp.kt

@Composable
fun MyApp(modifier: Modifier = Modifier) {
  val navController = rememberNavController()
  NavHost(
    navController = navController,
    startDestination = HomeRoute,
    modifier = modifier
  ) {

    homeScreen(
      onNavigateToConversation = { conversationId ->
        navController.navigateToConversation(conversationId)
      }
    )

    conversationScreen(
      onNavigateToParticipantList = { conversationId ->
        navController.navigateToParticipantList(conversationId)
      }
    }

    participantListScreen()
}

İç içe yerleştirilmiş gezinme grafiklerinde yazma güvenliği

Birden çok ekran sağlayan modüller için doğru görünürlüğü seçmelisiniz. Bu kavram, yukarıdaki bölümlerde yer alan her bir yöntem için aynıdır. Ancak tek tek ekranları diğer modüllere göstermek mantıklı olmayabilir. Bu durumda, bunları daha büyük ve bağımsız bir akışın parçası olarak değerlendirmelisiniz.

Bu bağımsız ekran grubuna iç içe yerleştirilmiş gezinme grafiği denir. Böylece birden fazla ekranı tek bir NavGraphBuilder uzatma yöntemine dahil edebilirsiniz. Bu yöntem, aynı modül içindeki ekranları birbirine bağlamak için bu NavController uzantı yöntemlerini kullanır.

Aşağıdaki örnekte, önceki bölümlerde açıklanan görüşme hedefi, diğer iki hedefle (görüşme listesi ve katılımcı listesi) birlikte iç içe yerleştirilmiş bir gezinme grafiğinde görünmektedir:

// ConversationGraphNavigation.kt

private val ConversationGraphRoutePattern = "conversation"

fun NavController.navigateToConversationGraph(navOptions: NavOptions? = null) {
  this.navigate(ConversationGraphRoutePattern, navOptions)
}

fun NavGraphBuilder.conversationGraph(navController: NavController) {
  navigation(
    startDestination = ConversationListRoutePattern,
    route = ConversationGraphRoutePattern
  ) {
    conversationListScreen(
      onNavigateToConversation = { conversationId ->
        navController.navigateToConversation(conversationId)
      }
    )
    conversationScreen(
      onNavigateToParticipantList = { conversationId ->
        navController.navigateToParticipantList(conversationId)
      }
    )
    partipantList()
}

Uygulama düzeyinde NavHost iç içe yerleştirilmiş birden çok gezinme grafiğini aşağıdaki şekilde kullanabilirsiniz:

// MyApp.kt

@Composable
fun MyApp(modifier: Modifier = Modifier) {
  val navController = rememberNavController()
  NavHost(
    navController = navController,
    startDestination = HomeGraphRoutePattern
    modifier = modifier
  ) {
    homeGraph(
      navController,
      onNavigateToConversation = {
        navController.navigateToConversationGraph()
      }
    }
    conversationGraph(navController)
}