Kotlin DSL'yi kullanarak programatik olarak bir grafik oluşturun

Gezinme bileşeni, Kotlin'in tür güvenli derleyicilerine dayanan Kotlin tabanlı, alana özgü bir dil (DSL) sağlar. Bu API, grafiğinizi bir XML kaynağının içinde değil, Kotlin kodunuzda bildirimli olarak oluşturmanızı sağlar. Bu, uygulamanızın gezinmesini dinamik olarak oluşturmak istiyorsanız yararlı olabilir. Örneğin, uygulamanız, harici bir web hizmetinden bir gezinme yapılandırmasını indirip önbelleğe alabilir ve ardından, etkinliğinizin onCreate() işlevinde dinamik olarak bir gezinme grafiği oluşturmak için bu yapılandırmayı kullanabilir.

Bağımlılıklar

Kotlin DSL'yi kullanmak için uygulamanızın build.gradle dosyasına aşağıdaki bağımlılığı ekleyin:

Modern

dependencies {
    def nav_version = "2.7.7"

    api "androidx.navigation:navigation-fragment-ktx:$nav_version"
}

Kotlin

dependencies {
    val nav_version = "2.7.7"

    api("androidx.navigation:navigation-fragment-ktx:$nav_version")
}

Grafik oluşturma

Sunflower uygulamasına dayalı temel bir örnekle başlayalım. Bu örnekte iki hedefimiz var: home ve plant_detail. home hedefi, kullanıcı uygulamayı ilk kez başlattığında bulunur. Bu hedef, kullanıcının bahçesindeki bitkilerin bir listesini görüntüler. Kullanıcı bitkilerden birini seçtiğinde uygulama, plant_detail hedefine gider.

Şekil 1'de bu hedefler, plant_detail hedefi için gerekli olan bağımsız değişkenlerle ve uygulamanın home konumundan plant_detail konumuna gitmek için kullandığı bir işlem (to_plant_detail) ile birlikte gösterilmektedir.

Sunflower uygulamasının iki hedefi ve bunları birbirine bağlayan bir işlem vardır.
Şekil 1. Sunflower uygulamasının home ve plant_detail olmak üzere iki hedefi ve bunları birbirine bağlayan bir işlem vardır.

Kotlin DSL Nav Grafiği Barındırma

Uygulamanızın gezinme grafiğini oluşturabilmek için önce grafiği barındıran bir yere ihtiyacınız vardır. Bu örnekte parçalar kullanıldığı için grafik, FragmentContainerView öğesinin içinde NavHostFragment içinde barındırılır:

<!-- activity_garden.xml -->
<FrameLayout
    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">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true" />

</FrameLayout>

Bu örnekte app:navGraph özelliğinin ayarlanmadığına dikkat edin. Grafik, res/navigation klasöründe bir kaynak olarak tanımlanmadığından, etkinlikteki onCreate() işleminin bir parçası olarak ayarlanması gerekir.

XML'de, bir işlem hedef kimliğini bir veya daha fazla bağımsız değişkenle birbirine bağlar. Ancak, Navigasyon DSL'sini kullanırken bir rota, rotanın parçası olarak bağımsız değişkenler içerebilir. Yani DSL'yi kullanırken işlem kavramı yoktur.

Bir sonraki adım, grafiğinizi tanımlarken kullanacağınız bazı sabitler tanımlamaktır.

Grafiğiniz için sabit değerler oluşturun

XML tabanlı gezinme grafikleri, Android derleme işleminin bir parçası olarak ayrıştırılır. Grafikte tanımlanan her id özelliği için sayısal bir sabit değer oluşturulur. Derleme zamanında oluşturulan bu statik kimlikler, çalışma zamanında gezinme grafiğiniz oluşturulurken kullanılamaz. Bu nedenle, Gezinme DSL'si kimlikler yerine rota dizeleri kullanır. Her rota benzersiz bir dizeyle temsil edilir. Yazım hatasıyla ilgili hata riskini azaltmak için bunları sabit değer olarak tanımlamak iyi bir uygulamadır.

