Junior Android Developer Mülakat Soru Örnekleri ve Cevapları

Şevval Özdamar
24 min readMar 5, 2024

--

Merhaba, bu yazıda Android alanındaki mülakatlarda sorulabilecek Junior seviye soruları ve cevapları not alıyorum. Yazıyı devamlı olarak güncelliyor olacağım. Hemen başlayalım!🚀

Kotlin programlama dilindeki data class ile class arasındaki fark nedir? Data class hangi durumlarda ve neden tercih edilir?

1. Farklar:

  • equals(), hashCode(), toString() ve copy() Metodlarını Otomatik Olarak Oluşturma: data class, içinde bulunan tüm özelliklerin equals(), hashCode(), toString() ve copy() metodlarını otomatik olarak oluşturur. Bu, nesne karşılaştırmaları ve kopyalama işlemleri için hazır yöntemler sağlar.
  • ComponentN() Metodları: data class, her bir özelliğin (property) sırasına göre component1(), component2() gibi fonksiyonlar oluşturur. Bu, değişkenlere kolay erişim sağlar.
  • Kopya Oluşturma: copy() metodu, bir nesnenin bir kopyasını oluşturur. Bu, nesnenin referansını değil, içeriğini kopyalar.
  • Derleyici Tarafından Otomatik Olarak getter ve setter Oluşturulmaması: data class'larda, genellikle veri taşıma amacıyla olduğu için, özelliklerin (property) değiştirilmesine ihtiyaç duyulmaz. Bu nedenle, data class'lar genellikle immutable (değişmez) olarak tasarlanır.

2. Tercih Edilme Durumları:

  • Veri Taşıma: data class, genellikle verilerin saklanması ve taşınması için kullanılır. Örneğin bir kullanıcının adı, soyadı, e-posta adresi gibi verileri taşımak için idealdir.
  • Immutability (Değişmezlik): data class, özelliklerinin değerlerinin değiştirilemez olduğu bir yapıya sahiptir. Bu, verilerin güvenliğini sağlamak için önemlidir.
  • Kod Kısalığı ve Okunabilirlik: data class, daha az kod yazarak temel veri saklama ve işleme işlevselliği sağlar. Bu da kodun daha kısa ve daha okunabilir olmasını sağlar.

Data class’da bahsi geçen ComponentN() metodlarını biraz daha açarak anlatır mısın?

ComponentN() metodları, data class içinde tanımlanan her özellik için otomatik olarak oluşturulan metodlardır. Bu metodlar, data class'ın her özelliğine sırayla erişimi kolaylaştırır.

Örneğin, bir data class'ı şu şekilde tanımladığınızı düşünelim:

data class Person(val name: String, val age: Int)

Person adlı bu data class, name ve age adında iki özelliğe sahiptir. ComponentN() metodları, bu özelliklere sırayla erişimi sağlar. Yani, Component1() metoduna name özelliğine, Component2() metoduna ise age özelliğine erişim sağlanır.

val person = Person(“John”, 30)
val (name, age) = person
println(“Name: $name, Age: $age”)

Yukarıdaki kodda, Person nesnesinin name ve age özelliklerine Component1() ve Component2() metodları aracılığıyla doğrudan erişim sağlanmıştır. Bu, kodun daha okunabilir ve kullanımının daha kolay hale gelmesini sağlar.

Kotlinde primary constructor ve secondary constructor nedir?

Kotlin’de, bir sınıfın başlangıçta tanımlanan birincil (primary) constructor’ı ve isteğe bağlı olarak tanımlanabilen ikincil (secondary) constructor’ı bulunabilir.

  1. Primary Constructor (Birincil Constructor):
  • Primary constructor, sınıf adı ile birlikte tanımlanan constructor’dır. Sınıfın başlatılması için kullanılır.
  • Primary constructor, sınıfın başka bir işlemi gerçekleştirmeden önce çalıştığı için, sınıfın ana constructor’ı olarak düşünülebilir.
  • Bir sınıfın sadece bir adet primary constructor’ı olabilir.

Örnek primary constructor tanımı:

class Person(val name: String, val age: Int) {
// Class’ın diğer metodları buraya gelebilir
}

2. Secondary Constructor (İkincil Constructor):

  • Secondary constructor’lar, sınıfın başlatılmasını özelleştirmek veya farklı parametre setleriyle nesne oluşturmayı sağlar.
  • Secondary constructor’lar, constructor anahtar kelimesi ile tanımlanır.
  • Bir sınıfın birden fazla secondary constructor’ı olabilir.
  • Secondary constructor’lar, this anahtar kelimesiyle birincil constructor'a veya başka bir secondary constructor'a parametreleri iletebilir.

Örnek secondary constructor tanımı:

class Person(val name: String, val age: Int) { 
constructor(name: String) : this(name, 0) {
// Secondary constructor, ismi verilen bir kişinin yaşını 0 olarak ayarlar
}
}

Secondary constructor’lar, özel durumlar için kullanılabilir ve sınıfın başlatılmasını farklı parametre setleri ile yönlendirmek için kullanılabilir. Ancak, genellikle primary constructor’ın tercih edilmesi ve secondary constructor’ların nadiren kullanılması önerilir, çünkü secondary constructor’lar sınıfın inşasını karmaşıklaştırabilir.

val ile const val arasındaki farklar nedir?

  1. val:
  • val, değişken tanımlarken kullanılan bir anahtar kelimedir.
  • Bir val değişkeni, değeri bir kez atanır ve daha sonra değiştirilemez.
  • Değer, çalışma zamanında atanır ve programın çalışması sırasında değiştirilemez.

2. const val:

  • const val, sabit tanımlarken kullanılan bir kombinasyondur.
  • Bir const val sabiti, derleme zamanında değeri atanır ve programın çalışması sırasında değiştirilemez.
  • Bir const val sadece bir dosya düzeyinde veya bir object içinde tanımlanabilir ve sınıf içinde kullanılamaz.

inline keyword’u nerede ve neden kullanılır?

inline anahtar kelimesi, genellikle fonksiyonların performansını artırmak ve işlevsel kullanım sağlamak için kullanılır. Özellikle, yüksek maliyetli fonksiyon çağrıları yapıldığında, fonksiyonun içeriğini bir kod parçası olarak yerleştirerek performans artışı elde edilebilir.

inline fonksiyonlar, genellikle lambda ifadeleriyle birlikte kullanıldığında en verimli şekilde kullanılır. Örneğin, bir high-order fonksiyon içinde tanımlanan lambda ifadeleri, inline olarak işaretlenirse, bu lambda ifadeleri, fonksiyon çağrısı yerine kodun doğrudan yerleştirilmesiyle çalıştırılabilir, bu da gereksiz çağrı maliyetini ortadan kaldırır.

inline fun calculateAndPrintResult(a: Int, b: Int, operation: (Int, Int) -> Int) {
val result = operation(a, b)
println("Result: $result")
}

fun main() {
calculateAndPrintResult(5, 3) { x, y -> x + y }
calculateAndPrintResult(10, 4) { x, y -> x * y }
}

Bu örnekte, calculateAndPrintResult fonksiyonu inline olarak işaretlenmiştir. Bu sayede, lambda ifadeleri fonksiyon çağrısı yerine doğrudan içeriğe yerleştirilir, bu da performans açısından daha verimli bir çalışma sağlar.

