Android ile yaygın Kotlin kalıplarını kullanma

Bu konuda, Android için geliştirme yaparken Kotlin dilinin en faydalı yönlerinden bazıları ele alınmaktadır.

Parçalarla çalışma

Aşağıdaki bölümlerde, Kotlin'in en iyi özelliklerinden bazılarını vurgulamak için Fragment örnekleri kullanılmaktadır.

Devralma

Kotlin'de class anahtar kelimesiyle sınıf bildirebilirsiniz. Aşağıdaki örnekte LoginFragment, Fragment öğesinin bir alt sınıfıdır. Alt sınıf ile üst sınıf arasında : operatörünü kullanarak devralmayı belirtebilirsiniz:

class LoginFragment : Fragment()

Bu sınıf bildiriminde, LoginFragment, üst sınıfı Fragment'ın oluşturucusunu çağırmaktan sorumludur.

LoginFragment içinde, Fragment durumundaki değişikliklere yanıt vermek için bir dizi yaşam döngüsü geri çağırmasını geçersiz kılabilirsiniz. Bir işlevi geçersiz kılmak için aşağıdaki örnekte gösterildiği gibi override anahtar kelimesini kullanın:

override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
): View? {
    return inflater.inflate(R.layout.login_fragment, container, false)
}

Üst sınıftaki bir işleve referans vermek için aşağıdaki örnekte gösterildiği gibi super anahtar kelimesini kullanın:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
}

Boş değer atanabilirlik ve başlatma

Önceki örneklerde, geçersiz kılınan yöntemlerdeki bazı parametrelerin türlerinin sonuna soru işareti ? eklenmiştir. Bu, bu parametreler için iletilen bağımsız değişkenlerin boş olabileceğini gösterir. Boş değerleri güvenli bir şekilde işlediğinizden emin olun.

Kotlin'de, bir nesnenin özelliklerini nesneyi tanımlarken başlatmanız gerekir. Bu, bir sınıfın örneğini aldığınızda erişilebilir özelliklerine hemen başvurabileceğiniz anlamına gelir. Ancak Fragment içindeki View nesneleri, Fragment#onCreateView çağrılana kadar şişirilmeye hazır değildir. Bu nedenle, View için özellik başlatmayı ertelemenin bir yolunu bulmanız gerekir.

lateinit, mülk başlatma işlemini ertelemenize olanak tanır. lateinit kullanırken mülkünüzü en kısa sürede başlatmanız gerekir.

Aşağıdaki örnekte, lateinit kullanılarak onViewCreated içinde View nesnelerinin nasıl atanacağı gösterilmektedir:

class LoginFragment : Fragment() {

    private lateinit var usernameEditText: EditText
    private lateinit var passwordEditText: EditText
    private lateinit var loginButton: Button
    private lateinit var statusTextView: TextView

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        usernameEditText = view.findViewById(R.id.username_edit_text)
        passwordEditText = view.findViewById(R.id.password_edit_text)
        loginButton = view.findViewById(R.id.login_button)
        statusTextView = view.findViewById(R.id.status_text_view)
    }

    ...
}

SHY dönüşümü

Android'de tıklama etkinliklerini dinlemek için OnClickListener arayüzünü uygulayabilirsiniz. Button nesneleri, OnClickListener uygulamasını alan bir setOnClickListener() işlevi içerir.

OnClickListener, uygulamanız gereken tek bir soyut yönteme (onClick()) sahiptir. setOnClickListener() her zaman bağımsız değişken olarak OnClickListener'yi kullandığı ve OnClickListener her zaman aynı tek soyut yönteme sahip olduğu için bu uygulama Kotlin'de anonim bir işlev kullanılarak temsil edilebilir. Bu işlem Single Abstract Method (Tek Soyut Yöntem) dönüştürme veya SAM dönüştürme olarak bilinir.

SAM dönüştürme, kodunuzu önemli ölçüde temizleyebilir. Aşağıdaki örnekte, OnClickListener için Button uygulamak üzere SAM dönüşümünün nasıl kullanılacağı gösterilmektedir:

loginButton.setOnClickListener {
    val authSuccessful: Boolean = viewModel.authenticate(
            usernameEditText.text.toString(),
            passwordEditText.text.toString()
    )
    if (authSuccessful) {
        // Navigate to next screen
    } else {
        statusTextView.text = requireContext().getString(R.string.auth_failed)
    }
}