Bağımsız değişkenlerle çalışırken bunlar rota dizesine eklenir. Rotada bu mantığın oluşturulması, tipografiyle ilgili hataların sızma riskini bir kez daha azaltabilir.

object nav_routes {
    const val home = "home"
    const val plant_detail = "plant_detail"
}

object nav_arguments {
    const val plant_id = "plant_id"
    const val plant_name = "plant_name"
}

Sabit değerlerinizi tanımladıktan sonra, gezinme grafiğini oluşturabilirsiniz.

val navController = findNavController(R.id.nav_host_fragment)
navController.graph = navController.createGraph(
    startDestination = nav_routes.home
) {
    fragment<HomeFragment>(nav_routes.home) {
        label = resources.getString(R.string.home_title)
    }

    fragment<PlantDetailFragment>("${nav_routes.plant_detail}/{${nav_arguments.plant_id}}") {
        label = resources.getString(R.string.plant_detail_title)
        argument(nav_arguments.plant_id) {
            type = NavType.StringType
        }
    }
}

Bu örnekte, takip eden lambda fragment()DSL oluşturucu işlevini kullanarak iki parça hedefi tanımlar. Bu işlev, hedef için sabit değerlerden elde edilen bir rota dizesi gerektirir. İşlev, hedef etiketi gibi ek yapılandırmalar için isteğe bağlı lambdaların yanı sıra bağımsız değişkenler ve derin bağlantılar için yerleştirilmiş oluşturucu işlevlerini de kabul eder.

Her hedefin kullanıcı arayüzünü yöneten Fragment sınıfı, açılı ayraçlar (<>) içinde parametre haline getirilmiş bir tür olarak iletilir. Bu, XML kullanılarak tanımlanan parça hedeflerinde android:name özelliğini ayarlamakla aynı etkiye sahiptir.

Son olarak, standart NavController.navigation() çağrılarını kullanarak home konumundan plant_detail konumuna gidebilirsiniz:

private fun navigateToPlant(plantId: String) {
   findNavController().navigate("${nav_routes.plant_detail}/$plantId")
}

PlantDetailFragment işlevinde, bağımsız değişkenin değerini aşağıdaki örnekte gösterildiği gibi elde edebilirsiniz:

val plantId: String? = arguments?.getString(nav_arguments.plant_id)

Gezinme sırasında bağımsız değişkenlerin nasıl sağlanacağıyla ilgili ayrıntıları hedef bağımsız değişkenleri sağlama bölümünde bulabilirsiniz.

Bu kılavuzun geri kalanında, yaygın gezinme grafiği öğeleri, hedefler ve grafiğinizi oluştururken bunların nasıl kullanılacağı açıklanmaktadır.

Hedefler

Kotlin DSL, üç hedef türü için yerleşik destek sağlar: Fragment, Activity ve NavGraph hedefleri. Bu hedeflerin her birinin, hedefi oluşturmak ve yapılandırmak için kendi satır içi uzantı işlevi vardır.

Parça hedefleri

fragment()DSL işlevi, uygulama parça sınıfına parametre haline getirilebilir ve bu hedefe atamak için benzersiz bir rota dizesi ve ardından Kotlin DSL grafiğinizle gezinme bölümünde açıklandığı şekilde ek yapılandırma sağlayabileceğiniz bir lambda alır.

fragment<FragmentDestination>(nav_routes.route_name) {
   label = getString(R.string.fragment_title)
   // arguments, deepLinks
}

Aktivite hedefi

activity()DSL işlevi, bu hedefe atamak için benzersiz bir rota dizesi alır ancak uygulama etkinlik sınıflarına parametresine tabi değildir. Bunun yerine, sondaki lambda içinde isteğe bağlı bir activityClass ayarlarsınız. Bu esneklik, dolaylı intent kullanılarak başlatılması gereken bir etkinlik için etkinlik hedefi tanımlamanıza olanak tanır. Bu durumda, uygunsuz etkinlik sınıfının bir anlamı olmaz. Parça hedeflerde olduğu gibi, bir etiket, bağımsız değişkenler ve derin bağlantılar da yapılandırabilirsiniz.

activity(nav_routes.route_name) {
   label = getString(R.string.activity_title)
   // arguments, deepLinks...

   activityClass = ActivityDestination::class
}

