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.
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"
}
NavGraphBuilder DSL ile grafik oluşturma
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.
Kotlin DSL grafiğinizle gezinme
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
}
Navigasyon grafiğinin hedefi
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.