Yukarıda örnek verdiğin inline fonksiyonu, inline olarak tanımlanmasaydı nasıl yazılırdı ve ne gibi bir farkı olurdu?

Eğer calculateAndPrintResult fonksiyonu inline olarak tanımlanmamış olsaydı, lambda ifadeleri yerine fonksiyon çağrıları kullanılacaktı. İşte inline olarak tanımlanmamış bir versiyonu:

fun calculateAndPrintResult(a: Int, b: Int, operation: (Int, Int) -> Int) {
val result = operation(a, b)
println("Result: $result")
}

fun main() {
calculateAndPrintResult(5, 3, ::add)
calculateAndPrintResult(10, 4, ::multiply)
}

fun add(x: Int, y: Int): Int {
return x + y
}

fun multiply(x: Int, y: Int): Int {
return x * y
}

Bu versiyonda, calculateAndPrintResult fonksiyonu inline olarak işaretlenmediği için, lambda ifadeleri yerine add ve multiply gibi ayrıca tanımlanmış fonksiyonlar kullanılıyor.

Farklar:

  1. Performans:
  • inline fonksiyonlar, çağrıldığı yerde kodun doğrudan yerleştirilmesini sağlar, böylece gereksiz çağrı maliyetini ortadan kaldırır. Bu, performans açısından daha etkin bir çalışma sağlar.
  • inline olmayan fonksiyonlar ise, her bir çağrıda ayrı bir işlem gerçekleştirir, bu da biraz daha performans maliyetine yol açabilir.

2. Kod Temizliği:

  • inline fonksiyonlar, kodu daha temiz ve kısa tutar, çünkü lambda ifadeleri doğrudan fonksiyon çağrısı içinde yer alır.
  • inline olmayan fonksiyonlar, lambda ifadeleri yerine ayrıca tanımlanmış fonksiyonları kullanır, bu da kodun daha dağılmış ve okunması daha zor hale gelmesine neden olabilir.

Access modifiers nedir? Örnekler vererek açıklar mısın?

  1. public:
  • En genel erişim belirleyicidir.
  • Herhangi bir yerden erişilebilir.
  • public erişim belirleyicisi, default erişim belirleyicisidir yani bir sınıf, metod veya özellik eğer belirtilmemişse otomatik olarak public olur (Bu ifade Kotlin dili için geçerlidir).

2. private:

  • Tanımlandığı sınırlı kapsamda erişilebilir.
  • Özellikle sadece içinde tanımlandığı sınıf içerisinde kullanılması gereken metodlar veya özellikler için kullanılır.
  • Dışarıdan erişilemez.

3. protected:

  • protected belirleyici, sadece sınıfın kendisi veya alt sınıfları tarafından erişilebilir.

4. internal:

  • Aynı modül içindeki herhangi bir yerden erişilebilir.

Örnek:

open class Outer {
private val a = 1
protected open val b = 2
internal open val c = 3
val d = 4 // public by default

protected class Nested {
public val e: Int = 5
}
}

class Subclass : Outer() {
// a is not visible
// b, c and d are visible
// Nested and e are visible

override val b = 5 // 'b' is protected
override val c = 7 // 'c' is internal
}

class Unrelated(o: Outer) {
// o.a, o.b are not visible
// o.c and o.d are visible (same module)
// Outer.Nested is not visible, and Nested::e is not visible either
}

Inheritance modifiers nedir? Örnekler vererek açıklar mısın?

  1. final:
  • final erişim belirleyicisi, Kotlin’de varsayılan erişim belirleyicisidir yani eğer belirtilmemişse bir sınıf, metod veya özellik otomatik olarak final olur.
  • Bir sınıf veya bir metod final olarak işaretlenirse, bu üye alt sınıflar tarafından yeniden uygulanamaz.
  • Yani, final olarak işaretlenen bir sınıfın alt sınıfları olamaz veya final olarak işaretlenen bir metodu alt sınıflar tarafından yeniden uygulanamaz.
class Button {
fun click() = print("Click")
}

class SpecificButton : Button() { // Hata, kalıtılamaz
override fun click() = print("Specific Click") // Hata
}

Bu örnekte Button sınıfı defaut olarak final olduğundan SpecificButton, Button’dan kalıtılamaz. Ayrıca, click() metodu da override edilemez çünkü click metodu da default olarak final’dır.

2. open:

  • Bir sınıf veya bir metod open olarak işaretlenirse, bu üye alt sınıflar tarafından yeniden uygulanabilir.
  • Yani, open olarak işaretlenen bir sınıfın alt sınıfları olabilir veya open olarak işaretlenen bir metodu alt sınıflar yeniden uygulayabilir.
open class Button {
open fun click() = print("Click")
fun doubleClick() = print("Double Click")
}

class SpecificButton() : Button { // Kalıtım şuan mümkündür
override fun click() = print("Specific Click") // Şuan çalışır
override fun doubleClick() = print("Specific Double Click") // Hata
}

Button sınıfı open olarak işaretlendi, bu sebeple alt sınıflar tarafından kalıtılabilir.

click() metodu da open olarak işaretlendiği için alt sınıflar tarafından kalıtılabilir.

doubleClick() metodu, open olarak işaretlenmediği için varsayılan değer olarak final alır, bu sebeple alt sınıftan kalıtılamaz. Kalıtılmaya çalışılırsa da bu noktada hata döner.

3. abstract:

  • Bir sınıf veya bir metod abstract olarak işaretlenirse, bu üye bir soyut üye olur ve doğrudan örneklendirilemez veya uygulanamaz.
  • abstract olarak işaretlenen bir metodu, alt sınıflar override etmek zorundadır.
  • abstract olan bir sınıfın en az bir adet abstract metodu olmalıdır. Bu abstract olan metodun implementasyonu, ana sınıf içerisinde yapılmamalıdır.
abstract class Widget {
abstract fun draw() // override edilmek zorundadır
open fun focus() {} // override edilebilir (zorunluluk yok)
fun hide() {} // varsayılan olarak final, override edilemez
}

Sonuç olarak, abstract methodlar varsayılan olarak open olur ve ayrıca override edilmek zorundadır.

overload ile override arasındaki fark nedir? Örnekler verir misin?

  1. Overloading (Aşırı Yükleme):
  • Overloading, aynı isme sahip fakat farklı parametre listelerine sahip birden fazla fonksiyonun tanımlanmasıdır. Yani, aynı fonksiyon adı birden fazla kez tanımlanabilir ancak parametre listeleri farklı olmalıdır.
  • Overloading, aşırı yükleme demektir çünkü bir fonksiyon adı, farklı parametrelerle yüklenir.
  • Overloading, genellikle farklı davranışlar sergileyen işlevleri gruplamak veya aynı işlemi farklı veri tipleri üzerinde gerçekleştirmek için kullanılır.

Örnek:

class Calculator {
// İki integer toplama
fun add(a: Int, b: Int): Int {
return a + b
}

// İki double toplama
fun add(a: Double, b: Double): Double {
return a + b
}
}