navigation()DSL işlevi, iç içe yerleştirilmiş bir gezinme grafiği oluşturmak için kullanılabilir. Bu işlev üç bağımsız değişken alır: grafiğe atanacak rota, grafiğin başlangıç hedefinin rotası ve grafiği daha ayrıntılı yapılandırmak için lambda. Geçerli öğeler arasında diğer hedefler, bağımsız değişkenler, derin bağlantılar ve hedef için açıklayıcı bir etiket bulunur. Bu etiket, GezinmeUI kullanarak gezinme grafiğini kullanıcı arayüzü bileşenlerine bağlamak için yararlı olabilir

navigation("route_to_this_graph", nav_routes.home) {
   // label, other destinations, deep links
}

Özel hedefleri destekleme

Kotlin DSL'yi doğrudan desteklemeyen yeni bir hedef türü kullanıyorsanız addDestination() kullanarak şu hedefleri Kotlin DSL'nize ekleyebilirsiniz:

// The NavigatorProvider is retrieved from the NavController
val customDestination = navigatorProvider[CustomNavigator::class].createDestination().apply {
    route = Graph.CustomDestination.route
}
addDestination(customDestination)

Alternatif olarak, yeni oluşturulan bir hedefi doğrudan grafiğe eklemek için tekli artı operatörünü de kullanabilirsiniz:

// The NavigatorProvider is retrieved from the NavController
+navigatorProvider[CustomNavigator::class].createDestination().apply {
    route = Graph.CustomDestination.route
}

Hedef bağımsız değişkenleri sağlama

Herhangi bir hedef, isteğe bağlı veya gerekli bağımsız değişkenleri tanımlayabilir. İşlemler, tüm hedef oluşturucu türleri için temel sınıf olan NavDestinationBuilder üzerinde argument() işlevi kullanılarak tanımlanabilir. Bu işlev, bağımsız değişkenin adını bir dize ve NavArgument oluşturmak ve yapılandırmak için kullanılan lambda olarak alır.

Lambda içinde bağımsız değişken veri türünü, varsa varsayılan değeri ve null olup olmadığını belirtebilirsiniz.

fragment<PlantDetailFragment>("${nav_routes.plant_detail}/{${nav_arguments.plant_id}}") {
    label = getString(R.string.plant_details_title)
    argument(nav_arguments.plant_id) {
        type = NavType.StringType
        defaultValue = getString(R.string.default_plant_id)
        nullable = true  // default false
    }
}

Bir defaultValue sağlanırsa tür tahmin edilebilir. Hem defaultValue hem de type sağlanırsa türlerin eşleşmesi gerekir. Mevcut bağımsız değişken türlerinin tam listesi için NavType referans belgelerine bakın.

Özel türler sağlama

ParcelableType ve SerializableType gibi bazı türler, rotalar veya derin bağlantılar tarafından kullanılan dizelerden değerlerin ayrıştırılmasını desteklemez. Bunun nedeni, çalışma zamanında yansımaya dayalı olmamalarıdır. Özel bir NavType sınıfı sağlayarak türünüzün bir rotadan veya derin bağlantıdan tam olarak nasıl ayrıştırılacağını kontrol edebilirsiniz. Bu sayede, özel türünüzde yansıtmasız kodlama ve kod çözme işlemi sunmak için Kotlin Serileştirme'yi veya diğer kitaplıkları kullanabilirsiniz.

Örneğin, arama ekranınıza iletilen arama parametrelerini temsil eden bir veri sınıfı, hem Serializable (kodlama/kod çözme desteği sağlamak için) hem de Parcelize (Bundle öğesine kaydetme ve ondan geri yüklemeyi desteklemek için) uygulayabilir:

@Serializable
@Parcelize
data class SearchParameters(
  val searchQuery: String,
  val filters: List<String>
)

Özel NavType şöyle yazılabilir:

