Takip et

Jetpack ViewModels’ta AutoCloseable ile Kaynak Temizliği

Android uygulamalarında performans ve kararlılık, kaynak yönetiminin ne kadar etkili yapıldığıyla doğrudan ilişkilidir. Özellikle ViewModel’lar, yaşam döngüsüne duyarlı mimarileri nedeniyle kaynak temizliğinin hayati önem taşıdığı bileşenlerdir. Bu yazımızda, Jetpack ViewModels’ta AutoCloseable arayüzünü kullanarak otomatik kaynak temizliğinin nasıl sağlanacağını, uygulamanızın bellek sızıntılarından nasıl korunacağını ve kodunuzu nasıl daha düzenli hale getireceğinizi adım adım inceleyeceğiz. Modern Android geliştiricilerinin sıkça karşılaştığı bu sorunlara pratik çözümler sunarak, hem yeni başlayanlar hem de deneyimli geliştiriciler için kapsamlı bir rehber hazırladık.

Android uygulamaları geliştirirken, kullanıcı deneyimini doğrudan etkileyen en önemli faktörlerden biri kuşkusuz uygulamanın performansıdır. Performansı düşüren, uygulamanın çökmesine neden olan veya pil ömrünü olumsuz etkileyen unsurların başında ise bellek sızıntıları ve düzgün yönetilmeyen kaynaklar gelir. Özellikle Jetpack Compose ve MVVM (Model-View-ViewModel) mimarisiyle geliştirilen modern Android uygulamalarında ViewModel‘lar, kullanıcı arayüzü (UI) verilerini yaşam döngüsüne duyarlı bir şekilde saklamak ve işlemek için vazgeçilmez bir role sahiptir. Bu bağlamda, ViewModel içinde tutulan kaynakların doğru bir şekilde yönetilmesi, uygulamanın genel sağlığı için hayati bir önem taşımaktadır.

Bir ViewModel, ilgili Activity veya Fragment yaşam döngüsü boyunca hayatta kalır ve yapılandırma değişikliklerinde (örneğin ekran döndürme) yeniden oluşturulmaz. Bu, ViewModel‘in veri tutma yeteneğini mükemmel kılar, ancak aynı zamanda potansiyel tehlikeleri de beraberinde getirir. Eğer bir ViewModel içinde, yaşam döngüsü sona erdiğinde (yani Activity veya Fragment tamamen yok edildiğinde) serbest bırakılması gereken kaynaklar (veritabanı bağlantıları, ağ istemcileri, akışlar, RxJava Disposable‘lar veya Coroutine Job‘ları gibi) düzgün bir şekilde kapatılmazsa, bu durum bellek sızıntılarına yol açar. Bellek sızıntıları, uygulamanızın gereksiz yere bellek tüketmesine, zamanla yavaşlamasına ve hatta “Out Of Memory” (OOM) hataları vererek çökmesine neden olabilir. Bu nedenle, ViewModel içindeki kaynakların temizliği, uygulamanın kararlılığını ve performansını doğrudan etkileyen kritik bir adımdır.

Genellikle, bu tür kaynakların temizliği ViewModel‘in onCleared() metodunda manuel olarak yapılır. Ancak bu manuel yaklaşım, bazı dezavantajları da beraberinde getirir. Örneğin, birden fazla kaynağınız varsa ve her birini manuel olarak temizlemeniz gerekiyorsa, kodunuz hızla büyüyebilir ve karmaşık hale gelebilir. Ayrıca, her yeni kaynak eklediğinizde onCleared() metodunu güncellemeyi unutma riski de her zaman mevcuttur. Bu durum, özellikle büyük ekiplerde veya karmaşık projelerde hata yapma olasılığını artırır ve bellek sızıntılarına yol açabilecek gizli hataların ortaya çıkmasına zemin hazırlar. İşte tam bu noktada, Java’nın AutoCloseable arayüzü ve Kotlin’in modern yapısıyla birleştiğinde otomatik kaynak temizliği devreye girerek bu sorunlara zarif ve etkili bir çözüm sunar. Bu yaklaşımla, kaynak yönetimini daha düzenli, daha az hataya açık ve daha sürdürülebilir hale getirebiliriz. Dolayısıyla, ViewModel‘larda kaynak yönetimi, sadece bir iyi uygulama olmaktan öte, sağlam ve güvenilir bir Android uygulaması geliştirmenin temel bir direğidir.

AutoCloseable Arayüzü Nedir ve Nasıl Çalışır?

AutoCloseable arayüzü, Java 7 ile birlikte tanıtılan ve özellikle kaynak yönetimi konularında geliştiricilere büyük kolaylık sağlayan bir yapıdır. Temel amacı, bir kaynağın (örneğin bir dosya akışı, veritabanı bağlantısı veya ağ bağlantısı gibi) işi bittiğinde otomatik olarak kapatılmasını sağlamaktır. Bu, manuel try-finally bloklarının getirdiği karmaşıklığı azaltır ve kaynak temizliğini daha güvenli ve okunaklı hale getirir. Bir sınıfın AutoCloseable arayüzünü uygulaması demek, o sınıfın bir close() metoduna sahip olması gerektiği anlamına gelir. Bu close() metodu, ilgili kaynağın serbest bırakılmasından sorumlu olacaktır.

Peki, AutoCloseable tam olarak nasıl çalışır? Java’da bu arayüz, özellikle “try-with-resources” ifadesiyle birlikte kullanıldığında tüm gücünü ortaya koyar. “try-with-resources” bloğu, bir veya daha fazla kaynağı bildirmenize olanak tanır ve bu kaynaklar, bloğun sonunda otomatik olarak kapatılır, hatta bir istisna oluşsa bile. Bu mekanizma, kaynakların her zaman düzgün bir şekilde temizlenmesini garanti eder. Android geliştirme bağlamında, bu özellik ViewModel‘lar gibi yaşam döngüsüne sahip bileşenlerdeki kaynak temizliği için müthiş bir potansiyel sunar.


import java.io.Closeable; // Closeable AutoCloseable'ı extend eder

public class MyResource implements AutoCloseable {
    private String resourceName;

    public MyResource(String name) {
        this.resourceName = name;
        System.out.println(resourceName + " kaynağı oluşturuldu.");
    }

    public void doWork() {
        System.out.println(resourceName + " kaynağı ile çalışılıyor...");
    }