setOnClickListener() öğesine iletilen anonim işlevdeki kod, kullanıcı loginButton öğesini tıkladığında yürütülür.

Tamamlayıcı nesneler

Eşlik eden nesneler, kavramsal olarak bir türe bağlı olan ancak belirli bir nesneye bağlı olmayan değişkenleri veya işlevleri tanımlamak için bir mekanizma sağlar. Companion nesneleri, değişkenler ve yöntemler için Java'nın static anahtar kelimesini kullanmaya benzer.

Aşağıdaki örnekte TAG, String sabitidir. LoginFragment öğesinin her örneği için benzersiz bir String örneğine ihtiyacınız yoktur. Bu nedenle, String öğesini yardımcı bir nesnede tanımlamanız gerekir:

class LoginFragment : Fragment() {

    ...

    companion object {
        private const val TAG = "LoginFragment"
    }
}

TAG öğesini dosyanın en üst düzeyinde tanımlayabilirsiniz ancak dosyada en üst düzeyde tanımlanmış çok sayıda değişken, işlev ve sınıf da olabilir. Yardımcı nesneler, değişkenleri, işlevleri ve sınıf tanımını, bu sınıfın belirli bir örneğine başvurmadan bağlamaya yardımcı olur.

Mülk yetkisi verme

Özellikleri başlatırken Android'in daha yaygın desenlerinden bazılarını (ör. Fragment içinde ViewModel öğesine erişme) tekrarlayabilirsiniz. Aşırı miktarda yinelenen kodu önlemek için Kotlin'in özellik yetkilendirme söz dizimini kullanabilirsiniz.

private val viewModel: LoginViewModel by viewModels()

Özellik temsilcisi, uygulamanızda yeniden kullanabileceğiniz ortak bir uygulama sağlar. Android KTX, sizin için bazı özellik temsilcileri sunar. Örneğin, viewModels, mevcut Fragment ile kapsamı belirlenmiş bir ViewModel alır.

Özellik devretme, yansıtma kullandığından performans açısından biraz ek yük oluşturur. Bunun karşılığında, geliştirme süresini kısaltan kısa bir söz dizimi elde edilir.

Boş değer atanabilirliği

Kotlin, uygulamanızda tür güvenliğini koruyan katı nullability kuralları sağlar. Kotlin'de nesnelere yapılan referanslar varsayılan olarak null değerler içeremez. Bir değişkene boş değer atamak için temel türün sonuna ? ekleyerek nullable değişken türünü bildirmeniz gerekir.

Örneğin, aşağıdaki ifade Kotlin'de yasa dışıdır. name, String türünde ve boş değerli olamaz:

val name: String = null

Boş değere izin vermek için aşağıdaki örnekte gösterildiği gibi boş değer atanabilir bir String türü (String?) kullanmanız gerekir:

val name: String? = null

Birlikte çalışabilirlik

Kotlin'in katı kuralları, kodunuzu daha güvenli ve kısa hale getirir. Bu kurallar, uygulamanızın kilitlenmesine neden olacak bir NullPointerException oluşma olasılığını azaltır. Ayrıca, kodunuzda yapmanız gereken boşluk kontrollerinin sayısını da azaltırlar.

Çoğu Android API'si Java programlama dilinde yazıldığından, Android uygulaması yazarken genellikle Kotlin olmayan kodları da çağırmanız gerekir.

Nullability, Java ve Kotlin'in davranış açısından farklılık gösterdiği önemli bir alandır. Java, nullability söz dizimi konusunda daha az katıdır.

Örneğin, Account sınıfının name adlı bir String özelliği de dahil olmak üzere birkaç özelliği vardır. Java'da Kotlin'in boş değerlere ilişkin kuralları yoktur. Bunun yerine, boş değer atayıp atayamayacağınızı açıkça belirtmek için isteğe bağlı boş değer ek açıklamaları kullanılır.

Android çerçevesi temel olarak Java ile yazıldığından, nullability ek açıklamaları olmadan API'leri çağırırken bu senaryoyla karşılaşabilirsiniz.

Platform türleri

Java Account sınıfında tanımlanan, açıklama eklenmemiş bir name üyeye referans vermek için Kotlin kullanıyorsanız derleyici, String'nin Kotlin'de String veya String? ile eşlenip eşlenmediğini bilmez. Bu belirsizlik, String! platform türü aracılığıyla gösterilir.