val SearchParametersType = object : NavType<SearchParameters>(
  isNullableAllowed = false
) {
  override fun put(bundle: Bundle, key: String, value: SearchParameters) {
    bundle.putParcelable(key, value)
  }
  override fun get(bundle: Bundle, key: String): SearchParameters {
    return bundle.getParcelable(key) as SearchParameters
  }

  override fun serializeAsValue(value: SearchParameters): String {
    // Serialized values must always be Uri encoded
    return Uri.encode(Json.encodeToString(value))
  }

  override fun parseValue(value: String): SearchParameters {
    // Navigation takes care of decoding the string
    // before passing it to parseValue()
    return Json.decodeFromString<SearchParameters>(value)
  }
}

Bu, daha sonra Kotlin DSL'nizde diğer türler gibi kullanılabilir:

fragment<SearchFragment>(nav_routes.plant_search) {
    label = getString(R.string.plant_search_title)
    argument(nav_arguments.search_parameters) {
        type = SearchParametersType
        defaultValue = SearchParameters("cactus", emptyList())
    }
}

NavType, her bir alanın hem yazma hem de okuma bilgilerini içerir. Bu, biçimlerin eşleştiğinden emin olmak için hedefe gittiğinizde NavType öğesinin de kullanılması gerektiği anlamına gelir:

val params = SearchParameters("rose", listOf("available"))
val searchArgument = SearchParametersType.serializeAsValue(params)
navController.navigate("${nav_routes.plant_search}/$searchArgument")

Parametre, hedefteki bağımsız değişkenlerden edinilebilir:

val params: SearchParameters? = arguments?.getParcelable(nav_arguments.search_parameters)

Derin bağlantılar

Derin bağlantılar, XML odaklı bir gezinme grafiğinde olduğu gibi herhangi bir hedefe eklenebilir. Bir hedef için derin bağlantı oluşturma bölümünde tanımlanan tüm prosedürler, Kotlin DSL'yi kullanarak açık derin bağlantı oluşturma sürecinde de geçerlidir.

Ancak dolaylı derin bağlantı oluştururken <deepLink> öğeleri için analiz edilebilecek bir XML gezinme kaynağınız olmaz. Bu nedenle, AndroidManifest.xml dosyanıza <nav-graph> öğesi yerleştirmenize gerek yoktur ve bunun yerine etkinliğinize manuel olarak amaç filtreleri eklemeniz gerekir. Sağladığınız amaç filtresi, uygulamanızın derin bağlantılarının temel URL kalıbı, işlemi ve mime türüyle eşleşmelidir.

deepLink()DSL işlevini kullanarak her bir derin bağlantılı hedef için daha spesifik bir deeplink sağlayabilirsiniz. Bu işlev, URI modelini temsil eden bir String, amaç işlemlerini temsil eden bir String ve mimeType öğesini temsil eden bir String içeren NavDeepLink öğesini kabul eder .

Örnek:

deepLink {
    uriPattern = "http://www.example.com/plants/"
    action = "android.intent.action.MY_ACTION"
    mimeType = "image/*"
}

Ekleyebileceğiniz derin bağlantı sayısıyla ilgili bir sınır yoktur. deepLink() uygulamasını her aradığınızda bu hedef için saklanan bir listeye yeni bir derin bağlantı eklenir.

Yol ve sorgu tabanlı parametreleri de tanımlayan daha karmaşık bir örtülü derin bağlantı senaryosu aşağıda gösterilmiştir:

val baseUri = "http://www.example.com/plants"

fragment<PlantDetailFragment>(nav_routes.plant_detail) {
   label = getString(R.string.plant_details_title)
   deepLink(navDeepLink {
    uriPattern = "${baseUri}/{id}"
   })
   deepLink(navDeepLink {
    uriPattern = "${baseUri}/{id}?name={plant_name}"
   })
}

Tanımı basitleştirmek için dize interpolasyonu kullanabilirsiniz.

Sınırlamalar

Safe Args eklentisi, Kotlin DSL ile uyumlu değildir. Çünkü eklenti, Directions ve Arguments sınıflarını oluşturmak için XML kaynak dosyalarını arar.

Daha fazla bilgi

Kotlin DSL ve Gezinme Oluşturma Yazma kodunuzda tür güvenliğini nasıl sağlayacağınızı öğrenmek için Gezinme türü güvenlik sayfasına göz atın.