2. Override (Geçersiz Kılma):

  • Override, bir alt sınıfta (türetilmiş sınıf) bir metodun, üst sınıftaki (temel sınıf) aynı metod adıyla ve imzasıyla tanımlanmasıdır.
  • Bu, alt sınıfın üst sınıfın davranışını geçersiz kılmasını ve kendi özel davranışını uygulamasını sağlar.
  • Genellikle bir sınıfın metodlarını değiştirme veya genişletme ihtiyacı olduğunda kullanılır.

Örnek:

open class Animal {
open fun makeSound() {
println("Animal makes a sound")
}
}

class Dog : Animal() {
override fun makeSound() { // Animal sınıfındaki makeSound metodunu override ederek geçersiz kılma
println("Dog barks")
}
}

Bu örnekte, Dog sınıfı Animal sınıfındaki makeSound metodunu geçersiz kılar ve kendi özel davranışını uygular.

Bir Activity ve Fragment arasındaki farklar nelerdir?

  1. Activity:
  • Bir Activity, bir kullanıcı arayüzü ekranını temsil eder ve genellikle bir UI (Kullanıcı Arayüzü) penceresine karşılık gelir.
  • Activity, tek başına bir kullanıcı etkileşimini yönetir ve genellikle tüm kullanıcı arayüzünü barındıran tek bir pencere olarak düşünülür.
  • Uygulamanın bir parçası olarak manifest dosyasında bildirilir ve uygulama tarafından başlatılır ve sonlandırılır.
  • Bir Activity, diğer Activity’leri başlatmak, sistem hizmetleriyle iletişim kurmak, kullanıcı etkileşimlerini yönetmek gibi görevleri yerine getirir.

2. Fragment:

  • Fragment, Activity içindeki modüler ve yeniden kullanılabilir bir UI parçasını temsil eder.
  • Fragment, kendi yaşam döngüsüne sahiptir ve Activity’nin yaşam döngüsü ile ilişkilidir. Bu sayede, Activity’nin yaşam döngüsü içinde farklı parçaları kontrol etmek mümkün olur.
  • Tek bir Activity içinde birden çok Fragment bulunabilir ve bu sayede modüler tasarımlar sağlanabilir. Örneğin, bir tablet cihazda aynı anda iki farklı Fragment’i gösterebilirsiniz.
  • Fragment’lar, Android API 11 (Honeycomb) ve sonraki sürümlerde tanıtılmıştır ve daha çok büyük ekranlı cihazlarda (tabletler gibi) veya çoklu parçalı kullanıcı arayüzleri oluşturmak için kullanılır.
  • Fragment’lar, kendi içeriklerini ve kullanıcı etkileşimlerini yönetirler, ancak genellikle bir Activity içinde barındırılırlar ve Activity tarafından yönetilirler.

Android Jetpack bileşenleri nelerdir?

Android Jetpack, geliştiricilere Android uygulamalarını hızlı ve etkili bir şekilde geliştirmeleri için bir dizi bileşen sunar. Bunlar arasında LiveData, ViewModel, Room, Navigation ve daha birçok bileşen bulunmaktadır.

  1. Lifecycle: Uygulama yaşam döngüsü olaylarına tepki vermek için kullanılır. Aktiviteler ve Fragmentlerin yaşam döngüsü olaylarını yönetmek ve bu olaylara tepki vermek için kullanılır.
  2. ViewModel: Uygulama veri tutma ve işleme mantığını ayırarak, veriye UI’dan daha kolay erişim sağlar. Uygulama verilerinin UI’ya bağlı olmadan saklanmasını ve yönetilmesini sağlar.
  3. LiveData: Yaşam döngüsü bilinci olan observer paterni kullanarak, veri değişikliklerini otomatik olarak UI’ya yansıtmak için kullanılır. Veri değiştiğinde UI’ın otomatik olarak güncellenmesini sağlar.
  4. Room: SQLite veritabanını kullanan, yerel veri saklama işlemleri için yüksek seviyeli bir kütüphanedir. SQL sorgularını kullanarak veri tabanı işlemlerini gerçekleştirmeyi kolaylaştırır.
  5. Navigation: Uygulama içi navigasyonun karmaşıklığını yönetmek için kullanılır. Sayfa geçişleri, derin bağlantılar ve animasyonlar gibi navigasyon işlemlerini kolaylaştırır.
  6. Paging: Büyük veri kümesini etkili bir şekilde yüklemek ve göstermek için kullanılır. RecyclerView ile entegre olarak, sayfa sayfa veri yüklemeyi ve göstermeyi sağlar.
  7. Data Binding: UI bileşenleri ile veri modeli arasında bağlantı kurmayı kolaylaştırır. XML dosyaları üzerinde veri bağlama işlemlerini destekler, böylece veri değişikliklerini otomatik olarak UI’ya yansıtabilir.
  8. WorkManager: Arka planda çalışan işlemleri zamanlama ve yönetme işlemleri için kullanılır. Tekrar deneme, görev zincirleri ve işlem sıralamaları gibi arka plandaki işleri yönetmek için kullanılır.

AsyncTask’ın yerini ne alabilir?

AsyncTask, arka planda işlem yapmak için kullanılan eski bir yöntemdir. Modern yaklaşımlar arasında Kotlin Coroutines veya RxJava gibi asenkron programlama kütüphaneleri yer alır.

  1. Kotlin Coroutines

Kotlin Coroutines, asenkron ve ardışık işlemleri yönetmek için kullanılan bir dil özelliğidir. Coroutines, kodunuzu daha okunabilir ve yönetilebilir hale getirir, aynı zamanda hata işleme ve iş parçacığı yönetimi konusunda esneklik sağlar.

Avantajları:

  • Kod okunabilirliğini artırır.
  • İşlem yönetimi ve hata işleme için daha esnek bir yaklaşım sunar.
  • İş parçacığı yönetimini otomatik olarak sağlar.

Kotlin Coroutines’in en temel kullanım senaryoları için basit kod örnekleri:

1.1. Asenkron İşlemler (Ağ İstekleri)

fun main() = runBlocking {
println("Program başladı")

// Coroutine başlatma
val job = GlobalScope.launch {
val result = fetchUrl("https://www.example.com")
println("Veri alındı: $result")
}

println("Program devam ediyor")

// Coroutine'in tamamlanmasını beklemek için
job.join()

println("Program sonlandı")
}

suspend fun fetchUrl(url: String): String {
return URL(url).readText()
}

Bu örnekte, fetchUrl fonksiyonu ile veri almak için ağ isteği yapılır. GlobalScope.launch ile yeni bir coroutine başlatılır ve fetchUrl fonksiyonu içerisinde bu ağ isteği yapılır.

1.2. Splash Ekranı

class SplashScreenActivity {

fun showSplashScreen() {
CoroutineScope(Dispatchers.Main).launch {
delay(3000) // 3 saniye splash ekranını gösterme
navigateToMainActivity() // ana ekrana yönelme
}
}

fun navigateToMainActivity() {
// ana ekrana geçiş kodu
}
}

Bu örnekte, SplashScreenActivity sınıfı içerisinde showSplashScreen fonksiyonu splash ekranını göstermek için kullanılmıştır. CoroutineScope(Dispatchers.Main).launch ile ana iş parçacığında bir coroutine başlatılmış ve delay(3000) ile 3 saniye bekletme yapılmıştır. Bu süre zarfında splash ekranı gösterilir ve belirtilen süre sonunda navigateToMainActivity fonksiyonu ile ana ekrana geçiş yapılır.