    @Override
    public void close() throws Exception {
        System.out.println(resourceName + " kaynağı kapatılıyor.");
        // Kaynağı serbest bırakma mantığı burada yer alır.
        // Örneğin, veritabanı bağlantısını kapatma, akışı sonlandırma vb.
    }

    public static void main(String[] args) {
        try (MyResource resource1 = new MyResource("Veritabanı Bağlantısı")) {
            resource1.doWork();
        } catch (Exception e) {
            e.printStackTrace();
        }

        // try-with-resources bloğu bittiğinde resource1 otomatik olarak kapatılacak.
        // Manuel kapatmaya gerek kalmaz.
    }
}

Yukarıdaki örnekte, MyResource sınıfı AutoCloseable arayüzünü uyguluyor. close() metodu, kaynağın serbest bırakılmasından sorumludur. main metodunda, try-with-resources bloğu içinde MyResource nesnesi oluşturuluyor. Bloğun sonunda (ister normal sonlansın ister bir istisna fırlatılsın), close() metodu otomatik olarak çağrılarak kaynak temizleniyor. Bu yaklaşım, kaynak yönetimindeki boilerplate kodu önemli ölçüde azaltır ve geliştiricinin daha çok iş mantığına odaklanmasını sağlar. Ancak, ViewModel'lar doğrudan bir try-with-resources bloğu içinde yaşamadığı için, AutoCloseable'ın ViewModel yaşam döngüsüyle nasıl entegre edileceği özel bir yaklaşımla ele alınmalıdır. İşte bu noktada Jetpack'in androidx.lifecycle.ViewModel sınıfının sunduğu olanaklar devreye girer. Geliştiriciler, ViewModel içinde tutulan AutoCloseable nesnelerini, ViewModel'in onCleared() metodunda manuel olarak değil, daha organize ve otomatik bir şekilde temizlemenin yollarını aramaktadırlar. Bu sayede, uygulamanın bellek sızıntılarına karşı daha dirençli olması sağlanır ve kaynak temizliği süreci daha az hataya açık hale gelir.

AutoCloseable ve Closeable Arasındaki Farklar Nelerdir?

Java ekosisteminde kaynak yönetimi denince akla gelen iki önemli arayüz vardır: java.lang.AutoCloseable ve java.io.Closeable. Her ikisi de kaynakların kapatılmasını sağlayan bir close() metoduna sahip olsa da, aralarında önemli farklar bulunur ve bu farkları anlamak, doğru arayüzü seçmek için kritik öneme sahiptir.

Öncelikle, java.io.Closeable arayüzüne bakalım. Bu arayüz, Java 5 ile birlikte tanıtılmıştır ve genellikle I/O (giriş/çıkış) kaynakları için tasarlanmıştır. Örneğin, FileInputStream, FileOutputStream, BufferedReader gibi sınıflar Closeable arayüzünü uygular. Closeable arayüzünün close() metodu, imzası itibarıyla void close() throws IOException şeklindedir. Yani, bu metodun yalnızca IOException fırlatabileceği varsayılır. Bu arayüz, "try-finally" bloklarında kaynakları manuel olarak kapatmak için yaygın olarak kullanılmıştır. Ancak, bir I/O kaynağı dışındaki bir kaynağı kapatırken farklı türde istisnalar fırlatma ihtiyacı doğabilir ve Closeable bu senaryolar için uygun değildir.

Diğer yandan, java.lang.AutoCloseable arayüzü, Java 7 ile birlikte tanıtılan daha genel bir arayüzdür ve Closeable arayüzünün temelini oluşturur. Aslında, Closeable arayüzü AutoCloseable'ı extend eder. AutoCloseable arayüzünün close() metodu imzası void close() throws Exception şeklindedir. Bu, AutoCloseable'ı uygulayan bir sınıfın close() metodunun herhangi bir istisna türünü fırlatabileceği anlamına gelir. Bu esneklik, AutoCloseable'ı I/O kaynakları dışındaki daha geniş bir kaynak yelpazesi için uygun hale getirir. Örneğin, bir veritabanı bağlantısı, bir ağ soketi, bir thread havuzu veya özel bir kaynak yöneticisi gibi her türlü "kapatılabilir" kaynağı temsil edebilir.

İşte bu iki arayüz arasındaki temel farkları özetleyen bir tablo:

Kavram Açıklama
java.io.Closeable Java 5'te tanıtıldı. Genellikle I/O kaynakları için kullanılır. close() metodu IOException fırlatır.
java.lang.AutoCloseable Java 7'de tanıtıldı. Daha genel bir arayüzdür. close() metodu Exception fırlatabilir (daha genel bir istisna). Closeable, AutoCloseable'ı extend eder.

AutoCloseable'ın en büyük avantajı, "try-with-resources" ifadesiyle birlikte kullanılabilmesidir. Bu mekanizma, bir istisna fırlatılsa bile kaynakların her zaman otomatik olarak kapatılmasını garanti eder. Closeable da "try-with-resources" ile kullanılabilir çünkü AutoCloseable'ı extend eder, ancak close() metodunun fırlattığı istisna türü açısından daha kısıtlıdır. Dolayısıyla, bir kaynak kapatılırken IOException dışında başka bir istisnanın fırlatılma olasılığı varsa veya kaynağınız bir I/O kaynağı değilse, AutoCloseable kullanmak daha doğru ve esnek bir tercihtir. Modern Android geliştirmesinde, genellikle AutoCloseable, kaynak temizliği için daha genel ve güçlü bir seçenek olarak karşımıza çıkar.

ViewModel'larda AutoCloseable Kullanımı: Adım Adım Rehber

ViewModel'lar içinde AutoCloseable kullanmanın amacı, manuel onCleared() temizleme işlemlerinin yükünü hafifletmek ve kaynak yönetimini daha otomatik, güvenli ve hatasız hale getirmektir. Bu bölümde, Jetpack ViewModel'larında AutoCloseable'ı nasıl kullanacağımızı adım adım inceleyeceğiz. Bu rehber, özellikle Kotlin Coroutines ile çalışan geliştiriciler için oldukça faydalı olacaktır.

