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

Gezinme bileşeni, Kotlin'in tür açısından güvenli derleyicilerine dayanan Kotlin tabanlı, alana özgü bir dil (DSL) sağlar. Bu API, grafiğinizi XML kaynağı içinde değil, Kotlin kodunuzda bildirimli şekilde oluşturmanızı sağlar. Bu, uygulamanızın gezinme öğelerini 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 bu yapılandırmayı kullanarak etkinliğinizin onCreate() işlevinde dinamik olarak bir gezinme grafiği oluşturmak için kullanılabilir.

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ını temel alan temel bir örnekle başlayalım. Bu örnekte, iki hedefimiz vardır: home ve plant_detail. home hedefi, kullanıcı uygulamayı ilk kez başlattığında mevcuttur. Bu hedef, kullanıcının bahçesindeki bitkilerin bir listesini gösterir. Kullanıcı bu bitkilerden birini seçtiğinde uygulama plant_detail hedefine gider.

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

Sunflower uygulamasında iki hedef ve bunları birbirine bağlayan bir işlem bulunuyor.
Şekil 1. Sunflower uygulamasında home ve plant_detail olmak üzere iki hedef ve bunları birbirine bağlayan bir işlem bulunuyor.

Kotlin DSL Nav Grafiği Barındırma

Uygulamanızın gezinme grafiğini oluşturmadan önce, grafiği barındıracağınız bir yere ihtiyacınız vardır. Bu örnekte parçalar kullanıldığından, grafiği bir FragmentContainerView içindeki NavHostFragment içinde barındırı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 kaynak olarak tanımlanmaz. Bu nedenle, etkinlikteki onCreate() işleminin bir parçası olarak ayarlanması gerekir.

XML'de bir eylem, bir veya daha fazla bağımsız değişkenle hedef kimliğini bağlar. Ancak, Navigasyon DSL'yi kullanırken bir rota, rotanın parçası olarak bağımsız değişkenler içerebilir. Bu, DSL kullanılırken herhangi bir eylem kavramının bulunmadığı anlamına gelir.

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

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

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. Çalışma zamanında gezinme grafiğinizi oluştururken derleme sırasında oluşturulan bu statik kimlikler kullanılamaz. Bu nedenle, Gezinme DSL'si, kimlikler yerine rota dizelerini kullanır. Her rota benzersiz bir dizeyle temsil edilir. Yazım hatasıyla ilgili hata riskini azaltmak için bunları sabit değerler olarak tanımlamak iyi bir uygulamadır.

Bağımsız değişkenlerle çalışırken bunlar rota dizesine eklenir. Rotada bu mantığı oluşturmak, 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"
}

Sabitlerinizi 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, sondaki lambda fragment()DSL oluşturucu işlevini kullanarak iki parça hedefi tanımlar. Bu işlev, hedef için sabit değerlerden alınan bir rota dizesi gerektirir. İşlev, ek yapılandırma için (ör. hedef etiketi) isteğe bağlı bir lambda'nın yanı sıra bağımsız değişkenler ve derin bağlantılar için yerleşik oluşturucu işlevlerini de kabul eder.

Her bir hedefin kullanıcı arayüzünü yöneten Fragment sınıfı, açılı ayraçlar (<>) içinde parametreleştirilmiş bir tür olarak aktarılır. Bu, XML kullanılarak tanımlanan parça hedeflerinde android:name özelliğinin ayarlanmasıyla 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 aracında, 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, genel gezinme grafiği öğeleri, hedefleri ve grafiğinizi oluştururken bunları nasıl kullanacağınız açıklanmaktadır.

Hedefler

Kotlin DSL, üç hedef türü için yerleşik destek sunar: Fragment, Activity ve NavGraph hedefleri. Bunların her biri, hedefi oluşturmak ve yapılandırmak için kendi satır içi uzantı işlevine sahiptir.

Parçalı hedefler

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

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

Etkinlik hedefi

activity()DSL işlevi, bu hedefe atamak için benzersiz bir rota dizesi alır ancak herhangi bir uygulama etkinliği sınıfına parametre haline getirilmez. Bunun yerine, sondaki bir lambda içinde isteğe bağlı bir activityClass ayarlarsınız. Bu esneklik, dolaylı amaç kullanılarak başlatılması gereken bir etkinlik için etkinlik hedefi tanımlamanıza olanak tanır. Bu tür bir açık etkinlik sınıfının mantıklı olmayacağı anlamına gelir. Parça hedeflerinde 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ş gezinme grafiği oluşturmak için kullanılabilir. Bu işlev üç bağımsız değişken alır: grafiğe atanacak bir rota, grafiğin başlangıç hedefinin rotası ve grafiği daha ayrıntılı yapılandırmak için bir lambda. Geçerli öğeler diğer hedefler, bağımsız değişkenler, derin bağlantılar ve hedef için açıklayıcı bir etiketi içerir. Bu etiket, navigationUI 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 bu hedefleri addDestination() kullanarak 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şturulmuş 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 bir lambda olarak alır.

Lambda'nın 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ürler eşleşmelidir. 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 belirli 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ımaların dayanmaması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, özel türünüzde yansımasız kodlama ve kod çözme işlemi sağlamak için Kotlin Serileştirme'yi veya diğer kitaplıkları kullanmanıza olanak tanır.

Örneğin, arama ekranınıza geçirilen 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 geri yükleme işlemlerini desteklemek için) uygulayabilir:

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

Özel bir NavType şu şekilde 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 parseValue(value: String): SearchParameters {
    return Json.decodeFromString<SearchParameters>(value)
  }

  // Only required when using Navigation 2.4.0-alpha07 and lower
  override val name = "SearchParameters"
}

Daha sonra bu, 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())
    }
}

Bu örnekte, değeri dizeden ayrıştırmak için Kotlin Serileştirmesi kullanılmaktadır. Diğer bir deyişle, biçimlerin eşleştiğinden emin olmak için hedefe gittiğinizde Kotlin Serileştirmesinin de kullanılması gerekir:

val params = SearchParameters("rose", listOf("available"))
val searchArgument = Uri.encode(Json.encodeToString(params))
navController.navigate("${nav_routes.plant_search}/$searchArgument")

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

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

Derin bağlantılar

Derin bağlantılar, XML destekli 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 kullanarak açık derin bağlantı oluşturma süreci için geçerlidir.

Ancak örtülü derin bağlantı oluştururken <deepLink> öğeleri için analiz edilebilecek bir XML gezinme kaynağınız yoktur. Bu nedenle, AndroidManifest.xml dosyanıza <nav-graph> öğesi yerleştirmeyi tercih edemezsiniz. 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, derin bağlantılı her hedef için daha spesifik bir deeplink sağlayabilirsiniz. Bu işlev, URI kalıbını temsil eden bir String, amaç işlemlerini temsil eden bir String ve mimeType'ı temsil eden bir String içeren bir NavDeepLink kabul eder .

Örneğin:

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

Ekleyebileceğiniz derin bağlantı sayısı için bir sınır yoktur. deepLink() her çağrıldığında, söz konusu hedef için tutulan 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

Eklenti, Directions ve Arguments sınıflarını oluşturmak için XML kaynak dosyalarını aradığından Safe Args eklentisi, Kotlin DSL ile uyumlu değildir.

Daha fazla bilgi

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