2. RxJava

RxJava, reaktif programlama prensiplerini kullanarak asenkron ve olay temelli programlama için kullanılır. Observable, Single, Completable gibi veri türleri ile asenkron işlemleri kolayca yönetebilirsiniz.

Avantajları:

  • Fonksiyonel reaktif programlama prensiplerini destekler.
  • Veri akışını daha etkili ve esnek bir şekilde yönetmenizi sağlar.
  • Hata işleme ve geri dönüş değerleri için zengin bir API seti sunar.

Android’de arayüz tasarımında ConstraintLayout’ın avantajları nelerdir?

ConstraintLayout, esnek ve karmaşık arayüzleri kolayca tasarlamak için kullanılan bir layout türüdür. Göreceli konumlandırma ve sürükleyip bırakma gibi özellikler sayesinde esnek ve performanslı bir arayüz oluşturulabilir. ConstraintLayout, animasyonları destekler. Bu, arayüzdeki geçişlerin ve etkileşimlerin daha pürüzsüz ve etkileyici olmasını sağlar.

Retrofit nedir ve ne için kullanılır?

Retrofit, Android uygulamalarında HTTP tabanlı web servis çağrıları yapmayı kolaylaştıran bir kütüphanedir. Özellikle RESTful API’lar ile etkileşim kurmak için tercih edilen bir araçtır. Retrofit, HTTP isteklerini oluşturmayı, istekleri göndermeyi, yanıtları işlemeyi ve model sınıflarına dönüştürmeyi kolaylaştıran bir arayüz sağlar.

Retrofit’in Ana Özellikleri:

  1. Retrofit, JSON, XML ve diğer veri formatlarını otomatik olarak Java veya Kotlin objelerine dönüştürmeyi sağlar. Bu, veri işleme sürecini kolaylaştırır ve kodun daha okunabilir olmasını sağlar.
  2. URL’lerdeki parametrelerin dinamik olarak değiştirilmesini ve farklı istek tiplerine uygun şekilde özelleştirilmesini sağlar.
  3. Retrofit, HTTP hatalarını ve istisnaları kolayca işlemenizi sağlar. Hata durumlarında geriye özelleştirilmiş mesajlar veya işlemler döndürebilirsiniz.
  4. Retrofit, istek ve yanıtlar üzerinde değişiklik yapmanıza izin veren Interceptor ile entegre çalışır. Bu, istekleri loglama, oturum bilgileri eklemek gibi özel işlemler yapmak için kullanışlıdır.

Interceptor kullanım örneği:
Aşağıda, bir RequestInterceptor adında bir Interceptor sınıfı oluşturduk. Bu sınıf, her istekte Authorization header'ını eklemek için kullanılacaktır.

import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET

// Interceptor oluşturma
class RequestInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer YOUR_ACCESS_TOKEN")
.build()
return chain.proceed(request)
}
}

// API arayüzü
interface ApiService {
@GET("endpoint_path")
suspend fun fetchData(): ResponseModel
}

// Retrofit istemcisi oluşturma
val interceptor = RequestInterceptor()

val client = OkHttpClient.Builder()
.addInterceptor(interceptor)
.build()

val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()

// API hizmeti oluşturma
val apiService = retrofit.create(ApiService::class.java)

// API'den veri alma
val response = apiService.fetchData()

Bu örnekte, RequestInterceptor adında bir Interseptör sınıfı oluşturduk. Bu sınıf, Interceptor arabirimini uygulayarak istekleri yakalar ve "Authorization" header'ını ekler.

Daha sonra, OkHttpClient oluştururken bu Interseptörü ekledik ve bu OkHttpClient'i Retrofit istemcisine bağladık. Sonuç olarak, her API isteği gönderildiğinde "Authorization" header'ı otomatik olarak eklenmiş olacaktır.

LiveData nedir ve nasıl kullanılır?

LiveData, yaşam döngüsü bilinci olan veri tutma sınıfıdır. Uygulama durumu değiştikçe otomatik olarak UI güncellemelerini sağlar ve bellek sızıntılarını önlemeye yardımcı olur.

  1. Yaşam Döngüsü Bilinci: LiveData, Android bileşenlerinin (aktiviteler, fragmentler) yaşam döngüsü durumlarına otomatik olarak uyum sağlar. Bu, bellek sızıntılarına ve potansiyel olarak uygulama çökmelerine yol açabilecek hatalı observe etme durumlarını önler.
  2. Veri Güncellemeleri: LiveData, veri değişikliklerini dinamik olarak observer’lara bildirir. Bu sayede, veri değiştiğinde UI’ın otomatik olarak güncellenmesini sağlar.

LiveData kullanımına örnek:

  1. LiveData Oluşturma: LiveData, genellikle ViewModel içerisinde kullanılır. Öncelikle, bir LiveData nesnesi oluşturmalısınız:
class MyViewModel : ViewModel() {
private val _myLiveData = MutableLiveData<String>()

val myLiveData: LiveData<String>
get() = _myLiveData
}

2. Veri Güncelleme: LiveData’ya veri eklemek için setValue veya postValue metodlarını kullanabilirsiniz:

_myLiveData.value = "LiveData"

// veya

_myLiveData.postValue("LiveData")

setValue direkt ana iş parçacığında kullanılırken, postValue arka plan iş parçacığında kullanılmalıdır.

3. LiveData Gözlemleme: LiveData’nın değerini gözlemlemek için observe metodunu kullanabilirsiniz:

myViewModel.myLiveData.observe(viewLifecycleOwner, { value ->
// değer değiştiğinde bu blok çalışır
textView.text = value
})

RecyclerView’ın avantajları nelerdir?

RecyclerView, Android’de dinamik ve büyük veri setlerini etkili bir şekilde göstermek için kullanılan bir widget’tir. RecyclerView, ListView ve GridView gibi eski listeleme ve grid yapılarına göre birçok avantaj sunar. İşte RecyclerView’ın bazı önemli avantajları:

1. Esneklik ve Özelleştirme

RecyclerView, özelleştirilebilir bir mimariye sahiptir. LayoutManager, ItemDecoration ve ItemAnimator gibi bileşenler ile görünüm ve davranışları kolayca özelleştirebilirsiniz.

2. Verimlilik

RecyclerView, sadece görünen öğeleri yeniden oluşturur ve yeniden kullanır. Bu, bellek kullanımını azaltır ve performansı artırır, özellikle büyük veri setleri ile çalışırken faydalıdır.

3. Esnek Layout Yönetimi

RecyclerView, farklı düzenleme yöneticileri (LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager) ile farklı görünüm düzenlerini destekler. Bu, listeler, grid yapıları, karmaşık grid yapıları ve diğer özelleştirilmiş düzenler oluşturmanıza olanak tanır.

4. Animasyon Desteği

ItemAnimator kullanarak eklenen, silinen veya değiştirilen öğeler için animasyonlar ekleyebilirsiniz. Bu, kullanıcı deneyimini zenginleştirir ve kullanıcı etkileşimlerini daha eğlenceli hale getirir.

5. Veri Bağlama

RecyclerView.Adapter sınıfı, veri ve görünüm arasında bir köprü görevi görür. Bu, veri kaynağını ve UI’yi kolayca bağlamanızı ve dinamik olarak güncellemeler yapmanızı sağlar.

