Mimari Bileşenler'in amacı, yaşam döngüsü yönetimi ve veri kalıcılığı gibi sık yapılan görevler için kitaplıklarla uygulama mimarisine rehberlik etmektir. Mimari bileşenler, uygulamanızı daha az ortak kodla sağlam, test edilebilir ve sürdürülebilir bir şekilde yapılandırmanıza yardımcı olur. Mimari Bileşen kitaplıkları, Android Jetpack'in bir parçasıdır.
Bu, codelab'in Kotlin sürümüdür. Java programlama dilindeki sürüme buradan ulaşabilirsiniz.
Bu codelab'den çalışırken herhangi bir sorunla (kod hataları, dil bilgisi hataları, belirsiz ifadeler vb.) karşılaşırsanız lütfen codelab'in sol alt köşesindeki Hata bildir bağlantısını kullanarak sorunu bildirin.
Ön koşullar
Özellikle Kotlin, nesne odaklı tasarım kavramları ve Android geliştirmenin temelleri hakkında bilgi sahibi olmanız gerekir. Özellikle de:
RecyclerView
ve bağdaştırıcı- SQLite veritabanı ve SQLite sorgu dili
- Temel eş yordamlar (coroutine'ler hakkında bilginiz yoksa Android uygulamanızda Kotlin Coroutines'i kullanma konusuna göz atabilirsiniz.)
Ayrıca, MVP veya MVC gibi verileri kullanıcı arayüzünden ayıran yazılım mimarisi modelleri hakkında bilgi edinmeniz de yararlı olur. Bu codelab'de, Uygulama mimarisi kılavuzunda tanımlanan mimari uygulanır.
Bu codelab, Android Mimari Bileşenlerine odaklanmaktadır. Konuları kopyalayıp yapıştırmak için konu dışı kavramlar ve kodlar yeterlidir.
Kotlin hakkında bilginiz yoksa bu codelab'in bir sürümü, burada Java programlama dilinde sunulmaktadır.
Ne yaparsınız?
Bu codelab'de Mimari Bileşenleri Odası, ViewModel ve LiveData kullanarak uygulama tasarlayıp oluşturmayı ve aşağıdakileri yapan bir uygulama oluşturmayı öğreneceksiniz:
- Android Mimari Bileşenleri'ni kullanarak önerilen mimarimizi uygular.
- Verileri almak ve kaydetmek için bir veritabanıyla çalışır ve veritabanını bazı kelimelerle önceden doldurur.
MainActivity
RecyclerView
içindeki tüm kelimeleri gösterir.- Kullanıcı + düğmesine dokunduğunda ikinci bir etkinlik açar. Kullanıcı bir kelime girdiğinde kelimeyi veritabanına ve listeye ekler.
Uygulama basittir ancak uygulama geliştirmek için şablon olarak kullanabileceğiniz kadar karmaşıktır. Önizleme:
Gerekenler
- Android Studio 3.0 veya sonraki sürümler ve bunların nasıl kullanılacağı hakkında bilgi. Android Studio'nun yanı sıra SDK'nızın ve Gradle'ın da güncellendiğinden emin olun.
- Bir Android cihaz veya emülatör.
Bu codelab'de, uygulamanın tamamını oluşturmak için ihtiyaç duyduğunuz tüm kodlar yer almaktadır.
Mimari Bileşenleri kullanmanın ve önerilen mimarisi uygulamanın birçok adımı vardır. En önemlisi, neler olduğuyla ilgili zihinsel bir model oluşturmak, parçaların nasıl bir araya geldiğini ve verilerin nasıl aktığını anlamaktır. Bu codelab'den yararlanırken, yalnızca kodu kopyalayıp yapıştırmanın yanı sıra kendi iç anlayışınızı oluşturmaya başlayın.
Önerilen Mimari Bileşenleri nelerdir?
Terimleri tanıtmak için Mimari Bileşenler ve bunların birlikte nasıl çalıştığıyla ilgili kısa bir tanıtım. Bu codelab'in, LiveData, ViewModel ve Room gibi bileşenlerin bir alt kümesine odaklandığını unutmayın. Her bileşen siz kullandıkça daha fazla açıklanır.
Bu şema, mimarinin temel bir biçimini göstermektedir:
Varlık: Oda ile çalışırken veritabanı tablosunu tanımlayan ek açıklamalı sınıf.
SQLite veritabanı: Cihaz depolama alanında. Oda kalıcılığı kitaplığı bu veritabanını sizin için oluşturur ve korur.
DAO: Veri erişimi nesnesi. SQL sorgularının işlevlerle eşlenmesi. DAO kullandığınızda yöntemleri çağırırsınız ve gerisini Oda yapar.
Oda veritabanı: Veritabanı çalışmasını basitleştirir ve temel SQLite veritabanına erişim noktası görevi görür (SQLiteOpenHelper)
gizlidir). Oda veritabanı, SQLite veritabanına sorgu göndermek için DAO'yu kullanır.
Veri havuzu: Birden çok veri kaynağını yönetmek için kullanılan bir sınıftır.
ViewModel: Depo (veri) ile kullanıcı arayüzü arasında bir iletişim merkezi görevi görür. Kullanıcı arayüzünün artık verilerin kaynağı konusunda endişelenmesi gerekmiyor. ViewModel örnekleri Etkinlik/Parça eğlencesinden sonra hayatta kalmaya devam eder.
LiveData: Gözlemlenebilen bir veri sahibi sınıfı. Her zaman verilerin en son sürümünü tutar/önbelleğe alır ve veriler değiştiğinde gözlemleyenleri bilgilendirir. LiveData
yaşam döngüsüne duyarlı. Kullanıcı arayüzü bileşenleri yalnızca alakalı verileri gözlemler ve gözlemi durdurmaz. Gözlemle alakalı yaşam döngüsü durumu değişikliklerini bildiğinden LiveData tüm bunları otomatik olarak yönetir.
RoomWordSample mimarisine genel bakış
Aşağıdaki şemada uygulamanın tüm bölümleri gösterilmektedir. Çevreleyen kutuların (SQLite veritabanı hariç) her biri, oluşturacağınız bir sınıfı temsil eder.
- Android Studio'yu açın ve Yeni bir Android Studio projesi başlatın'ı tıklayın.
- Yeni Proje Oluştur penceresinde, Boş Etkinlik'i seçin ve İleri'yi tıklayın.
- Sonraki ekranda, uygulamayı RoomWordSample olarak adlandırın ve Son'u tıklayın.
Daha sonra, Gradle dosyalarınıza bileşen kitaplıklarını eklemeniz gerekir.
- Android Studio'da, Projeler sekmesini tıklayın ve Gradle Komut Dosyaları klasörünü genişletin.
build.gradle
uygulamasını açın (Modül: uygulama).
kapt
ek açıklama işlemcisi Kotlin eklentisini,build.gradle
(Modül: uygulama) dosyanızın üst kısmında tanımlanan diğer eklentilerden sonra ekleyerek uygulayın.
apply plugin: 'kotlin-kapt'
- Paketten atomik işlevler modülünü hariç tutmak ve uyarıları önlemek için
android
blokunun içinepackagingOptions
blokunu ekleyin.
android {
// other configuration (buildTypes, defaultConfig, etc.)
packagingOptions {
exclude 'META-INF/atomicfu.kotlin_module'
}
}
dependencies
blokunun sonuna aşağıdaki kodu ekleyin.
// Room components
implementation "androidx.room:room-runtime:$rootProject.roomVersion"
kapt "androidx.room:room-compiler:$rootProject.roomVersion"
androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion"
// Lifecycle components
implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.archLifecycleVersion"
kapt "androidx.lifecycle:lifecycle-compiler:$rootProject.archLifecycleVersion"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$rootProject.archLifecycleVersion"
// Kotlin components
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$rootProject.coroutines"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$rootProject.coroutines"
// Material design
implementation "com.google.android.material:material:$rootProject.materialVersion"
// Testing
testImplementation 'junit:junit:4.12'
androidTestImplementation "androidx.arch.core:core-testing:$rootProject.coreTestingVersion"
build.gradle
(Proje: RoomWordsSample) dosyanızda, aşağıdaki kodda belirtildiği gibi sürüm numaralarını dosyanın sonuna ekleyin.
ext {
roomVersion = '2.2.5'
archLifecycleVersion = '2.2.0'
coreTestingVersion = '2.1.0'
materialVersion = '1.1.0'
coroutines = '1.3.4'
}
Bu uygulamanın verileri yalnızca kelimelerdir ve bu değerleri tutmak için basit bir tabloya ihtiyacınız vardır:
Oda, bir Varlık ile tablo oluşturmanıza olanak tanır. Haydi başlayalım.
Word
veri sınıfını içeren,Word
adlı yeni bir Kotlin sınıfı dosyası oluşturun.
Bu sınıf, kelimelerinizle ilgili Varlık'ı (SQLit tablosunu temsil eden) açıklar. Sınıftaki her özellik, tablodaki bir sütunu temsil eder. Oda son olarak, bu özellikleri hem tabloyu oluşturmak hem de nesneleri veritabanındaki satırlardan örnek almak için kullanır.
Kod şu şekildedir:
data class Word(val word: String)
Word
sınıfını Oda veritabanı için anlamlı hale getirmek üzere nota eklemeniz gerekir. Ek açıklamalar, bu sınıfın her bölümünün veritabanındaki bir girişle nasıl ilişkili olduğunu tanımlar. Oda, bu bilgileri kod oluşturmak için kullanır.
Ek açıklamaları kendiniz yapıştırırsanız (yapıştırma işlemi yerine) Android Studio, ek açıklama sınıflarını otomatik olarak içe aktarır.
Word
sınıfınızı bu kodda gösterildiği gibi ek açıklamalarla güncelleyin:
@Entity(tableName = "word_table")
class Word(@PrimaryKey @ColumnInfo(name = "word") val word: String)
Bu ek açıklamaların ne yaptığını görelim:
@Entity(tableName =
"word_table"
)
Her@Entity
sınıfı bir SQLite tablosunu temsil eder. Bir varlık olduğunu belirtmek için sınıf açıklamanıza ek açıklama ekleyin. Sınıfın adından farklı olmasını istiyorsanız tablonun adını belirtebilirsiniz. Bu tablo tabloya "kelime_tablo" adını verir.@PrimaryKey
Her varlık için birincil anahtar gerekir. Basit olması için her kelime kendi birincil anahtarı görevi görür.@ColumnInfo(name =
"word"
)
Üye değişkeninin adından farklı olmasını istiyorsanız tablodaki sütunun adını belirtir. Sütun “kelime” olarak adlandırılır.- Veritabanında depolanan her mülk, Kotlin varsayılanı olan, herkese açık olmalıdır.
Ek açıklamaların tam listesini Oda paketi özet referansında bulabilirsiniz.
DAO nedir?
DAO'da (veri erişimi nesnesi) SQL sorgularını belirtir ve yöntem çağrılarıyla ilişkilendirirsiniz. Derleyici SQL'i kontrol eder ve @Insert
gibi yaygın sorgular için kolaylık ek açıklamalarından sorgular oluşturur. Room, kodunuz için temiz bir API oluşturmak amacıyla DAO'yu kullanır.
DAO bir arayüz veya soyut sınıf olmalıdır.
Varsayılan olarak, tüm sorguların ayrı bir ileti dizisinde çalıştırılması gerekir.
Odanın eş yordam desteği vardır. Bu sayede sorgularınıza suspend
değiştiriciyle ek açıklama girebilir ve daha sonra bir eş yordamından veya başka bir askıya alma işlevinden çağrılabilir.
DAO'yu uygulama
Aşağıdakiler için sorgu sağlayan bir DAO yazalım:
- Tüm kelimeleri alfabetik olarak sıralama
- Kelime ekleme
- Tüm kelimeler siliniyor
WordDao
adlı yeni bir Kotlin sınıfı dosyası oluşturun.- Aşağıdaki kodu kopyalayıp
WordDao
içine yapıştırın ve içe aktarma işlemini derlemek için gereken içe aktarma işlemlerini düzeltin.
@Dao
interface WordDao {
@Query("SELECT * from word_table ORDER BY word ASC")
fun getAlphabetizedWords(): List<Word>
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(word: Word)
@Query("DELETE FROM word_table")
suspend fun deleteAll()
}
Şimdi bunları inceleyelim:
WordDao
bir arayüzdür; DAO'lar arayüzler veya soyut sınıflar olmalıdır.@Dao
ek açıklaması, Oda için DAO sınıfı olarak tanımlanır.suspend fun insert(word: Word)
: Bir kelime eklemek için askıya alma işlevini tanımlar.@Insert
ek açıklaması, SQL sağlamanıza gerek olmayan özel bir DAO yöntemi ek açıklamasıdır. (Satırları silmek ve güncellemek için@Delete
ve@Update
ek açıklamaları da vardır, ancak bunları bu uygulamada kullanmıyorsunuz.)onConflict = OnConflictStrategy.IGNORE
: Seçili OnÇakışan stratejisi, zaten listede bulunanla bire bir aynı olan yeni bir kelimeyi yoksayar. Kullanılabilen çakışma stratejileri hakkında daha fazla bilgi edinmek için dokümanlara göz atın.suspend fun deleteAll()
: Tüm kelimeleri silmek için askıya alma işlevi bildirir.- Birden çok varlığı silmek için uygun bir ek açıklama olmadığından genel
@Query
ek açıklamasıyla belirtilir. @Query
("DELETE FROM word_table")
:@Query
ek açıklama için dize parametresi olarak bir SQL sorgusu sağlamanızı gerektirir. Bu da, karmaşık okuma sorgularına ve diğer işlemlere olanak tanır.fun getAlphabetizedWords(): List<Word>
: Tüm kelimeleri alma veList
/Words
döndürmesini sağlama yöntemi.@Query(
"SELECT * from word_table ORDER BY word ASC"
)
: Artan düzende sıralanmış kelime listesini döndüren sorgu.
Veriler değiştiğinde genellikle bazı işlemler yapmak istersiniz (ör. güncellenen verileri kullanıcı arayüzünde görüntülemek). Yani, değiştiğinde verilere yanıt verebilmeniz için verileri gözlemlemeniz gerekir.
Verilerin nasıl depolandığına bağlı olarak bu işlem karmaşık olabilir. Uygulamanızın birden fazla bileşenindeki verilerde yapılan değişiklikleri gözlemlemek, bileşenler arasında açık ve katı bağımlılık yolları oluşturabilir. Bu, diğer testlerin yanı sıra test ve hata ayıklamayı zorlaştırır.
Veri gözlemi için yaşam döngüsü kitaplığı sınıfı olan LiveData
bu sorunu çözer. Yöntem açıklamanızda LiveData
türünde bir dönüş değeri kullanın. Oda, veritabanı güncellendiğinde LiveData
öğesini güncellemek için gereken tüm kodu oluşturur.
WordDao
içinde, getAlphabetizedWords()
yöntem imzasını değiştirerek döndürülen List<Word>
değerinin LiveData
ile sarmalanması için değiştirin.
@Query("SELECT * from word_table ORDER BY word ASC")
fun getAlphabetizedWords(): LiveData<List<Word>>
Bu codelab'in ilerleyen bölümlerinde, veri değişikliklerini MainActivity
ürününde bir Observer
üzerinden takip edeceksiniz.
Oda veritabanı nedir?
- Oda, SQLite veritabanının üzerinde bir veritabanı katmanıdır.
- Oda,
SQLiteOpenHelper
ile çalışırken yaptığınız sıradan görevleri halleder. - Room, veritabanına sorgu göndermek için DAO'yu kullanır.
- Varsayılan olarak, zayıf kullanıcı arayüzü performansından kaçınmak için Oda, ana ileti dizisinde sorgular yayınlamanıza izin vermez. Oda sorguları
LiveData
döndürdüğünde sorgular otomatik olarak arka plandaki ileti dizisinde çalıştırılır. - Oda, SQLite ifadelerinin derleme anında kontrollerini sağlar.
Oda veritabanını uygulama
Oda veritabanı sınıfınız soyut olmalı ve RoomDatabase
uzatmalıdır. Uygulamanın tamamı için yalnızca bir Oda veritabanı örneğine ihtiyacınız vardır.
Hemen bir tane oluşturalım.
WordRoomDatabase
adında bir Kotlin sınıfı dosyası oluşturun ve bu kodu dosyaya ekleyin:
// Annotates class to be a Room Database with a table (entity) of the Word class
@Database(entities = arrayOf(Word::class), version = 1, exportSchema = false)
public abstract class WordRoomDatabase : RoomDatabase() {
abstract fun wordDao(): WordDao
companion object {
// Singleton prevents multiple instances of database opening at the
// same time.
@Volatile
private var INSTANCE: WordRoomDatabase? = null
fun getDatabase(context: Context): WordRoomDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
WordRoomDatabase::class.java,
"word_database"
).build()
INSTANCE = instance
return instance
}
}
}
}
Şimdi kodun üzerinden geçelim:
- Odanın veritabanı sınıfı
abstract
olmalı veRoomDatabase
süresini uzatmalıdır - Sınıfa
@Database
ile bir Oda veritabanı olarak ek açıklama eklersiniz ve veritabanına ait olan varlıkları belirtmek ve sürüm numarasını ayarlamak için ek açıklama parametrelerini kullanırsınız. Her varlık, veritabanında oluşturulacak bir tabloya karşılık gelir. Veritabanı taşıma işlemleri bu codelab'in kapsamı dışındadır. Bu nedenle, derleme uyarısı almamak içinexportSchema
değerini false olarak ayarladık. Gerçek bir uygulamada, şemayı dışa aktarmak üzere Oda için kullanılacak bir dizin oluşturmayı düşünebilirsiniz. Böylece, geçerli şemayı sürüm kontrol sisteminize kontrol edebilirsiniz. - Veritabanı, her bir @Dao için soyut "alıcı" yöntemiyle DAO'ları gösterir.
- Veritabanının aynı anda birden fazla örneğinin açılmasını önlemek için tekli (
WordRoomDatabase,
) tanımını yaptık. getDatabase
, tekili döndürür.WordRoomDatabase
veritabanındaki uygulama bağlamında birRoomDatabase
nesnesi oluşturmak ve bunu"word_database"
olarak adlandırmak için veritabanına ilk erişildiğinde veritabanını oluşturur.
Veri Deposu nedir?
Kod deposu sınıfı, birden fazla veri kaynağına erişimi soyutlar. Kod deposu, Mimari Bileşenler kitaplıklarının bir parçası değildir ancak kod ayırma ve mimari için önerilen en iyi uygulamadır. Repository sınıfı, uygulamanın geri kalanına veri erişimi için temiz bir API sağlar.
Neden bir Depo kullanılmalı?
Kod deposu, sorguları yönetir ve birden fazla arka uç kullanmanıza olanak tanır. En yaygın örnekte, Repository bir ağdan veri alma veya yerel veritabanında önbelleğe alınmış sonuçları kullanma kararını verir.
Veri Deposunu Uygulama
WordRepository
adında bir Kotlin sınıfı dosyası oluşturun ve aşağıdaki kodu bu dosyaya yapıştırın:
// Declares the DAO as a private property in the constructor. Pass in the DAO
// instead of the whole database, because you only need access to the DAO
class WordRepository(private val wordDao: WordDao) {
// Room executes all queries on a separate thread.
// Observed LiveData will notify the observer when the data has changed.
val allWords: LiveData<List<Word>> = wordDao.getAlphabetizedWords()
suspend fun insert(word: Word) {
wordDao.insert(word)
}
}
Temel çıkarımlar:
- DAO, veritabanının tamamı yerine depo oluşturucuya iletilir. Bunun nedeni, DAO'nun veritabanı için tüm okuma/yazma yöntemlerini içermesi nedeniyle yalnızca DAO'ya erişmesi gerektiğidir. Veritabanının tamamını depoya göstermeniz gerekmez.
- Kelime listesi, herkese açık bir özelliktir. Bu işlem, Odadan
LiveData
kelime listesi alarak başlar. Bu işlemi, "LiveData class" adımındaLiveData
özelliğini döndürmek içingetAlphabetizedWords
yöntemini nasıl tanımladığımızdan yapabiliriz. Room, tüm sorguları ayrı bir mesaj dizisinde yürütür. Ardından, gözlemlenenLiveData
veriler değiştiğinde ana iş parçacığındaki gözlemciyi bilgilendirir. suspend
değiştiricisi, derleyiciye bunun bir eş yordamdan veya başka bir askıya alma işlevinden çağrılması gerektiğini bildirir.
ViewModel nedir?
ViewModel
adlı iş ortağının rolü, kullanıcı arayüzüne veri sağlamak ve yapılandırma değişikliklerinden sonra hayatta kalmaktır. ViewModel
, Kod Deposu ve kullanıcı arayüzü arasında iletişim merkezi görevi görür. Verileri parçalar arasında paylaşmak için de ViewModel
kullanabilirsiniz. ViewModel, yaşam döngüsü kitaplığının bir parçasıdır.
Bu konuyla ilgili tanıtım kılavuzu için ViewModel Overview
veya ViewModels: Basit Bir Örnek blog yayınına bakın.
Neden ViewModel kullanılmalı?
ViewModel
, uygulamanızın kullanıcı arayüzü verilerini, yapılandırma değişikliklerinden sonra hayatta kalmaya devam edecek şekilde tutar. Uygulamanızın kullanıcı arayüzü verilerini Activity
ve Fragment
sınıflarınızdan ayırmak, tek sorumluluk ilkesini yerine getirmenize olanak sağlar: Ekrana veri çizmek, etkinlikleriniz ve parçalarınızdan sorumludur. ViewModel
ise kullanıcı arayüzü için gereken tüm verileri tutma ve işleme yetkisine sahip olabilir.
ViewModel
içinde, kullanıcı arayüzünün kullanacağı veya göstereceği değiştirilebilir veriler için LiveData
kullanın. LiveData
kullanmanın pek çok avantajı vardır:
- Verilere bir gözlemci koyabilir (değişiklikler için anket uygulamak yerine) ve kullanıcı arayüzünü, yalnızca veriler gerçekten değiştiğinde
güncelleyebilirsiniz. - Depo ve kullanıcı arayüzü
ViewModel
ile tamamen ayrılır. ViewModel
üzerinden hiç veritabanı çağrısı yok (bu işlem, depoda gerçekleştirilir ve kodu daha test edilebilir hale getirir.
viewModelScope
Kotlin'de tüm eş yordamlar CoroutineScope
içinde çalışır. Kapsam, eş yordamların kullanım ömrünü kontrol eder. Bir kapsam işini iptal ettiğinizde bu kapsamda başlatılan tüm eş yordamlar iptal edilir.
AndroidX lifecycle-viewmodel-ktx
kitaplığı, ViewModel
sınıfının uzantı işlevi olarak bir viewModelScope
ekler ve kapsamlarla çalışmanıza olanak tanır.
ViewModel'de coroutine'ler ile çalışma hakkında daha fazla bilgi edinmek için Android uygulamanızda Kotlin Coroutines'i kullanma codelab'inin 5. adımına veya Android'de kolay coroutine'ler: viewModelScope blog yayınına bakın.
ViewModel'i uygulama
WordViewModel
için bir Kotlin sınıfı dosyası oluşturun ve bu kodu ona ekleyin:
class WordViewModel(application: Application) : AndroidViewModel(application) {
private val repository: WordRepository
// Using LiveData and caching what getAlphabetizedWords returns has several benefits:
// - We can put an observer on the data (instead of polling for changes) and only update the
// the UI when the data actually changes.
// - Repository is completely separated from the UI through the ViewModel.
val allWords: LiveData<List<Word>>
init {
val wordsDao = WordRoomDatabase.getDatabase(application).wordDao()
repository = WordRepository(wordsDao)
allWords = repository.allWords
}
/**
* Launching a new coroutine to insert the data in a non-blocking way
*/
fun insert(word: Word) = viewModelScope.launch(Dispatchers.IO) {
repository.insert(word)
}
}
Burada:
Application
adlı parametreyi parametre olarak alıpAndroidViewModel
süresini uzatan,WordViewModel
adında bir sınıf oluşturuldu.- Depoya referans içerecek bir gizli üye değişkeni eklendi.
- Kelime listesini önbelleğe almak için herkese açık bir
LiveData
üye değişkeni eklendi. WordRoomDatabase
öğesindenWordDao
öğesine referans veren birinit
bloğu oluşturuldu.init
bloğundaWordRepository
WordRoomDatabase
temel alınarak oluşturulmuştur.init
blokunda, kod deposu kullanılarakallWords
LiveData başlatıldı.- Repository's
insert()
yöntemini çağıran bir sarmalayıcıinsert()
yöntemi oluşturuldu. Bu şekilde,insert()
işlevi kullanıcı arayüzünden kapsüllenir. Ana iş parçacığını engellemek için ekleme yapmak istemiyoruz. Bu nedenle yeni bir eş değer liste başlatıyor ve askıya alma işlevi olan kod deposunu çağırıyoruz. Daha önce de bahsettiğimiz gibi, ModelModeller, yaşam döngülerimize göre burada kullandığımızviewModelScope
türünde bir eş yordam kapsamına sahiptir.
Ardından, liste ve öğeler için XML düzeni eklemeniz gerekir.
Bu codelab'de XML'de düzen oluşturmayı bildiğiniz varsayıldığında, yalnızca size kod sağlıyoruz.
AppTheme
üst öğesini Theme.MaterialComponents.Light.DarkActionBar
olarak ayarlayarak uygulama tema materyalinizi oluşturun. values/styles.xml
listesindeki liste öğeleri için bir stil ekleyin:
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<!-- The default font for RecyclerView items is too small.
The margin is a simple delimiter between the words. -->
<style name="word_title">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_marginBottom">8dp</item>
<item name="android:paddingLeft">8dp</item>
<item name="android:background">@android:color/holo_orange_light</item>
<item name="android:textAppearance">@android:style/TextAppearance.Large</item>
</style>
</resources>
layout/recyclerview_item.xml
düzeni ekleyin:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textView"
style="@style/word_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/holo_orange_light" />
</LinearLayout>
layout/activity_main.xml
içinde TextView
öğesini bir RecyclerView
ile değiştirin ve bir kayan işlem düğmesi (FAB) ekleyin. Düzeniniz artık aşağıdaki gibi olacaktır:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="0dp"
android:layout_height="0dp"
tools:listitem="@layout/recyclerview_item"
android:padding="@dimen/big_padding"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:contentDescription="@string/add_word"/>
</androidx.constraintlayout.widget.ConstraintLayout>
FAB'nızın görünümü, kullanılabilir işleme karşılık gelmelidir. Bu nedenle, simgeyi "'+' sembolüyle değiştirmek istiyoruz.
Öncelikle yeni bir Vector Öğesi eklememiz gerekiyor:
- File > New > Vector Item'ı seçin.
- Küçük resim: alanındaki Android robot simgesini tıklayın.
- &"ekle"yi arayın ve '+' öğesini seçin. Tamam
'ı tıklayın. - Ardından İleri'yi tıklayın.
- Simge yolunu
main > drawable
olarak onaylayın ve öğeyi eklemek için Son'u tıklayın. - Hâlâ
layout/activity_main.xml
içinde, FAB'yı yeni çekilebilirliği içerecek şekilde güncelleyin:
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:contentDescription="@string/add_word"
android:src="@drawable/ic_add_black_24dp"/>
Verileri RecyclerView
içinde gösterebilirsiniz. Bu, verileri TextView
özelliğine yerleştirmekten biraz daha kolaydır. Bu codelab'de RecyclerView
, RecyclerView.LayoutManager
, RecyclerView.ViewHolder
ve RecyclerView.Adapter
özelliğinin nasıl çalıştığını bildiğiniz varsayılmaktadır.
Adaptördeki words
değişkeninin verileri önbelleğe aldığını unutmayın. Bir sonraki görevde, verileri otomatik olarak güncelleyen kodu eklersiniz.
WordListAdapter
için RecyclerView.Adapter
süresini kapsayan bir Kotlin sınıfı dosyası oluşturun. Kod şu şekildedir:
class WordListAdapter internal constructor(
context: Context
) : RecyclerView.Adapter<WordListAdapter.WordViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var words = emptyList<Word>() // Cached copy of words
inner class WordViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val wordItemView: TextView = itemView.findViewById(R.id.textView)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordViewHolder {
val itemView = inflater.inflate(R.layout.recyclerview_item, parent, false)
return WordViewHolder(itemView)
}
override fun onBindViewHolder(holder: WordViewHolder, position: Int) {
val current = words[position]
holder.wordItemView.text = current.word
}
internal fun setWords(words: List<Word>) {
this.words = words
notifyDataSetChanged()
}
override fun getItemCount() = words.size
}
RecyclerView
öğesini MainActivity
öğesinin onCreate()
yöntemine ekleyin.
setContentView
yönteminden sonra onCreate()
yönteminde:
val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
val adapter = WordListAdapter(this)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this)
Her şeyin çalıştığından emin olmak için uygulamanızı çalıştırın. Henüz veri bağlamadığınız için öğe yok.
Veritabanında veri yok. Verileri iki şekilde eklersiniz: Veritabanı açıldığında bazı veriler ekleme ve kelime ekleme için Activity
ekleme.
Uygulama her başlatıldığında tüm içerikleri silmek ve veritabanını yeniden doldurmak için bir RoomDatabase.Callback
oluşturursunuz ve onOpen()
geçersiz kılınır. Kullanıcı arayüzü ileti dizisinde Oda veritabanı işlemlerini gerçekleştiremeyeceğiniz için onOpen()
, IO Dispatcher'da bir eş yordam başlatır.
Coroutine başlatmak için CoroutineScope
gerekli. Parametre olarak eş yordam kapsamı da almak için WordRoomDatabase
sınıfının getDatabase
yöntemini güncelleyin:
fun getDatabase(
context: Context,
scope: CoroutineScope
): WordRoomDatabase {
...
}
Kapsamın da başarılı olması için WordViewModel
blokunun init
bloğundaki veritabanı alma başlatıcıyı güncelleyin:
val wordsDao = WordRoomDatabase.getDatabase(application, viewModelScope).wordDao()
WordRoomDatabase
içinde, oluşturucu parametresi olarak CoroutineScope
alan özel bir RoomDatabase.Callback()
uygulaması oluştururuz. Ardından, veritabanını doldurmak için onOpen
yöntemini geçersiz kılarız.
WordRoomDatabase
sınıfının içinde geri arama oluşturma kodu aşağıda verilmiştir:
private class WordDatabaseCallback(
private val scope: CoroutineScope
) : RoomDatabase.Callback() {
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
INSTANCE?.let { database ->
scope.launch {
populateDatabase(database.wordDao())
}
}
}
suspend fun populateDatabase(wordDao: WordDao) {
// Delete all content here.
wordDao.deleteAll()
// Add sample words.
var word = Word("Hello")
wordDao.insert(word)
word = Word("World!")
wordDao.insert(word)
// TODO: Add your own words!
}
}
Son olarak, Room.databaseBuilder()
öğesinde .build()
öğesini çağırmadan hemen önce geri çağırmayı veritabanı derleme sırasına ekleyin:
.addCallback(WordDatabaseCallback(scope))
Son kod şu şekilde görünmelidir:
@Database(entities = arrayOf(Word::class), version = 1, exportSchema = false)
abstract class WordRoomDatabase : RoomDatabase() {
abstract fun wordDao(): WordDao
private class WordDatabaseCallback(
private val scope: CoroutineScope
) : RoomDatabase.Callback() {
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
INSTANCE?.let { database ->
scope.launch {
var wordDao = database.wordDao()
// Delete all content here.
wordDao.deleteAll()
// Add sample words.
var word = Word("Hello")
wordDao.insert(word)
word = Word("World!")
wordDao.insert(word)
// TODO: Add your own words!
word = Word("TODO!")
wordDao.insert(word)
}
}
}
}
companion object {
@Volatile
private var INSTANCE: WordRoomDatabase? = null
fun getDatabase(
context: Context,
scope: CoroutineScope
): WordRoomDatabase {
// if the INSTANCE is not null, then return it,
// if it is, then create the database
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
WordRoomDatabase::class.java,
"word_database"
)
.addCallback(WordDatabaseCallback(scope))
.build()
INSTANCE = instance
// return instance
instance
}
}
}
}
Bu dize kaynaklarını values/strings.xml
listesine ekleyin:
<string name="hint_word">Word...</string>
<string name="button_save">Save</string>
<string name="empty_not_saved">Word not saved because it is empty.</string>
Bu renk kaynağını value/colors.xml
ürününe ekle:
<color name="buttonLabel">#FFFFFF</color>
Yeni bir boyut kaynak dosyası oluşturun:
- Proje penceresinde uygulama modülünü tıklayın.
- Dosya > Yeni > Android Kaynak Dosyası'nı seçin
- Kullanılabilir Niteleyicilerden Boyut'u seçin
- Dosya adını ayarlama: loş
values/dimens.xml
uygulamasında şu boyut kaynaklarını ekleyin:
<dimen name="small_padding">8dp</dimen>
<dimen name="big_padding">16dp</dimen>
Boş Etkinlik şablonuyla yeni bir boş Android Activity
oluşturun:
- Dosya > Yeni > Etkinlik > Boş Etkinlik'i seçin.
- Etkinlik adı için
NewWordActivity
girin. - Yeni etkinliğin Android Manifest'e eklendiğini doğrulayın.
<activity android:name=".NewWordActivity"></activity>
Düzen klasöründeki activity_new_word.xml
dosyasını şu kodla güncelleyin:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/edit_word"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/min_height"
android:fontFamily="sans-serif-light"
android:hint="@string/hint_word"
android:inputType="textAutoComplete"
android:layout_margin="@dimen/big_padding"
android:textSize="18sp" />
<Button
android:id="@+id/button_save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:text="@string/button_save"
android:layout_margin="@dimen/big_padding"
android:textColor="@color/buttonLabel" />
</LinearLayout>
Etkinlik kodunu güncelleyin:
class NewWordActivity : AppCompatActivity() {
private lateinit var editWordView: EditText
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_new_word)
editWordView = findViewById(R.id.edit_word)
val button = findViewById<Button>(R.id.button_save)
button.setOnClickListener {
val replyIntent = Intent()
if (TextUtils.isEmpty(editWordView.text)) {
setResult(Activity.RESULT_CANCELED, replyIntent)
} else {
val word = editWordView.text.toString()
replyIntent.putExtra(EXTRA_REPLY, word)
setResult(Activity.RESULT_OK, replyIntent)
}
finish()
}
}
companion object {
const val EXTRA_REPLY = "com.example.android.wordlistsql.REPLY"
}
}
Son adım, kullanıcının girdiği yeni kelimeleri kaydedip RecyclerView
veritabanındaki kelime veritabanının mevcut içeriğini göstererek kullanıcı arayüzünü veritabanına bağlamaktır.
Veritabanının mevcut içeriğini görüntülemek için ViewModel
içindeki LiveData
değerlerini gözlemleyen bir gözlemci ekleyin.
Veri her değiştiğinde, adaptörün önbelleğe alınan verilerini güncellemek ve görüntülenen listeyi yenilemek için adaptörün setWords()
yöntemini çağıran onChanged()
geri çağırma çağrılır.
MainActivity
içinde ViewModel
için bir üye değişkeni oluşturun:
private lateinit var wordViewModel: WordViewModel
ViewModel
cihazınızı Activity
ile ilişkilendirmek için ViewModelProvider
özelliğini kullanın.
Activity
cihazınız ilk kez başlatıldığında ViewModelProviders
, ViewModel
etiketini oluşturur. Etkinlik kaldırıldığında (örneğin, bir yapılandırma değişikliği aracılığıyla) ViewModel
devam eder. Etkinlik yeniden oluşturulduğunda, ViewModelProviders
mevcut ViewModel
değerini döndürür. Daha fazla bilgi edinmek için ViewModel
sayfasına göz atın.
RecyclerView
kod bloğunun altındaki onCreate()
bölgesinde, ViewModelProvider
üzerinden ViewModel
kazanın:
wordViewModel = ViewModelProvider(this).get(WordViewModel::class.java)
Ayrıca onCreate()
WordViewModel
adlı ürüne ait allWords LiveData
özelliği için bir gözlemci ekleyin.
Gözlemlenen veriler değiştiğinde ve etkinlik ön planda olduğunda onChanged()
yöntemi (Lambda'mız için varsayılan yöntem) tetiklenir:
wordViewModel.allWords.observe(this, Observer { words ->
// Update the cached copy of the words in the adapter.
words?.let { adapter.setWords(it) }
})
FAB'a dokunurken NewWordActivity
özelliğini açmak ve MainActivity
konumuna geri döndüğümüzde yeni kelimeyi veritabanına eklemek veya bir Toast
göstermek istiyoruz. Bunun için bir istek kodu tanımlayarak başlayalım:
private val newWordActivityRequestCode = 1
MainActivity
içinde, NewWordActivity
için onActivityResult()
kodunu ekleyin.
Etkinlik RESULT_OK
ile döndürülürse WordViewModel
öğesinin insert()
yöntemini çağırarak döndürülen kelimeyi veritabanına ekleyin:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == newWordActivityRequestCode && resultCode == Activity.RESULT_OK) {
data?.getStringExtra(NewWordActivity.EXTRA_REPLY)?.let {
val word = Word(it)
wordViewModel.insert(word)
}
} else {
Toast.makeText(
applicationContext,
R.string.empty_not_saved,
Toast.LENGTH_LONG).show()
}
}
MainActivity,
Kullanıcı FAB'ya dokunduğunda NewWordActivity
uygulamasını başlatın. MainActivity
onCreate
bölümünde, FAB'yı bulun ve şu kodla bir onClickListener
ekleyin:
val fab = findViewById<FloatingActionButton>(R.id.fab)
fab.setOnClickListener {
val intent = Intent(this@MainActivity, NewWordActivity::class.java)
startActivityForResult(intent, newWordActivityRequestCode)
}
Bitmiş kodunuz şu şekilde görünmelidir:
class MainActivity : AppCompatActivity() {
private const val newWordActivityRequestCode = 1
private lateinit var wordViewModel: WordViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
val adapter = WordListAdapter(this)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this)
wordViewModel = ViewModelProvider(this).get(WordViewModel::class.java)
wordViewModel.allWords.observe(this, Observer { words ->
// Update the cached copy of the words in the adapter.
words?.let { adapter.setWords(it) }
})
val fab = findViewById<FloatingActionButton>(R.id.fab)
fab.setOnClickListener {
val intent = Intent(this@MainActivity, NewWordActivity::class.java)
startActivityForResult(intent, newWordActivityRequestCode)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == newWordActivityRequestCode && resultCode == Activity.RESULT_OK) {
data?.getStringExtra(NewWordActivity.EXTRA_REPLY)?.let {
val word = Word(it)
wordViewModel.insert(word)
}
} else {
Toast.makeText(
applicationContext,
R.string.empty_not_saved,
Toast.LENGTH_LONG).show()
}
}
}
Şimdi uygulamanızı çalıştırın. NewWordActivity
içinde veritabanına bir kelime eklediğinizde kullanıcı arayüzü otomatik olarak güncellenir.
Artık çalışan bir uygulamanız olduğuna göre oluşturduğunuzu özetleyelim. Uygulama yapısı aşağıda tekrar açıklanmıştır:
Uygulamanın bileşenleri şunlardır:
MainActivity
:RecyclerView
veWordListAdapter
kullanarak listedeki kelimeleri gösterir.MainActivity
içinde, veritabanındaki LiveData kelimelerini gözlemleyen ve değiştiğinde bildirim alan birObserver
vardır.NewWordActivity:
listeye yeni bir kelime ekler.WordViewModel
: Veri katmanına erişim yöntemleri sunar ve MainActivity'in gözlemci ilişkisini ayarlayabilmesi için LiveData değerini döndürür.*LiveData<List<Word>>
: Kullanıcı arayüzü bileşenlerinde otomatik güncellemeleri mümkün kılar.MainActivity
içinde, veritabanındaki LiveData kelimelerini gözlemleyen ve değiştirildiğinde bilgilendirilen birObserver
bulunur.Repository:
, bir veya daha fazla veri kaynağını yönetiyor.Repository
, ViewModel'in temel veri sağlayıcısıyla etkileşim kurma yöntemlerini gösterir. Bu uygulamada arka uç bir Oda veritabanıdır.Room
: Sarmalayıcıdır ve SQLite veritabanı uygular. Oda sizin için eskiden yaptığınız çok işin bir parçası.- DAO: Metod çağrılarını veritabanı sorgularıyla eşler. Böylece, Repository
getAlphabetizedWords()
gibi bir yöntemi çağırdığında Oda,SELECT * from word_table ORDER BY word ASC
yürütebilir. Word
: Tek bir kelime içeren öğe sınıfıdır.
* Views
ve Activities
(ve Fragments
) yalnızca ViewModel
aracılığıyla verilerle etkileşimde bulunur. Bu nedenle, verilerin kaynağı önemli değildir.
Otomatik Kullanıcı Arayüzü Güncellemeleri Verisi (Reactive arayüz)
LiveData kullandığımız için otomatik güncelleme mümkün. MainActivity
içinde, veritabanındaki LiveData kelimelerini gözlemleyen ve değiştirildiğinde bilgilendirilen bir Observer
bulunur. Bir değişiklik olduğunda, gözlemcinin onChange()
yöntemi çalıştırılır ve WordListAdapter
içinde mWords
güncellenir.
Veriler LiveData
olduğundan gözlemlenebilir. Gözlemlenen değer, WordViewModel
allWords
özelliği tarafından döndürülen LiveData<List<Word>>
'dir.
WordViewModel
, arka uçla ilgili her şeyi kullanıcı arayüzü katmanından gizler. Veri katmanına erişmek için yöntemler sunar ve MainActivity
öğesinin gözlemleyici ilişkisini kurabilmesi için LiveData
katmanı döndürür. Views
ve Activities
(ve Fragments
) yalnızca ViewModel
aracılığıyla verilerle etkileşimde bulunur. Bu nedenle, verilerin kaynağı önemli değildir.
Bu durumda, veriler bir Repository
kaynağından gelir. ViewModel
, Repository'nin neyle etkileşimde bulunduğunu bilmesine gerek yoktur. Yalnızca, Repository
tarafından gösterilen yöntemleri kullanarak Repository
ile nasıl etkileşim kuracağını bilmesi gerekir.
Depo bir veya daha fazla veri kaynağını yönetiyor. WordListSample
uygulamasında bu arka uç bir Oda veritabanıdır. Oda, bir sarmalayıcıdır ve SQLite veritabanı uygular. Oda sizin için eskiden yaptığınız çok işin bir parçası. Örneğin, Odalar SQLiteOpenHelper
sınıfında yaptığınız tüm işlemleri yapar.
DAO, yöntem çağrılarını veritabanı sorgularına eşler. Böylece, Repository getAllWords()
gibi bir yöntemi çağırdığında Oda, SELECT * from word_table ORDER BY word ASC
yürütebilir.
Sorgudan döndürülen sonuç LiveData
olarak gözlemlendiği için Odadaki veriler her değiştiğinde Observer
arayüzünün onChanged()
yöntemi yürütülüp kullanıcı arayüzü güncellenir.
[İsteğe bağlı] Çözüm kodunu indirin
Henüz yapmadıysanız codelab'in çözüm koduna göz atabilirsiniz. github veri havuzuna bakabilir veya kodu buradan indirebilirsiniz:
İndirilen ZIP dosyasını paketten çıkarın. Bu işlem, uygulamanın tamamını içeren bir kök klasörün (android-room-with-a-view-kotlin
) paketini açar.