İlk olarak, Jetpack Lifecycle kütüphanesinin sunduğu androidx.lifecycle.ViewModel sınıfı, onCleared() adında bir metod sunar. Bu metod, ViewModel'ın artık kullanılmadığı ve yok edileceği zaman çağrılır. Bizim hedefimiz, AutoCloseable nesnelerimizi bu onCleared() metodunu otomatik olarak çağıracak bir mekanizmaya bağlamaktır. Neyse ki, Jetpack ekibi bu ihtiyacı düşünerek androidx.lifecycle:lifecycle-viewmodel-ktx kütüphanesine AutoCloseable entegrasyonu için harika bir özellik eklemiştir.

ViewModel sınıfının bir yapıcı metodu (constructor) bulunmaktadır ve bu yapıcı metod, vararg AutoCloseable argümanı alabilir. Bu sayede, ViewModel oluşturulurken kendisine geçirilen tüm AutoCloseable nesneleri, ViewModel'in yaşam döngüsü sona erdiğinde (yani onCleared() çağrıldığında) otomatik olarak kapatılır. İşte bu, bellek sızıntılarını önlemenin ve kaynak temizliğini otomatize etmenin sihirli yoludur.

Şimdi bu süreci adım adım inceleyelim:

  1. Bağımlılığı Ekleme: Öncelikle, projenizin build.gradle (Module: app) dosyasına gerekli kütüphane bağımlılığını eklemeniz gerekir:

    
    dependencies {
        // ViewModel KTX kütüphanesi
        implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2" // Veya daha yeni versiyon
        // Diğer bağımlılıklar...
    }
            


    Bu kütüphane, ViewModel'in AutoCloseable entegrasyonu için gerekli uzantı fonksiyonlarını ve yapıları içerir.

  2. Kendi AutoCloseable Kaynağınızı Oluşturma: Diyelim ki, bir veritabanı bağlantısını veya özel bir akış kaynağını yönetmek istiyorsunuz. Bu kaynağı temsil eden bir sınıf oluşturup AutoCloseable arayüzünü uygulamanız gerekir.

    
    import java.io.Closeable // Genellikle Closeable da AutoCloseable'ı extend ettiği için yeterlidir
    
    class DatabaseConnection(private val dbName: String) : Closeable {
        init {
            println("[$dbName] Veritabanı bağlantısı açıldı.")
            // Veritabanı bağlantısını burada kurun
        }
    
        fun executeQuery(query: String) {
            println("[$dbName] Sorgu çalıştırılıyor: $query")
            // Sorgu çalıştırma mantığı
        }
    
        override fun close() {
            println("[$dbName] Veritabanı bağlantısı kapatılıyor.")
            // Veritabanı bağlantısını burada kapatın
        }
    }
            


    Yukarıdaki örnekte Closeable kullandık, çünkü Closeable, AutoCloseable'ı extend eder ve bu bağlamda da çalışır. Ancak genel olarak, farklı istisnalar fırlatabilen kaynaklar için AutoCloseable doğrudan kullanılabilir.

  3. ViewModel İçinde Kaynağı Kullanma ve Kaydetme: Şimdi bu DatabaseConnection nesnesini ViewModel içinde oluşturalım ve ViewModel'a otomatik temizleme için kaydedelim.

    
    import androidx.lifecycle.ViewModel
    import androidx.lifecycle.viewModelScope
    import kotlinx.coroutines.delay
    import kotlinx.coroutines.launch
    import java.io.Closeable
    
    // 1. Kendi AutoCloseable objemizi tanımladık (yukarıda DatabaseConnection)
    
    // 2. ViewModel'ı bu AutoCloseable objelerle birlikte oluşturma
    class MyDataViewModel : ViewModel(
        DatabaseConnection("UserDB"), // AutoCloseable 1
        DatabaseConnection("ProductDB") // AutoCloseable 2
    ) {
        init {
            println("MyDataViewModel oluşturuldu.")
            viewModelScope.launch {
                delay(2000) // 2 saniye beklet
                println("ViewModel içerisinde asenkron işlem tamamlandı.")
            }
        }
    
        fun loadUserData() {
            println("Kullanıcı verileri yükleniyor...")
            // UserDB bağlantısı üzerinden veri çekilebilir.
            // VeritabanıConnection nesnelerine doğrudan erişemeyiz, sadece oluşturulurken kaydettik.
            // Eğer ViewModel içinde doğrudan erişim gerekiyorsa, onu property olarak tutmalı ve onCleared'da manuel kapatmalıydık.
            // Veya daha gelişmiş bir yapılandırma ile bu mümkün olabilir.
        }
    
        override fun onCleared() {
            super.onCleared()
            // ViewModel'ın yapıcı metoduna geçirilen AutoCloseable'lar otomatik olarak burada çağrılır.
            println("MyDataViewModel temizleniyor.")
        }
    }
            


    Bu örnekte, MyDataViewModel'ın yapıcı metoduna doğrudan iki adet DatabaseConnection nesnesi geçirdik. ViewModel yaşam döngüsü sona erdiğinde, bu iki bağlantının close() metotları otomatik olarak çağrılacaktır. Bu, geliştiricinin her bir bağlantı için onCleared() içinde manuel olarak connection.close() yazma ihtiyacını ortadan kaldırır. Bu yaklaşım, kodun daha temiz, daha güvenli ve daha sürdürülebilir olmasını sağlar. Ayrıca, kaynak ekleme veya çıkarma durumlarında onCleared() metodunu manuel olarak güncelleme ihtiyacı da ortadan kalkar, bu da hata yapma olasılığını büyük ölçüde azaltır.

    Bu yöntem, özellikle birden fazla AutoCloseable kaynağı yönetmeniz gerektiğinde büyük kolaylık sağlar. Artık ViewModel'larınızdaki kaynak temizliği için endişelenmenize gerek kalmayacak, sistem bunu sizin için halledecek.

    Pratik Bir Örnek: Veritabanı Bağlantısı ve Ağ İsteği Yönetimi

    Yukarıdaki teorik bilgileri pratiğe dökelim ve gerçek dünya senaryolarında AutoCloseable'ın ViewModel'lar içinde nasıl kullanılabileceğini gösterelim. Özellikle veritabanı bağlantıları ve uzun süreli ağ istekleri gibi kaynaklar, ViewModel yaşam döngüsü sona erdiğinde düzgün bir şekilde kapatılmazsa ciddi bellek sızıntılarına yol açabilir. İşte bu sorunu çözmek için AutoCloseable'ı kullanarak daha sağlam bir yaklaşım sunan kapsamlı bir örnek.

    İlk olarak, veritabanı bağlantımızı temsil eden bir AutoCloseable sınıf oluşturalım. Bu sınıf, bir simülasyon amacıyla basit bir bağlantı açma ve kapatma işlemi yapacaktır.

    
    import java.io.Closeable
    
    // Veritabanı bağlantısını temsil eden AutoCloseable sınıf
    class DbManager(private val dbName: String) : Closeable {
        private var isConnected: Boolean = false
    
        init {
            connect()
        }
    
        private fun connect() {
            println("[$dbName] Veritabanı bağlantısı başlatılıyor...")
            Thread.sleep(500) // Bağlantı kurma simülasyonu
            isConnected = true
            println("[$dbName] Veritabanı bağlantısı başarılı.")
        }
    
        fun queryData(tableName: String): List {
            if (!isConnected) {
                println("[$dbName] Hata: Veritabanı bağlı değil.")
                return emptyList()
            }
            println("[$dbName] '$tableName' tablosundan veri sorgulanıyor.")
            return listOf("data_item_1", "data_item_2") // Sahte veri
        }
    
        override fun close() {
            if (isConnected) {
                println("[$dbName] Veritabanı bağlantısı kapatılıyor...")
                Thread.sleep(200) // Kapatma simülasyonu
                isConnected = false
                println("[$dbName] Veritabanı bağlantısı kapatıldı.")
            }
        }
    }
    

    Şimdi de, bir ağ isteğini veya uzun süreli bir soket bağlantısını temsil eden başka bir AutoCloseable sınıf oluşturalım. Bu, genellikle Retrofit veya OkHttp gibi kütüphanelerle yönetilen ancak kendi manuel kaynak temizliğine ihtiyaç duyan durumları simüle edebilir.

    
    import java.io.Closeable
    import kotlinx.coroutines.Job
    import kotlinx.coroutines.delay
    import kotlinx.coroutines.launch
    import androidx.lifecycle.viewModelScope // Bu sınıf ViewModel dışında kullanılamaz, örnek için buraya taşıdık.
    import androidx.lifecycle.ViewModel
    
    // Ağ isteğini veya bir akışı temsil eden AutoCloseable sınıf
    class NetworkClient(private val endpoint: String, private val coroutineScope: ViewModel) : Closeable {
        private var activeJob: Job? = null
        private var isConnected: Boolean = false
    
        init {
            connect()
        }
    
        private fun connect() {
            println("[NetworkClient:$endpoint] Ağ bağlantısı kuruluyor...")
            // Gerçekte burada bir soket veya WebRTC bağlantısı kurulabilir.
            // Veya belirli bir API için uzun ömürlü bir client başlatılabilir.
            isConnected = true
            println("[NetworkClient:$endpoint] Ağ bağlantısı başarılı.")
        }
    
        fun fetchData(): String {
            if (!isConnected) {
                println("[NetworkClient:$endpoint] Hata: Ağ istemcisi bağlı değil.")
                return "Error"
            }
            println("[NetworkClient:$endpoint] Veri çekiliyor...")
            // Gerçek bir ağ isteği veya uzun süreli veri akışı burada başlatılabilir.
            return "Received data from $endpoint"
        }
    
        // Bu metod ViewModel'ın yapıcı metoduna geçilemediği için
        // ViewModelScope dışındaki bir AutoCloseable'ı örneklendirmek için kullandık.
        // Gerçekte NetworkClient içinde doğrudan bir CoroutineScope referansı tutmak yerine,
        // gelen Scope üzerinde Job oluşturmak daha iyi bir yaklaşımdır.
        // Ancak burada ViewModel yapıcı metoduna geçişi göstermek adına basit tutuldu.
        fun startLongRunningTask() {
            activeJob = (coroutineScope as ViewModel).viewModelScope.launch { // CoroutineScope almak yerine ViewModel referansıyla viewModelScope'a erişildi.
                println("[NetworkClient:$endpoint] Uzun süreli görev başlatıldı.")
                delay(5000) // Uzun süreli bir işlem simülasyonu
                println("[NetworkClient:$endpoint] Uzun süreli görev tamamlandı.")
            }
        }
    
        override fun close() {
            if (isConnected) {
                println("[NetworkClient:$endpoint] Ağ bağlantısı kapatılıyor...")
                activeJob?.cancel() // Devam eden bir coroutine varsa iptal et
                isConnected = false
                println("[NetworkClient:$endpoint] Ağ bağlantısı kapatıldı.")
            }
        }
    }
    

    Şimdi bu iki AutoCloseable kaynağını bir ViewModel içinde nasıl kullanacağımızı görelim:

    
    import androidx.lifecycle.ViewModel
    import androidx.lifecycle.viewModelScope
    import kotlinx.coroutines.launch
    import java.io.Closeable
    
    class DashboardViewModel : ViewModel(
        DbManager("AnalyticsDB"), // AutoCloseable veritabanı yöneticisi
        DbManager("UserPreferencesDB") // Başka bir AutoCloseable veritabanı yöneticisi
        // NetworkClient gibi bir objeyi de buraya geçirebiliriz
        // Ancak NetworkClient, ViewModel'ın kendisini parametre olarak aldığı için
        // (viewModelScope'a erişim için) bu doğrudan burada yapılamaz.
        // Bunun yerine, ViewModel içinde oluşturup onCleared'da manuel kapatma ya da
        // daha gelişmiş bir dependency injection (bağımlılık enjeksiyonu) yaklaşımı gerekir.
        // Bu örnekte, sadece DbManager'ları ViewModel yapıcı metoduna geçiriyoruz.
    ) {
        // Eğer NetworkClient gibi bir objenin ViewModel içinde doğrudan erişilebilir olması gerekiyorsa,
        // onu bir property olarak tanımlamalıyız. Bu durumda, onCleared'da manuel olarak kapatılmalıdır.
        // Veya, NetworkClient'ı ViewModel dışından alıp, kendi içinde sadece bir Job başlatmasını sağlayabiliriz.
        private val apiService = NetworkClient("api.example.com", this) // ViewModel'ın kendisini scope olarak verdik
    
        init {
            println("DashboardViewModel oluşturuldu.")
            // ViewModelScope içindeki coroutine'ler otomatik olarak ViewModel temizlendiğinde iptal edilir.
            viewModelScope.launch {
                println("ViewModelScope'ta başlangıç görevi başlatıldı.")
                apiService.startLongRunningTask() // Ağ istemcisini kullanarak uzun süreli bir görev başlat
                println("Ağ istemcisi başlatıldı.")
    
                // DbManager'lar yapıcıda kaydedildiği için burada doğrudan erişilemezler.
                // Eğer erişim gerekiyorsa, ViewModel içinde bir property olarak tanımlanmalı ve
                // onCleared'da manuel kapatılmalıdır. Veya Factory deseni ile enjekte edilmelidir.
                // Örnek:
                // val userDbManager = DbManager("UserDB")
                // bu durumda 'this' ile birlikte userDbManager'ı da ViewModel'ın yapıcı metoduna geçiremezdik.
                // Kaynaklara ViewModel içinde erişim gerekiyorsa (ki genellikle gereklidir),
                // onları ViewModel'ın property'si olarak tanımlayıp manuel onCleared'da kapatmak hala geçerli bir yöntemdir.
                // Otomatik AutoCloseable mekanizması, ViewModel'ın dışında oluşturulan ve sadece temizlenmesi gereken
                // kaynaklar için daha uygundur.
            }
        }
    
        fun loadDashboardData() {
            println("Gösterge paneli verileri yükleniyor...")
            // apiService.fetchData() çağrılabilir
            // DbManager'lardan sorgu çekilebilir (eğer property olarak tanımlanmışlarsa)
        }
    
        override fun onCleared() {
            super.onCleared()
            // ViewModel'ın yapıcı metoduna geçirilen DbManager'lar otomatik olarak kapatılır.
            // Manuel olarak property olarak tanımlanan apiService'i de burada kapatmalıyız.
            apiService.close()
            println("DashboardViewModel temizleniyor ve tüm kaynaklar kapatıldı.")
        }
    }
    

    Bu örnekte, DashboardViewModel'in yapıcı metoduna iki adet DbManager nesnesi doğrudan geçiriliyor. Bu DbManager'lar, ViewModel yaşam döngüsü sona erdiğinde otomatik olarak kapatılacak. NetworkClient ise ViewModel içinde bir property olarak tanımlandı ve onCleared() metodunda manuel olarak kapatılıyor. Bu durum, bize önemli bir bakış açısı sunar:

    • Eğer bir AutoCloseable nesnesine ViewModel içinde doğrudan erişiminiz gerekmiyorsa ve sadece yaşam döngüsü sonunda temizlenmesini istiyorsanız, onu ViewModel'ın yapıcı metoduna geçirmek en temiz yoldur.
    • Eğer AutoCloseable bir nesneye ViewModel içinde erişiminiz gerekiyorsa (tıpkı apiService örneğinde olduğu gibi), onu bir property olarak tanımlamanız ve onCleared() metodunda manuel olarak kapatmanız gerekebilir. Veya daha gelişmiş bağımlılık enjeksiyonu (DI) çözümleriyle bu durumu farklı yönetebilirsiniz.

    Bu pratik örnek, AutoCloseable'ın kaynak temizliğini nasıl basitleştirdiğini ve ViewModel'larınızın bellek sızıntılarına karşı nasıl daha dirençli hale getirilebileceğini açıkça göstermektedir. Bu yöntemleri uygulayarak, Android uygulamanızın performansını ve kararlılığını önemli ölçüde artırabilirsiniz.

    Daha Temiz Kod İçin androidx.lifecycle.viewModelScope Kullanımı

    Modern Android geliştirmesinde, asenkron işlemler ve eşzamanlılık yönetimi, performanslı ve duyarlı uygulamalar oluşturmanın temelini oluşturur. Kotlin Coroutines, bu alanda geliştiricilere güçlü ve esnek bir araç sunar. Jetpack ise Coroutines ile uyumlu çalışacak şekilde tasarlanmış viewModelScope gibi uzantılar sunarak, ViewModel'lardaki asenkron görev yönetimini çok daha kolay ve güvenli hale getirmiştir.

    androidx.lifecycle.viewModelScope, ViewModel'ınıza bağlı özel bir CoroutineScope'tur. Bu kapsamda başlatılan tüm coroutine'ler, ViewModel temizlendiğinde (yani onCleared() metodu çağrıldığında) otomatik olarak iptal edilir. Bu özellik, bellek sızıntılarını önlemek ve gereksiz kaynak tüketimini durdurmak açısından son derece önemlidir. Daha önceki manuel Job yönetimi, Disposable iptali gibi karmaşık işlemleri büyük ölçüde basitleştirir ve kodu daha temiz hale getirir.

    viewModelScope Neden Önemlidir?

    1. Otomatik İptal: viewModelScope içinde başlatılan coroutine'ler, ViewModel yaşam döngüsü sona erdiğinde otomatik olarak iptal edilir. Bu, manuel olarak Job.cancel() çağırma ihtiyacını ortadan kaldırır.
    2. Bellek Sızıntılarını Önleme: Uzun süreli çalışan bir coroutine, ViewModel yok edildikten sonra bile çalışmaya devam ederse, UI bileşenlerine veya diğer yaşam döngüsüne bağlı nesnelere referans tutarak bellek sızıntılarına neden olabilir. viewModelScope bu riski ortadan kaldırır.
    3. Daha Temiz Kod: Manuel Job yönetimi veya Disposable havuzları gibi boilerplate kodların önüne geçerek daha okunaklı ve sürdürülebilir bir kod yazmanızı sağlar.
    4. Entegre Yaşam Döngüsü Yönetimi: Jetpack Lifecycle kütüphanesi ile tamamen entegredir, bu da Android'in yaşam döngüsü felsefesiyle uyumlu bir geliştirme deneyimi sunar.

    viewModelScope Nasıl Kullanılır?

    Kullanımı oldukça basittir. ViewModel sınıfınızın herhangi bir metodunda veya init bloğunda doğrudan viewModelScope.launch { ... } veya viewModelScope.async { ... } ifadelerini kullanabilirsiniz.

    
    import androidx.lifecycle.ViewModel
    import androidx.lifecycle.viewModelScope
    import kotlinx.coroutines.delay
    import kotlinx.coroutines.launch
    
    class DataFetcherViewModel : ViewModel() {
    
        init {
            println("DataFetcherViewModel oluşturuldu.")
            // ViewModel oluşturulur oluşturulmaz bir veri çekme işlemi başlatılabilir.
            // Bu coroutine, ViewModel temizlendiğinde otomatik olarak iptal edilecek.
            viewModelScope.launch {
                fetchInitialData()
            }
        }
    
        suspend fun fetchInitialData() {
            println("Başlangıç verileri çekiliyor... (viewModelScope içinde)")
            try {
                delay(3000) // Ağ isteği veya yoğun bir işlem simülasyonu
                println("Başlangıç verileri başarıyla çekildi.")
            } catch (e: Exception) {
                println("Veri çekme sırasında hata oluştu: ${e.message}")
            }
        }
    
        fun refreshData() {
            viewModelScope.launch {
                println("Veriler yenileniyor... (viewModelScope içinde)")
                try {
                    delay(2000) // Daha kısa bir yenileme süresi
                    println("Veriler başarıyla yenilendi.")
                } catch (e: Exception) {
                    println("Veri yenileme sırasında hata oluştu: ${e.message}")
                }
            }
        }
    
        override fun onCleared() {
            super.onCleared()
            // ViewModelScope içindeki tüm coroutine'ler burada otomatik olarak iptal edilir.
            println("DataFetcherViewModel temizlendi. Tüm viewModelScope coroutine'leri iptal edildi.")
        }
    }
    

    Bu örnekte, DataFetcherViewModel içinde başlatılan tüm asenkron işlemler viewModelScope kullanılarak yönetilir. ViewModel artık ihtiyaç duyulmadığında ve onCleared() metodu çağrıldığında, viewModelScope içindeki tüm coroutine'ler otomatik olarak iptal edilir. Bu, uygulamanızın gereksiz işlem yapmasını engeller ve bellek sızıntısı riskini minimize eder. AutoCloseable ile birlikte kullanıldığında ise, hem senkron kaynakların hem de asenkron işlemlerin temizliği çok daha düzenli ve güvenli hale gelir. Kısacası, viewModelScope, ViewModel'larınızda Coroutines kullanırken kaynak temizliğini düşünmenize gerek kalmayan, geliştirici dostu bir çözümdür.

    İleri Düzey Uygulamalar ve Yaygın Hatalardan Kaçınma Yolları

    AutoCloseable ve viewModelScope kullanarak kaynak yönetimini otomatikleştirmek, modern Android uygulamalarında önemli bir adımdır. Ancak, bu araçları en verimli şekilde kullanmak ve yaygın hatalardan kaçınmak için bazı ileri düzey uygulamaları ve dikkat edilmesi gereken noktaları bilmek gerekir. Bu bölüm, daha karmaşık senaryoları ele alırken karşılaşabileceğiniz zorlukları aşmanıza yardımcı olacak ipuçları sunmaktadır.

    Dependency Injection (DI) ile AutoCloseable Kaynakları Yönetme

    Büyük projelerde, ViewModel'ların doğrudan AutoCloseable nesnelerini oluşturması yerine, bunları bağımlılık enjeksiyonu (DI) framework'leri (örneğin Hilt, Koin, Dagger) aracılığıyla alması daha yaygın ve tercih edilen bir yöntemdir. Bu sayede, test edilebilirlik artar ve sorumluluklar daha net ayrılır.

    Peki, DI ile enjekte edilen AutoCloseable bir kaynağı ViewModel'ın yaşam döngüsüne nasıl bağlarız? İşte burada androidx.lifecycle.CloseableViewModel veya AutoCloseable mekanizması devreye girer. Hilt gibi framework'ler, @ViewModelScoped anotasyonunu kullanarak bir bağımlılığın ViewModel'ın yaşam döngüsüyle sınırlı olmasını sağlayabilir. Eğer bağımlılığınız AutoCloseable ise, Hilt gibi framework'ler bu kaynağın temizliğini otomatik olarak yönetmeye yardımcı olabilir. Ancak, bu her zaman doğrudan bir entegrasyon olmayabilir ve yine de ViewModel'in yapıcı metoduna geçiş veya onCleared() içinde manuel kapatma gerekebilir.

    
    // Örnek: Hilt ile enjekte edilen bir DatabaseManager'ın AutoCloseable olması
    @Module
    @InstallIn(ViewModelComponent::class)
    object DatabaseModule {
        @Provides
        @ViewModelScoped // Bu modülün ViewModel yaşam döngüsüyle sınırlı olduğunu belirtir
        fun provideDatabaseManager(@ApplicationContext context: Context): DbManager {
            return DbManager("AppDB") // DbManager, Closeable/AutoCloseable olduğu için
                                      // ViewModelComponent sona erdiğinde otomatik kapanması beklenebilir.
                                      // Ancak, Hilt doğrudan AutoCloseable'ı otomatik kapatmaz.
                                      // ViewModel'ın yapıcı metoduna geçmek daha güvenli bir yaklaşımdır.
        }
    }
    
    // ViewModel'ın bu manager'ı alması
    class AdvancedDataViewModel @Inject constructor(
        private val dbManager: DbManager // Hilt tarafından sağlanır
    ) : ViewModel(dbManager) { // dbManager'ı ViewModel'ın yapıcı metoduna geçirdik
    
        init {
            println("AdvancedDataViewModel oluşturuldu.")
            dbManager.queryData("Users")
        }
        // ... diğer işlemler
    }
    

    Bu örnekte, dbManager'ı ViewModel'ın yapıcı metoduna geçirerek, ViewModel'ın onCleared() metodu çağrıldığında otomatik olarak temizlenmesini sağladık. Bu, DI ile AutoCloseable kaynaklarını yönetirken en güvenli ve önerilen yoldur.

    Yaygın Hatalardan Kaçınma Yolları

    1. AutoCloseable Olmayan Kaynakları Unutmayın: Sadece AutoCloseable arayüzünü uygulayan nesneler otomatik olarak temizlenir. Eğer özel bir kaynağınız varsa ve AutoCloseable'ı uygulamıyorsa, onCleared() metodunda manuel olarak temizlemeyi unutmayın. Örneğin, RxJava Disposable'lar veya kendi özel kaynaklarınız.
    2. Doğru Kapsamı Seçin: viewModelScope, ViewModel'ın yaşam döngüsüyle sınırlıdır. Eğer bir görevin ViewModel yaşam döngüsünden daha uzun sürmesi gerekiyorsa (örneğin arka planda bir dosya indirme), bunun için farklı bir CoroutineScope (örneğin applicationScope) kullanmalısınız. Ancak bu durumda, kaynak temizliğini manuel olarak yönetmeniz gerekecektir.
    3. Kaynakları Erken Kapatmayın: Bir AutoCloseable kaynağı çok erken kapatmak, uygulamanızda çalışma zamanı hatalarına yol açabilir. Kaynağın sadece işi bittiğinde veya ilgili ViewModel yok edildiğinde kapatıldığından emin olun.
    4. İstisna Yönetimi: AutoCloseable.close() metodu bir Exception fırlatabilir. Kaynakları kapatırken olası istisnaları ele almayı unutmayın, aksi takdirde uygulamanız beklenmedik şekilde çökebilir. Genellikle, kaynak kapatma hataları loglanır ancak uygulamanın çökmesine izin verilmez.

      
      override fun close() {
          try {
              // Kaynağı kapatma mantığı
          } catch (e: Exception) {
              println("Kaynağı kapatırken hata oluştu: ${e.message}")
              // Hata loglama veya bildirim
          }
      }
              

    5. Test Edilebilirlik: Kaynak yönetimi mantığınızı unit testler ile doğrulamak, hataları erken aşamada tespit etmenin en iyi yoludur. ViewModel'ın onCleared() metodunu çağırarak ve AutoCloseable kaynaklarınızın close() metodunun çağrılıp çağrılmadığını kontrol ederek testler yazın.

    Uzman İpucu: Karmaşık durumlarda, bir CompositeCloseable sınıfı oluşturarak birden fazla AutoCloseable nesnesini tek bir birim olarak yönetebilirsiniz. Bu, onCleared() içinde daha az boilerplate kod yazmanızı sağlar ve kodu daha okunaklı hale getirir.

    Bu ileri düzey uygulamalar ve dikkat edilmesi gereken noktalar, AutoCloseable ve viewModelScope kullanımınızı daha sağlam ve hatasız hale getirecektir. Unutmayın, iyi bir kaynak yönetimi, kullanıcılarınıza kesintisiz ve güvenilir bir uygulama deneyimi sunmanın anahtarıdır.

    Performans İpuçları ve En İyi Uygulamalar

    Kaynak temizliği sadece bellek sızıntılarını önlemekle kalmaz, aynı zamanda uygulamanızın performansını ve kullanıcı deneyimini de doğrudan etkiler. Bu bölümde, AutoCloseable ve viewModelScope kullanırken uygulamanızın performansını artıracak ve en iyi uygulamaları takip etmenizi sağlayacak bazı ipuçlarını ele alacağız.

    1. Gereksiz Kaynaklardan Kaçınma: En iyi performans ipucu, kullanmadığınız kaynakları oluşturmamaktır. Bir AutoCloseable nesnesi oluşturmadan önce gerçekten ihtiyacınız olup olmadığını sorgulayın. Aşırı kaynak oluşturma, sadece bellek tüketimini artırmakla kalmaz, aynı zamanda bu kaynakların kapatılması için ek CPU döngüleri de gerektirir. Yalnızca ihtiyaç duyulduğunda (lazy initialization) kaynakları başlatmayı düşünün.
    2. Kaynakları En Kısa Sürede Serbest Bırakın: Bir kaynakla işiniz bittiğinde, onu mümkün olan en kısa sürede serbest bırakmaya çalışın. ViewModel yapıcı metoduna AutoCloseable geçirmek, ViewModel yaşam döngüsü sonuna kadar kaynağı tutar. Eğer bir kaynak, ViewModel yaşam döngüsünden daha kısa bir süre için gerekiyorsa, onu try-with-resources bloğu içinde lokal olarak kullanıp hemen kapatmak daha verimli olabilir.

      
      // ViewModel içinde bir metod
      fun processTemporaryFile() {
          try (val tempFileStream = MyTempFileStream("temp_data.txt")) {
              tempFileStream.writeData("Geçici veri")
              // İşlemler
          } catch (e: Exception) {
              e.printStackTrace()
          }
          // tempFileStream burada otomatik olarak kapatıldı
      }
              

    3. Background Thread'lerde Kapatma İşlemi: Bazı close() operasyonları (özellikle I/O veya ağ işlemleri), ana thread'i bloke edebilecek kadar uzun sürebilir. Bu tür durumlarda, close() metodunun kendisini bir background thread'de veya bir Coroutine'de çalıştırmayı düşünebilirsiniz. Ancak, AutoCloseable arayüzünün doğrudan bir CoroutineScope ile entegre olmadığını unutmayın. Bu, onCleared() içinde manuel olarak bir CoroutineScope.launch ile bu işlemi başlatmanız gerektiği anlamına gelir.

      
      override fun close() {
          // Örneğin, bir veritabanı kapatma işlemi uzun sürebilir
          GlobalScope.launch(Dispatchers.IO) { // Dikkat: GlobalScope genellikle kaçınılması gereken bir kullanımdır
              try {
                  // Ağ veya veritabanı bağlantısını kapatma
                  println("Arka planda ağır kaynak kapatma işlemi tamamlandı.")
              } catch (e: Exception) {
                  println("Arka planda kapatma hatası: ${e.message}")
              }
          }
      }
              


      Yukarıdaki GlobalScope örneği, ViewModel'ın kendisi temizlense bile coroutine'in çalışmaya devam etme riskini taşır. Bu nedenle, ViewModel'ın yaşam döngüsüne bağlı bir CoroutineScope içinde bu tür işlemleri ele almak daha güvenlidir. Ancak onCleared() çağrıldığında viewModelScope'un iptal edildiğini göz önünde bulundurarak, bu tür "temizlik" coroutine'lerini dikkatli bir şekilde yönetmek gerekir.

    4. Küçük ve Odaklanmış AutoCloseable'lar: Büyük ve çok amaçlı AutoCloseable sınıflar yerine, tek bir sorumluluğa sahip küçük ve odaklanmış AutoCloseable'lar oluşturun. Bu, hem kodu daha anlaşılır hale getirir hem de her bir kaynağın temizliğinin daha kolay yönetilmesini sağlar.
    5. Hata Durumunda Temizlik: AutoCloseable'ın en büyük avantajlarından biri, try-with-resources yapısı sayesinde bir hata durumunda bile kaynakların kapatılmasını garanti etmesidir. Kendi AutoCloseable uygulamalarınızda, close() metodunun bir istisna fırlatması durumunda bile temizlik işleminin tamamlandığından emin olun.

    Bu ipuçları, AutoCloseable ve viewModelScope'u kullanarak sadece kodunuzu temizlemekle kalmayıp, aynı zamanda uygulamanızın performansını ve kararlılığını da artırmanıza yardımcı olacaktır. Unutmayın, iyi bir kaynak yönetimi, modern Android geliştiriciliğinin temel taşlarından biridir.

    Sonuç: Kaynak Temizliğinin Geleceği

    Bu makale boyunca, Jetpack ViewModels'ta otomatik kaynak temizliğinin önemini ve AutoCloseable arayüzünü kullanarak bu sürecin nasıl basitleştirilebileceğini derinlemesine inceledik. Modern Android uygulamalarında bellek sızıntılarının ve verimsiz kaynak kullanımının yol açtığı sorunları ele alarak, ViewModel'ların yaşam döngüsüne duyarlı mimarisiyle AutoCloseable'ın entegrasyonunun ne kadar kritik olduğunu vurguladık. Adım adım rehberler ve pratik kod örnekleri aracılığıyla, veritabanı bağlantılarından ağ isteklerine kadar çeşitli senaryolarda bu güçlü mekanizmanın nasıl uygulanacağını gösterdik.

    androidx.lifecycle.viewModelScope gibi Jetpack kütüphanelerinin sağladığı kolaylıklar, Coroutine tabanlı asenkron işlemlerin yönetimini basitleştirirken, AutoCloseable ile birlikte kullanıldığında hem senkron hem de asenkron kaynakların temizliğini tam anlamıyla otomatize etmektedir. Bu yaklaşımlar, geliştiricilerin onCleared() metodunda manuel temizlik yapmak yerine iş mantığına odaklanmasına olanak tanır, bu da daha az hataya açık, daha okunaklı ve daha sürdürülebilir bir kod tabanı oluşturulmasına yardımcı olur.

    Gelişen Android ekosisteminde, performans ve kararlılık, kullanıcı deneyiminin temel direkleridir. AutoCloseable ve viewModelScope gibi araçları etkili bir şekilde kullanmak, uygulamanızın bu hedeflere ulaşmasında kritik bir rol oynar. İleri düzey uygulamalar ve yaygın hatalardan kaçınma yolları üzerine sunduğumuz ipuçları ise, bu teknolojileri en verimli şekilde kullanmanız için size yol gösterecektir. Sonuç olarak, otomatik kaynak temizliği, sadece bir "iyi uygulama" olmaktan çıkıp, her modern Android uygulamasının ayrılmaz bir parçası haline gelmiştir. Bu teknikleri benimseyerek, daha sağlam, hızlı ve güvenilir mobil uygulamalar geliştirebilirsiniz. Unutmayın, temiz kod, mutlu kullanıcılar demektir.

    Sıkça Sorulan Sorular

    Bu bölümde, AutoCloseable ve ViewModel'lardaki kaynak temizliği hakkında sıkça sorulan bazı soruları ve cevaplarını bulabilirsiniz.

    1. AutoCloseable kullanmak her zaman onCleared() metodunda manuel temizlikten daha mı iyidir?

      Genel olarak evet. AutoCloseable, ViewModel'ın yapıcı metoduna geçirildiğinde veya try-with-resources bloğu içinde kullanıldığında, kaynakların otomatik olarak ve güvenli bir şekilde temizlenmesini sağlar. Bu, manuel onCleared() kullanımına göre daha az hataya açık, daha temiz ve daha sürdürülebilir bir yaklaşımdır. Ancak, bazı özel durumlarda (örneğin, AutoCloseable olmayan kütüphane nesneleri veya karmaşık temizlik mantığı gerektiren durumlar) onCleared() hala gerekli olabilir.

    2. viewModelScope dışında başlatılan Coroutine'ler nasıl temizlenir?

      viewModelScope dışında başlatılan Coroutine'ler (örneğin GlobalScope veya özel bir CoroutineScope içinde), ViewModel temizlendiğinde otomatik olarak iptal edilmez. Bu Coroutine'lerin Job'larını manuel olarak takip etmeli ve ViewModel'ın onCleared() metodunda Job.cancel() çağırarak iptal etmelisiniz. Aksi takdirde, bellek sızıntılarına veya gereksiz arka plan işlemlerine yol açabilirler.

    3. Birden fazla AutoCloseable nesnesini ViewModel'a nasıl enjekte edebilirim?

      Birden fazla AutoCloseable nesnesini ViewModel'a enjekte etmek için, bu nesneleri ViewModel'ın yapıcı metoduna vararg AutoCloseable şeklinde geçirebilirsiniz. Bağımlılık enjeksiyonu framework'leri (Hilt, Koin vb.) kullanıyorsanız, bu framework'ler aracılığıyla sağlanan AutoCloseable nesnelerini yine ViewModel'ın yapıcı metoduna enjekte edip, oradan base ViewModel sınıfının yapıcı metoduna iletmeniz en güvenli yoldur.

    4. AutoCloseable.close() metodu bir istisna fırlatırsa ne olur?

      AutoCloseable.close() metodu bir Exception fırlatabilir. Eğer bu istisna yakalanmazsa, uygulama çökmeyebilir ancak kaynak temizliği tam olarak gerçekleşmeyebilir. En iyi uygulama, close() metodunun içindeki temizlik mantığını bir try-catch bloğuna alarak olası istisnaları yakalamak ve uygun şekilde loglamaktır. Bu, hem uygulamanın kararlılığını sağlar hem de temizlik sorunlarını tespit etmenize yardımcı olur.

    5. AutoCloseable sadece Java I/O sınıfları için mi geçerlidir?

      Hayır, AutoCloseable arayüzü java.io.Closeable'dan daha genel bir amaç için tasarlanmıştır. Herhangi bir sınıf, AutoCloseable arayüzünü uygulayarak kendi kaynaklarını yönetebilir. Bu, veritabanı bağlantıları, ağ soketleri, özel akışlar, yerel kaynaklar veya herhangi bir "kapatılması" gereken özel bir nesne olabilir. java.io.Closeable ise daha çok I/O odaklıdır ve IOException fırlatır.


Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.