6. Çoklu Seçim Desteği

RecyclerView, çoklu öğe seçimini destekler. Bu, kullanıcıların birden fazla öğeyi seçip işlem yapmasını kolaylaştırır.

7. Canlı Veri Desteği

Android Architecture Components kütüphanesi ile birlikte LiveData ve ViewModel gibi bileşenlerle kullanıldığında, RecyclerView otomatik olarak veri değişikliklerini algılar ve güncellenir. Bu, veri akışını yönetmeyi ve veri değişikliklerini otomatik olarak UI’ya yansıtmayı kolaylaştırır.

8. Esnek Görünüm Yönetimi

RecyclerView, özelleştirilebilir ViewHolders ve itemViewType’lar ile farklı görünümleri dinamik olarak yönetebilir. Bu, farklı öğe türlerini (örneğin, başlık, içerik, reklam gibi) aynı liste içinde görüntülemeyi mümkün kılar.

Intent nedir ve ne için kullanılır?

Intent, Android platformunda iki farklı bileşen arasında (örneğin, iki aktivite veya bir servis ve bir aktivite) iletişim kurmak için kullanılan bir mesajlaşma nesnesidir.

Intent, farklı türde görevleri gerçekleştirmek için kullanılabilir. Örneğin bir aktivite başlatmak, bir web sayfasını görüntülemek, bir servisi başlatmak veya bir veri göndermek gibi.

Temel Kullanım Örnekler:

  1. Activity Başlatma:
val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)

2. Activity’den Activity’e Veri Gönderme:

val intent = Intent(this, SecondActivity::class.java)
intent.putExtra("MESSAGE_TAG", "Hello World!")
startActivity(intent)

3. Web Sayfası Görüntüleme:

val intent = Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com"))
startActivity(intent)

4. Telefon Araması Yapma:

val intent = Intent(Intent.ACTION_DIAL, Uri.parse("tel:123456789"))
startActivity(intent)

5. E-posta Gönderme:

val intent = Intent(Intent.ACTION_SENDTO).apply {
data = Uri.parse("x@xxx.com") // E-posta adresini buraya ekleyin
putExtra(Intent.EXTRA_SUBJECT, "Konu")
putExtra(Intent.EXTRA_TEXT, "Mesaj")
}
if (intent.resolveActivity(packageManager) != null) {
startActivity(intent)
}

6. Bir Konumu Harita Uygulamasında Gösterme:

val gmmIntentUri = Uri.parse("geo:0,0?q=latitude,longitude(label)")
val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri)
mapIntent.setPackage("com.google.android.apps.maps")
if (mapIntent.resolveActivity(packageManager) != null) {
startActivity(mapIntent)
}

7. Activity’den Veri Alma:

val resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data: Intent? = result.data
val message = data?.getStringExtra("MESSAGE_TAG")
// veriyi işle
}
}

val intent = Intent(this, SecondActivity::class.java)
resultLauncher.launch(intent)

Android uygulamalarında proguard nedir ve nasıl kullanılır?

ProGuard, Android uygulamalarının boyutunu küçültmek ve uygulamanızı daha hızlı çalıştırmak için kullanılan bir optimizasyon aracıdır. ProGuard, kod obfuscation (kod karıştırma), code shrinking (kod küçültme) ve optimization (optimizasyon) gibi farklı teknikleri kullanarak uygulamanızın güvenliğini artırır ve kaynak kodunuzu korur.

  1. Kod Karıştırma (Obfuscation): ProGuard, sınıf, method ve değişken adlarını karıştırarak kaynak kodunuzu okunabilirliğini zorlaştırır.
  2. Kod Küçültme (Code Shrinking): Kullanılmayan kod parçalarını (dead code) kaldırarak APK boyutunu azaltır.
  3. Optimizasyon: Kodunuzu daha hızlı çalıştırmak için performans optimizasyonları yapar.

ProGuard Kullanımı:

1. ProGuard Etkinleştirme

ProGuard’ı etkinleştirmek için, build.gradle dosyasında minifyEnabled özelliğini true olarak ayarlayın:

android {
...
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}

2. ProGuard Kuralları

proguard-rules.pro dosyasında özel ProGuard kurallarını tanımlayabilirsiniz. Bu kurallar, belirli sınıfların, methodların veya değişkenlerin obfuscated (karıştırılmış) veya korunmuş olmasını sağlar.

Örnek proguard-rules.pro dosyası:

# Android için varsayılan kurallar
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService

# Kütüphaneler için özel kurallar
-keep class com.example.myLibrary.** { *; }

# Kaynak kodunu korumak için kurallar
-keepclassmembers class com.example.MyClass {
public void myMethod();
}

Bu örnekte, belirli Android bileşenlerini (Activity, Service vb.), belirli classları ve methodları korumak için ProGuard kuralları tanımlanmıştır.

Farklı bi örnek:

-keepclassmembers class * extends com.example.framework.infrastructure.business.BusinessPresenter {
public <init>(...);
}
  • -keepclassmembers: Bu komut, sınıf üyelerini (yani metodlar ve field’lar) koruma altına almayı belirtir.
  • class * extends com.example.framework.infrastructure.business.BusinessPresenter: Bu kısım, BusinessPresenter sınıfından kalıtılan tüm sınıfları seçer. Yani, BusinessPresenter sınıfından türetilen tüm alt sınıflar bu kurala dahil olacaktır.
  • public <init>(...);: Bu kısım, BusinessPresenter sınıfından türetilen alt sınıfların public erişim belirteci ile tanımlanan tüm constructor’larını korur. <init> terimi, Java dilinde constructor’ı temsil eder. (...) kısmı, herhangi bir parametreyi kabul eden constructor’ı ifade eder.

3. Optimizasyon

Optimizasyon için ek kurallar eklemek isterseniz, optimizations direktifini kullanabilirsiniz:

-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*