String!, Kotlin derleyicisi için özel bir anlam ifade etmez. String!, String veya String?'ı temsil edebilir ve derleyici, her iki türden de bir değer atamanıza olanak tanır. Türü String olarak gösterip boş değer atarsanız NullPointerException oluşturma riskiyle karşı karşıya kalacağınızı unutmayın.

Bu sorunu gidermek için Java'da kod yazarken her zaman nullability ek açıklamalarını kullanmanız gerekir. Bu ek açıklamalar hem Java hem de Kotlin geliştiricilerine yardımcı olur.

Örneğin, Java'da tanımlandığı şekliyle Account sınıfı aşağıda verilmiştir:

public class Account implements Parcelable {
    public final String name;
    public final String type;
    private final @Nullable String accessId;

    ...
}

Üye değişkenlerden biri olan accessId, @Nullable ile açıklama eklenmiş. Bu, değişkenin boş değer içerebileceğini gösteriyor. Kotlin, accessId değerini String? olarak kabul eder.

Bir değişkenin hiçbir zaman boş olamayacağını belirtmek için @NonNull ek açıklamasını kullanın:

public class Account implements Parcelable {
    public final @NonNull String name;
    ...
}

Bu senaryoda name, Kotlin'de null yapılamayan bir String olarak kabul edilir.

Nullability ek açıklamaları, tüm yeni Android API'lerinde ve mevcut Android API'lerinin çoğunda yer alır. Birçok Java kitaplığı, hem Kotlin hem de Java geliştiricilerini daha iyi desteklemek için nullability ek açıklamaları ekledi.

Null değer alabilme özelliğini işleme

Bir Java türünden emin değilseniz bu türün null değer atanabilir olduğunu düşünmelisiniz. Örneğin, Account sınıfının name üyesi açıklama eklenmemiş. Bu nedenle, bunun boş değer atanabilir bir String? olduğu varsayılmalıdır.

name değerinin başında veya sonunda boşluk olmaması için bu değeri kırpmak istiyorsanız Kotlin'in trim işlevini kullanabilirsiniz. Birkaç farklı şekilde güvenli bir şekilde String? kırpabilirsiniz. Bu yöntemlerden biri, aşağıdaki örnekte gösterildiği gibi null olmayan onay operatörünü (!!) kullanmaktır:

val account = Account("name", "type")
val accountName = account.name!!.trim()

!! operatörü, sol tarafındaki her şeyi null olmayan olarak kabul eder. Bu nedenle, bu durumda name, null olmayan bir String olarak kabul edilir. Solundaki ifadenin sonucu null ise uygulamanız NullPointerException oluşturur. Bu operatör hızlı ve kolaydır ancak kodunuza NullPointerException örneklerini yeniden ekleyebileceğinden dikkatli kullanılmalıdır.

Daha güvenli bir seçenek olarak, aşağıdaki örnekte gösterildiği gibi güvenli çağrı operatörünü, ?. kullanabilirsiniz:

val account = Account("name", "type")
val accountName = account.name?.trim()

Güvenli çağrı operatörü kullanıldığında, name boş değilse name?.trim() sonucu, başında veya sonunda boşluk olmayan bir ad değeri olur. name değeri null ise name?.trim() sonucu null olur. Bu, uygulamanızın bu ifadeyi yürütürken hiçbir zaman NullPointerException oluşturamayacağı anlamına gelir.

Güvenli çağrı operatörü sizi olası bir NullPointerException durumundan kurtarsa da bir sonraki ifadeye boş değer iletir. Bunun yerine, aşağıdaki örnekte gösterildiği gibi Elvis operatörünü (?:) kullanarak boş değerleri hemen işleyebilirsiniz:

val account = Account("name", "type")
val accountName = account.name?.trim() ?: "Default name"

Elvis operatörünün sol tarafındaki ifadenin sonucu null ise sağ taraftaki değer accountName değişkenine atanır. Bu teknik, aksi takdirde boş olacak bir varsayılan değer sağlamak için yararlıdır.

Ayrıca, aşağıdaki örnekte gösterildiği gibi, bir işlevden erken dönmek için Elvis operatörünü de kullanabilirsiniz:

fun validateAccount(account: Account?) {
    val accountName = account?.name?.trim() ?: "Default name"

    // account cannot be null beyond this point
    account ?: return

    ...
}

Android API değişiklikleri

Android API'leri Kotlin ile daha uyumlu hale geliyor. AppCompatActivity ve Fragment gibi Android'in en yaygın API'lerinin çoğu, nullability ek açıklamaları içerir. Fragment#getContext gibi belirli çağrıların ise Kotlin'e daha uygun alternatifleri vardır.

Örneğin, bir Fragment öğesinin Context öğesine erişmek neredeyse her zaman boş olmayan bir değer döndürür. Bunun nedeni, bir Fragment öğesinde yaptığınız çağrıların çoğunun Fragment öğesi bir Activity öğesine (Context öğesinin bir alt sınıfı) bağlıyken gerçekleşmesidir. Bununla birlikte, Fragment#getContext her zaman boş olmayan bir değer döndürmez. Çünkü Fragment öğesinin bir Activity öğesine bağlı olmadığı senaryolar vardır. Bu nedenle, Fragment#getContext işlevinin dönüş türü null değer alabilir.

Fragment#getContext, Fragment#getContext tarafından döndürülen değer boş değer atanabilir olduğundan (ve @Nullable olarak açıklama eklenmiş olduğundan) Kotlin kodunuzda Context? olarak ele almanız gerekir.Context Bu, özelliklerine ve işlevlerine erişmeden önce, daha önce bahsedilen operatörlerden birini null değerlere uygulanması anlamına gelir. Android, bu senaryoların bazılarında bu kolaylığı sağlayan alternatif API'ler içerir. Örneğin, Fragment#requireContext, boş olmayan bir Context döndürür ve Context boş olduğunda çağrılırsa bir IllegalStateException oluşturur. Bu şekilde, güvenli çağrı operatörlerine veya geçici çözümlere gerek kalmadan ortaya çıkan Context değerini null olmayan bir değer olarak değerlendirebilirsiniz.

Mülk başlatma

Kotlin'deki özellikler varsayılan olarak başlatılmaz. Kapsayan sınıfları başlatıldığında başlatılmalıdır.

Özellikleri birkaç farklı şekilde başlatabilirsiniz. Aşağıdaki örnekte, sınıf bildiriminde bir index değişkenine değer atanarak nasıl başlatılacağı gösterilmektedir:

class LoginFragment : Fragment() {
    val index: Int = 12
}

Bu başlatma, bir başlatıcı blokunda da tanımlanabilir:

class LoginFragment : Fragment() {
    val index: Int

    init {
        index = 12
    }
}

Yukarıdaki örneklerde, LoginFragment oluşturulduğunda index başlatılır.

Ancak, nesne oluşturma sırasında başlatılamayan bazı özellikleriniz olabilir. Örneğin, bir View öğesine Fragment içinden referans vermek isteyebilirsiniz. Bu durumda, düzenin önce genişletilmesi gerekir. Fragment oluşturulurken enflasyon oluşmaz. Bunun yerine, Fragment#onCreateView aranırken şişiriliyor.

Bu senaryoyu ele almanın bir yolu, görünümü null değer atanabilir olarak bildirmek ve aşağıdaki örnekte gösterildiği gibi mümkün olan en kısa sürede başlatmaktır:

class LoginFragment : Fragment() {
    private var statusTextView: TextView? = null

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)

            statusTextView = view.findViewById(R.id.status_text_view)
            statusTextView?.setText(R.string.auth_failed)
    }
}

Bu işlev beklendiği gibi çalışsa da artık View öğesinin boş değer içerebilirliğini her başvurduğunuzda yönetmeniz gerekir. Daha iyi bir çözüm, aşağıdaki örnekte gösterildiği gibi View başlatma için lateinit kullanmaktır:

class LoginFragment : Fragment() {
    private lateinit var statusTextView: TextView

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)

            statusTextView = view.findViewById(R.id.status_text_view)
            statusTextView.setText(R.string.auth_failed)
    }
}

lateinit anahtar kelimesi, bir nesne oluşturulduğunda özelliğin başlatılmasını önlemenizi sağlar. Mülkünüz başlatılmadan önce referans verilirse Kotlin UninitializedPropertyAccessException hatası verir. Bu nedenle, mülkünüzü en kısa sürede başlatın.