Bu kural, ProGuard optimizasyonları için belirli optimizasyon türlerini devre dışı bırakır. İşte bu kuralın ayrıntılı açıklaması:

  • !code/simplification/arithmetic: Bu kısım, aritmetik operasyonları basitleştiren kod optimizasyonunu devre dışı bırakır. ProGuard, matematiksel ifadeleri daha basit hale getirerek kodu optimize edebilir, ancak bu örnekte bu optimizasyonu devre dışı bırakıyoruz.
  • !code/simplification/cast: Bu kısım, dönüşümleri (casting) basitleştiren kod optimizasyonunu devre dışı bırakır. ProGuard, bazen dönüşümleri daha basit bir forma dönüştürerek kodu optimize eder, ancak bu örnekte bu optimizasyonu devre dışı bırakıyoruz.
  • !field/*: Bu kısım, field optimizasyonlarını devre dışı bırakır. ProGuard, kullanılmayan field’ları daha az bellek tüketen formatlara dönüştürebilir, ancak bu durumda bu optimizasyonu devre dışı bırakıyoruz.
  • !class/merging/*: Bu kısım, sınıf birleştirmesini devre dışı bırakır. ProGuard, benzer veya aynı özelliklere sahip sınıfları birleştirerek kodu optimize edebilir, ancak bu örnekte bu optimizasyonu devre dışı bırakıyoruz.

ProGuard, Android uygulamalarının güvenliğini ve performansını artırmak için etkili bir araçtır. Ancak, doğru kuralların tanımlanması ve uygulamanın doğru bir şekilde test edilmesi önemlidir. Yanlış kurallar veya eksik konfigürasyonlar uygulamanın düzgün çalışmamasına neden olabilir.

Type Inference nedir? Kotlin bunu sağlar mı?

Tip çıkarımı (type inference), bir programlama dili derleyicisinin veya yorumlayıcısının, değişkenlerin tiplerinin açıkça belirtilmeden otomatik olarak belirleyebilmesi yeteneğidir. Bu, kodun daha kısa ve okunabilir olmasını sağlar çünkü kodu yazan kişi her değişkenin tipini açıkça belirtmek zorunda kalmaz.

Örneğin, bir programlama dilinde aşağıdaki gibi bir değişken tanımlaması yapıyorsanız:

val x = 5

Burada x değişkeninin tipi belirtilmemiştir. Ancak derleyici, 5 sayısının bir int (tam sayı) olduğunu anlayarak, x değişkeninin tipini int olarak belirler. Bu süreç tip çıkarımı olarak adlandırılır. Tip çıkarımı, Kotlin dilinde görülmektedir.

Companion Object ile Object arasındaki farklar nedir?

Object

  • object, Kotlin'de tek bir örneği olan bir sınıf (singleton) oluşturur.
  • object ifadesi, bir sınıfın dışında tanımlanabilir ve bu nedenle top-level bir sınıf oluşturulabilir.
  • object içindeki tüm üyeler, ilgili sınıfın tek bir örneği üzerinde çalışır ve bu nedenle statik üyeler gibi davranır.
  • object ile tanımlanan sınıf, genellikle statik metodlar veya sabitlerin gruplanması için kullanılır.

Örnek:

object Constants {
const val BASE_API_URL = "https://api.example.com/.../"
fun something() {
// do something
}
}

fun main() {
Constants.something()
}

Companion Object

  • companion object, bir sınıfın içindeki statik üyeleri (metodlar, sabitler) gruplamak için kullanılır.
  • companion object, sınıfın içinde tanımlanır ve bu nedenle ilgili sınıfın bir örneği olmadan erişilebilir.
  • companion object içindeki üyeler, ilgili sınıfın tek bir örneği üzerinde çalışmazlar. Bu nedenle, companion object içindeki üyeleri diğer sınıf üyelerine erişmek için sınıf örneği oluşturulması gerekir.

Örnek:

class MyClass {
companion object {
const val BASE_API_URL = "https://api.example.com/.../"
fun something() {
// do something
}
}
}

fun main() {
MyClass.something()
}

Genel Karşılaştırma:

  • object genellikle tekil nesneleri oluşturmak için kullanılırken, companion object genellikle bir sınıfın statik üyelerini gruplamak için kullanılır.
  • object top-level bir sınıf oluştururken, companion object bir sınıf içinde tanımlanır.
  • object içindeki üyeler, ilgili sınıfın tek bir örneği üzerinde çalışırken, companion object içindeki üyeler, sınıf örneği oluşturmadan da erişilebilir ancak ilgili sınıfın tek bir örneği üzerinde çalışmaz.

Peki bir class içinde companion object yerine object oluşturulup kullanılamaz mı?

Bir sınıf içinde companion object yerine object oluşturulabilir. Ancak, bu durumda object, sınıfın her örneği için ayrı bir örneğe sahip olacaktır. Bu, her örneğin kendi kopyasına sahip olacağı anlamına gelir, ki bu genellikle istenen davranış değildir. companion object ise sınıfın tek bir örneği ile ilişkilendirilir ve bu nedenle sınıfın tek bir örneği üzerinde çalışır.

Hatalı bir kullanım:

class MyClass {
object Constants {
const val BASE_API_URL = "https://api.example.com/v1/"
fun something() {
// do something
}
}
}

fun main() {
MyClass.Constants.something()
}

Bu durumda, MyClass'ın herhangi bir örneği ayrı bir objectörneğine erişmektedir.

Doğru bir kullanım:

class MyClass {
companion object {
const val BASE_API_URL = "https://api.example.com/v1/"
fun something() {
// do something
}
}
}

fun main() {
MyClass.something()
}

Bu durumda, companion object, MyClass'ın tek bir örneği ile ilişkilendirilir. Yani, MyClass'ın herhangi bir örneği, aynı companion object örneğine erişir.

Kotlin dilindeki sealed anahtar kelimesi ne için kullanılır?

Kotlin’deki sealed anahtar kelimesi, sınıfların bir hiyerarşisini tanımlarken kullanılır ve bu sınıfların belirli bir sınıf içinde veya aynı dosya içinde olmasını sağlar. sealed sınıflar, enum türlerine benzer şekilde çalışır, ancak her bir alt sınıf farklı bir veri yapısına veya duruma sahip olabilir.

Bu yapı, özellikle bir sınıfın belirli bir alt kümesini sınırlamak ve sınıfların güvenli ve tahmin edilebilir bir şekilde kullanılmasını sağlamak için yararlıdır. Genellikle, when ifadeleri ile birlikte kullanılır çünkü when ifadeleri, tüm olası sınıf türlerini ele almanızı sağlar.

Örnek:

sealed class Shape {
data class Circle(val radius: Double) : Shape()
data class Rectangle(val width: Double, val height: Double) : Shape()
object NotAShape : Shape()
}

fun describeShape(shape: Shape): String {
return when (shape) {
is Shape.Circle -> "A circle with radius ${shape.radius}"
is Shape.Rectangle -> "A rectangle with width ${shape.width} and height ${shape.height}"
Shape.NotAShape -> "Not a valid shape"
}
}

fun main() {
println(describeShape(Shape.Circle(5.0))) // A circle with radius 5.0
println(describeShape(Shape.Rectangle(10.0, 5.0))) // A rectangle with width 10.0 and height 5.0
println(describeShape(Shape.NotAShape)) // Not a valid shape
}

Bu örnekte, Shape adlı sealed bir sınıf tanımlanmıştır. Circle, Rectangle, ve NotAShape bu sınıfın alt türleridir. when ifadesi kullanılarak her bir alt tür için özel bir davranış tanımlanabilir. Eğer when ifadesinde tüm alt sınıflar kapsanmışsa, else dalı kullanmanıza gerek kalmaz. Bu, sealed sınıfların getirdiği önemli bir avantajdır.

Kotlin dilindeki enum anahtar kelimesi ne için kullanılır?

Kotlin’de enum anahtar kelimesi, sabit bir değer kümesini temsil eden bir veri türü tanımlamak için kullanılır. enum sınıfları, belirli bir grup sabit (constant) değerle çalışmak için uygun bir yol sağlar. Her enum sabiti, enum sınıfının bir örneğidir ve bunlar, bir sınıf gibi özelliklere ve işlevlere sahip olabilirler.

Enum’lar genellikle sınırlı ve değişmeyecek bir dizi seçeneği belirtmek için kullanılır. Örneğin günlerin adlarını, ayların adlarını veya renkleri temsil etmek için kullanılabilirler.

Örnek:

enum class Color {
RED, GREEN, BLUE
}

Başka bir örnek:

enum class DayOfWeek(val isWeekend: Boolean) {
MONDAY(false),
TUESDAY(false),
WEDNESDAY(false),
THURSDAY(false),
FRIDAY(false),
SATURDAY(true),
SUNDAY(true);

fun printType() {
if (isWeekend) {
println("$this is a weekend.")
} else {
println("$this is a weekday.")
}
}
}

fun main() {
val today = DayOfWeek.SATURDAY
today.printType() // SATURDAY is a weekend.

for (day in DayOfWeek.values()) {
println(day)
}
}

Bu örnekte, DayOfWeek adlı bir enum sınıfı tanımlanmıştır. Her gün bir DayOfWeek sabitidir ve her biri isWeekend adlı bir özellik alır. Ayrıca, printType adlı bir fonksiyon tanımlanmıştır ve bu fonksiyon her bir günün hafta sonu mu yoksa hafta içi mi olduğunu yazdırır.

Enum sınıfları, genellikle sabit ve sınırlı seçenekler listesi ile çalışırken daha güvenli ve okunabilir bir kod sağlar. Bu tür sabitleri kontrol etmek ve karşılaştırmak daha kolaydır.

Kotlin scope fonksiyonlarının (LET, WITH, RUN, APPLY, ALSO) kullanımlarını sırayla açıklayıp sonrasında arasındaki farkları gösterir misin.

  1. let

let fonksiyonu bir nesne üzerinde işlem yapıp, sonucun dönüş değeri ile devam eder. İçerisinde bir lambda fonksiyonu alır ve bu fonksiyona nesne it anahtar kelimesi ile geçilir.

  • Dönüş Değeri: Lambda bloğunun son satırının değeri döner.
  • Kullanım Amacı: Null güvenli işlemler ve zincirleme işlemler yaparken kullanılır.

Örnek:

val name = "Sevval"

val length = name.let {
println(it) // Sevval
it.length
}

// length = 4

Başka bir örnek:

data class User(val name: String?, val email: String?, val age: Int?)

fun sendWelcomeEmail(user: User?) {
user?.let {
println("Name: ${it.name}")
println("Email: ${it.email}")

if (it.email != null) {
// Email gönderme işlemi
println("Welcome email sent to ${it.email}")
} else {
println("No email provided.")
}
} ?: run {
println("User is null")
}
}

// null ve non-null senaryolarıyla çalışır
sendWelcomeEmail(User("Sevval", "sevval@example.com", 23))
sendWelcomeEmail(null)

2. with

with fonksiyonu bir nesne üzerinde işlemler yapar ancak nesnenin kendisi this olarak kullanılır. Lambda içerisindeki işlemlerden sonra herhangi bir değer döndürebilir.

  • Dönüş Değeri: Lambda bloğunun son satırının değeri döner.
  • Kullanım Amacı: Bir nesne üzerinde çok sayıda işlem yaparken kullanılır.

Örnek:

val person = Person("Sevval", 23)

val description = with(person) {
println(name) // Sevval
println(age) // 23
"Name: $name, Age: $age"
}

// description = "Name: Sevval, Age: 23"

Başka bir örnek:

data class User(var name: String, var email: String, var isLoggedIn: Boolean)

fun updateUserProfile(user: User) {
with(user) {
// Kullanıcıya ait birden fazla alanı güncelle
name = "Updated Name"
email = "updatedemail@example.com"
isLoggedIn = true
println("Profile updated for user: $name")
}
}

// Kullanıcıyı güncellemek için kullan
val user = User("Sevval", "sevval@example.com", false)
updateUserProfile(user)
println(user) // User(name=Updated Name, email=updatedemail@example.com, isLoggedIn=true)

3. run

run hem let hem de with gibi kullanılabilir. Bir nesne üzerinde işlem yapar ve bir sonuç döner. Lambda bloğu içerisinde nesne this olarak kullanılır. Yeni bir scope yaratıp belirli bir kodu çalıştırmak için de kullanılabilir.

  • Dönüş Değeri: Lambda bloğunun son satırının değeri döner.
  • Kullanım Amacı: with ve let fonksiyonlarına benzer ama daha esnektir.

Örnek:

val name = "Sevval"

val greeting = name.run {
println(this) // Sevval
"Hello, $this!"
}

// greeting = "Hello, Sevval!"

Başka bir örnek:

data class DatabaseConnection(val url: String) {
fun connect() = println("Connected to $url")
fun close() = println("Disconnected from $url")
}

fun fetchDataFromDatabase(): List<String>? {
val connection = DatabaseConnection("jdbc://mydatabase")
return connection.run {
connect()
val data = listOf("Data1", "Data2", "Data3")
close()
data
}
}

val data = fetchDataFromDatabase()
println(data) // [Data1, Data2, Data3]

4. apply

apply, bir nesne üzerinde değişiklik yaparken kullanılır ve nesnenin kendisini döner. Lambda bloğu içinde this olarak kullanılır.

  • Dönüş Değeri: Lambda bloğunda ne yapılırsa yapılsın, nesnenin kendisi döner.
  • Kullanım Amacı: Bir nesnenin özelliklerini ayarlarken, yapılandırma blokları için idealdir.

Örnek:

val person = Person("Sevval", 23).apply {
name = "Deniz"
age = 30
}

// person.name = "Deniz", person.age = 30

Başka bir örnek:

data class Configuration(var host: String = "", var port: Int = 0, var timeout: Int = 0)

fun configureServer(): Configuration {
return Configuration().apply {
host = "localhost"
port = 8080
timeout = 1000
}
}

val config = configureServer()
println(config) // Configuration(host=localhost, port=8080, timeout=1000)

5. also

also, let'e benzerdir fakat it anahtar kelimesini kullanarak nesnenin kendisini döner. let'ten farkı, nesnenin üzerinde yan etkili işlemler yapılmak istenirse tercih edilir.

  • Dönüş Değeri: Lambda bloğunda ne yapılırsa yapılsın, nesnenin kendisi döner.
  • Kullanım Amacı: Yan etkiler oluşturmak, loglama veya debugging amaçlı kullanılır.

Örnek:

val person = Person("Sevval", 23).also {
println(it) // Person(name="Sevval", age=23)
it.name = "Deniz"
}

// person.name = "Deniz"

Başka bir örnek:

data class File(val name: String, var isDownloaded: Boolean = false)

fun downloadFile(file: File) {
file.also {
println("Starting download for file: ${it.name}")
}.apply {
isDownloaded = true
}.also {
println("File downloaded: ${it.name}, Download status: ${it.isDownloaded}")
}
}

val file = File("myfile.txt")
downloadFile(file)

Karşılaştırma:

Kotlin scope fonksiyonları ve özellikleri

Bir fragment’ın yaşam döngüsü adımları nelerdir?

Fragment’ın yaşam döngüsü sırası şu şekildedir:

1. onAttach()

  • Fragment, aktiviteye bağlandığı zaman çağrılır.
  • Fragment'ın içinde olduğu Activity'ye erişilebilir hale geldiği yerdir.
  • context ya da activity nesneleri bu noktada erişilebilir hale gelir.

2. onCreate()

  • Fragment oluşturulurken çağrılır.
  • Fragment’ın yaşam döngüsü boyunca korunacak verilerin ayarlandığı veya başlatıldığı yer olabilir (örneğin değişkenlerin tanımlanması).
  • Fragment’ın yaşam döngüsü boyunca bazı veriler korunmak isteniyorsa setRetainInstance(true) çağrısı burada yapılır.

3. onCreateView()

  • Fragment’ın kullanıcı arayüzü (UI) oluşturulduğunda çağrılır.
  • LayoutInflater ve ViewGroup kullanılarak XML dosyasından görünüm hiyerarşisi yaratılır.
  • Fragment’a ait UI, bu metot içinde geri döndürülmelidir (örneğin return inflater.inflate(R.layout.fragment_example, container, false))

4. onViewCreated()

  • onCreateView()'den hemen sonra çağrılır.
  • Bu metot, Fragment’ın görsel öğeleri tamamen oluşturulduktan sonra çalıştırılır ve UI ile ilgili işlemler bu adımda yapılabilir.
  • Örneğin, RecyclerView veya Button gibi arayüz öğeleri burada tanımlanabilir.

5. onActivityCreated()

  • Fragment, içindeki etkinliğin (Activity) onCreate() metodu tamamlandıktan sonra çağrılır.
  • Fragment ile aktivite arasındaki etkileşimler bu noktada başlar. Veritabanı sorguları veya arayüz güncellemeleri yapılabilir.

6. onStart()

  • Fragment görünür hale geldiğinde çağrılır.
  • Fragment kullanıcıya gösterildiği andır, ancak henüz etkileşime açık değildir.

7. onResume()

  • Fragment artık aktif ve etkileşime hazırdır.
  • Kullanıcı artık fragment ile etkileşimde bulunabilir (butonlar, liste öğeleri vb.)
  • Fragment, Activity ile birlikte çalışmaya devam eder ve en güncel duruma sahiptir.

8. onPause()

  • Fragment arka plana alındığında çağrılır, fakat tamamen görünmez değildir.
  • Örneğin, başka bir fragment veya bir dialog fragment görüntülendiğinde çağrılır.
  • Fragment görünürlüğünü kaybetmek üzereyken yapılması gereken işlemler burada yapılabilir (örneğin animasyonları durdurma, kaynakları serbest bırakma)

9. onStop()

  • Fragment tamamen görünürlüğünü kaybettiğinde çağrılır.
  • Fragment artık kullanıcıya gösterilmez ve arka planda çalışmaya devam eder.
  • Uzun süreli görevlerin iptali veya gereksiz kaynakların serbest bırakılması için uygun bir yerdir.

10. onDestroyView()

  • Fragment’ın arayüzü yok edilmeden önce çağrılır.
  • onCreateView()'de oluşturulan UI öğeleri bu noktada kaldırılır.
  • Örneğin, bu noktada view binding işlemi yapılmışsa, ilgili binding referansı temizlenebilir.

11. onDestroy()

  • Fragment tamamen yok edilmeden hemen önce çağrılır.
  • Kaynaklar serbest bırakılabilir, arka plan işlemleri durdurulabilir ve sonlandırma işlemleri yapılabilir.

12. onDetach()

  • Fragment’ın aktivite ile bağlantısı tamamen kesildiğinde çağrılır.
  • Fragment’ın yaşam döngüsü sona erer ve fragment bağlı olduğu aktiviteye artık erişemez.

Kotlindeki liste tipleri nelerdir? Bunların özellikleri ve arasındaki farkları açıklar mısın?

Kotlin’de listeler temel olarak List, ArrayList, MutableList, Array şeklinde kategorize edilebilir.

1. List<T>

  • Değiştirilemez (Immutable) Liste: Liste oluşturulduktan sonra elemanları değiştirilemez. Eleman ekleyip çıkarma işlemleri yapılmaz.
  • Kullanım Amacı: Sabit bir liste gerektiğinde kullanılır. Örneğin, bir listenin içeriğini değiştirmek istemediğin durumlarda.
  • Özellikler:
  • size: Listenin eleman sayısını döner.
  • get(index): Belirtilen indeksteki elemanı döner.
  • contains(element): Belirtilen eleman listede var mı kontrol eder.
  • indexOf(element): Elemanın listedeki indeksini bulur.
  • lastIndexOf(element): Elemanın son göründüğü indeksi bulur.
  • Örnek:
val immutableList = listOf(1, 2, 3)
println(immutableList[0]) // 1

2. MutableList<T>

  • Değiştirilebilir (Mutable) Liste: Eleman eklenebilir, çıkarılabilir veya değiştirilebilir bir liste. List'in aksine, bu liste dinamik olarak değiştirilebilir.
  • Kullanım Amacı: Dinamik bir listeye ihtiyaç duyduğun, yani eleman ekleme/çıkarma işlemleri yapacağın durumlarda kullanılır.
  • Ekstra Özellikler:
  • add(element): Listeye eleman ekler.
  • remove(element): Belirtilen elemanı listeden çıkarır.
  • set(index, element): Belirtilen indeksteki elemanı değiştirir.
  • clear(): Listedeki tüm elemanları siler.
  • Örnek:
val mutableList = mutableListOf(1, 2, 3)
mutableList.add(4) // [1, 2, 3, 4]
mutableList.remove(2) // [1, 3, 4]

3. ArrayList<T>

  • Java’dan gelen bir liste türü: Kotlin’de MutableList gibi davranır ama Java tabanlı bir veri yapısıdır. Dizi temelli olduğu için performansı optimize edilebilir.
  • Kullanım Amacı: Dinamik bir listeye ihtiyaç duyulduğunda kullanılır. Genellikle büyük veri setleriyle çalışırken veya yüksek performans gereken durumlarda tercih edilir.
  • Özellikler: MutableList ile aynı işlevlere sahiptir, ek olarak performans ve bellek yönetimi açısından ArrayList biraz daha optimize edilebilir.
  • Örnek:
val arrayList = arrayListOf(1, 2, 3)
arrayList.add(4) // [1, 2, 3, 4]
arrayList.removeAt(1) // [1, 3, 4]

4. Array<T>

  • Sabit Boyutlu Dizi: Array, sabit boyutlu bir veri yapısıdır. Yani oluşturulduğunda boyutu belirlenir ve değiştirilemez. Ancak dizinin içindeki elemanlar değiştirilebilir.
  • Kullanım Amacı: Dizi boyutunun sabit olduğu ve hızlı erişim gerektiğinde kullanılır. Bellek açısından daha az yer kaplar ve performans açısından List ve MutableList'ten daha hızlı olabilir.
  • Özellikler:
  • size: Dizinin boyutunu verir.
  • get(index): Belirtilen indeksteki elemanı döner.
  • set(index, value): Belirtilen indeksteki elemanı değiştirir.
  • Dizi boyutu sabittir ama elemanları değiştirilebilir.
  • Örnek:
val array = arrayOf(1, 2, 3)
array[0] = 10 // [10, 2, 3]

Özet:

  • List: Değiştirilemez ve sabit içerikli bir liste.
  • MutableList: Dinamik olarak eleman eklenip çıkarılabilen liste.
  • ArrayList: MutableList gibi çalışır ama Java'dan gelir ve optimize edilebilir.
  • Array: Sabit boyutlu, fakat elemanları değiştirilebilen bir dizi.
Kotlin’de Liste Türlerinin Karşılaştırması

--

--

Şevval Özdamar
Şevval Özdamar

Written by Şevval Özdamar

Computer Engineer - Android Developer

No responses yet