Android'de Veri Okuma ve Yazma

Bu belgede, Firebase verilerini okuma ve yazma ile ilgili temel bilgiler verilmektedir.

Firebase verileri bir FirebaseDatabase referansına yazılır ve referansa eşzamansız bir dinleyici eklenerek alınır. Dinleyici, verilerin ilk durumu için bir kez, veriler her değiştiğinde ise tekrar tetiklenir.

(İsteğe bağlı) Firebase Local Emulator Suite ile prototip oluşturma ve test etme

Uygulamanızın Realtime Database ile nasıl okuma ve yazma işlemi yaptığından bahsetmeden önce, Realtime Database işlevselliğini prototip oluşturmak ve test etmek için kullanabileceğiniz bir dizi aracı tanıtalım: Firebase Local Emulator Suite. Farklı veri modellerini deniyorsanız, güvenlik kurallarınızı optimize ediyorsanız veya arka uçla etkileşim kurmanın en uygun maliyetli yolunu bulmaya çalışıyorsanız canlı hizmetleri dağıtmadan yerel olarak çalışabilmek harika bir fikir olabilir.

Realtime Database emülatörü, Local Emulator Suite'nin bir parçasıdır. Bu araç, uygulamanızın emüle edilmiş veritabanı içeriğiniz ve yapılandırmanızla, ayrıca isteğe bağlı olarak emüle edilmiş proje kaynaklarınızla (işlevler, diğer veritabanları ve güvenlik kuralları) etkileşim kurmasını sağlar.

Realtime Database emülatörünü kullanmak için yalnızca birkaç adım gerekir:

  1. Emülatöre bağlanmak için uygulamanızın test yapılandırmasına bir kod satırı ekleyin.
  2. Yerel proje dizininizin kökünden firebase emulators:start komutunu çalıştırın.
  3. Uygulamanızın prototip kodundan Realtime Database platform SDK'sını her zamanki gibi kullanarak veya Realtime Database REST API'yi kullanarak çağrı yapma.

Realtime Database ve Cloud Functions ile ilgili ayrıntılı birrehber mevcuttur. Ayrıca Local Emulator Suite giriş bölümüne de göz atmanız gerekir.

DatabaseReference alma

Veri tabanından veri okumak veya veri tabanına veri yazmak için DatabaseReference örneğine ihtiyacınız vardır:

Kotlin

private lateinit var database: DatabaseReference
// ...
database = Firebase.database.reference

Java

private DatabaseReference mDatabase;
// ...
mDatabase = FirebaseDatabase.getInstance().getReference();

Veri yazma

Temel yazma işlemleri

Temel yazma işlemleri için setValue() kullanarak verileri belirtilen bir referansa kaydedebilir ve bu yoldaki mevcut verilerin yerini alabilirsiniz. Bu yöntemi kullanarak şunları yapabilirsiniz:

  • Kullanılabilir JSON türlerine karşılık gelen kart türleri aşağıdaki gibidir:
    • String
    • Long
    • Double
    • Boolean
    • Map<String, Object>
    • List<Object>
  • Tanımlayan sınıfın, bağımsız değişken almayan ve atanacak özellikler için herkese açık alıcıları olan varsayılan bir oluşturucusu varsa özel bir Java nesnesi iletin.

Bir Java nesnesi kullanırsanız nesnenizin içeriği, iç içe yerleştirilmiş şekilde alt konumlara otomatik olarak eşlenir. Java nesnesi kullanmak da genellikle kodunuzu daha okunabilir ve bakımı daha kolay hale getirir. Örneğin, temel kullanıcı profili olan bir uygulamanız varsa User nesneniz aşağıdaki gibi görünebilir:

Kotlin

@IgnoreExtraProperties
data class User(val username: String? = null, val email: String? = null) {
    // Null default values create a no-argument default constructor, which is needed
    // for deserialization from a DataSnapshot.
}

Java

@IgnoreExtraProperties
public class User {

    public String username;
    public String email;

    public User() {
        // Default constructor required for calls to DataSnapshot.getValue(User.class)
    }

    public User(String username, String email) {
        this.username = username;
        this.email = email;
    }

}

setValue() ile kullanıcı eklemek için:

Kotlin

fun writeNewUser(userId: String, name: String, email: String) {
    val user = User(name, email)

    database.child("users").child(userId).setValue(user)
}

Java

public void writeNewUser(String userId, String name, String email) {
    User user = new User(name, email);

    mDatabase.child("users").child(userId).setValue(user);
}

setValue() öğesini bu şekilde kullanmak, belirtilen konumdaki verilerin (alt düğümler dahil) üzerine yazar. Ancak yine de tüm nesneyi yeniden yazmadan bir çocuğu güncelleyebilirsiniz. Kullanıcıların profillerini güncellemesine izin vermek istiyorsanız kullanıcı adını aşağıdaki gibi güncelleyebilirsiniz:

Kotlin

database.child("users").child(userId).child("username").setValue(name)

Java

mDatabase.child("users").child(userId).child("username").setValue(name);

Verileri okuma

Kalıcı dinleyicilerle veri okuma

Bir yoldaki verileri okumak ve değişiklikleri dinlemek için addValueEventListener() yöntemini kullanarak DatabaseReference öğesine ValueEventListener ekleyin.

Dinleyici Etkinlik geri araması Tipik kullanım
ValueEventListener onDataChange() Bir yolun tüm içeriğindeki değişiklikleri okuma ve dinleme

Belirli bir yoldaki içeriklerin, etkinlik sırasında olduğu şekliyle statik bir anlık görüntüsünü okumak için onDataChange() yöntemini kullanabilirsiniz. Bu yöntem, dinleyici eklendiğinde bir kez, veriler (çocuklar dahil) her değiştiğinde ise tekrar tetiklenir. Etkinlik geri çağırmasına, alt veriler de dahil olmak üzere o konumdaki tüm verileri içeren bir anlık görüntü iletilir. Veri yoksa exists()'ı aradığınızda anlık görüntü false, getValue()'ı aradığınızda ise null değerini döndürür.

Aşağıdaki örnekte, bir gönderinin ayrıntılarını veritabanından alan sosyal blog uygulaması gösterilmektedir:

Kotlin

val postListener = object : ValueEventListener {
    override fun onDataChange(dataSnapshot: DataSnapshot) {
        // Get Post object and use the values to update the UI
        val post = dataSnapshot.getValue<Post>()
        // ...
    }

    override fun onCancelled(databaseError: DatabaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException())
    }
}
postReference.addValueEventListener(postListener)

Java

ValueEventListener postListener = new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        // Get Post object and use the values to update the UI
        Post post = dataSnapshot.getValue(Post.class);
        // ..
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
    }
};
mPostReference.addValueEventListener(postListener);

Dinleyici, etkinlik sırasında veritabanında belirtilen konumdaki verileri içeren bir DataSnapshot alır. Bir anlık görüntüde getValue() çağrıldığında verilerin Java nesne temsili döndürülür. Konumda veri yoksa getValue() çağrısı null değerini döndürür.

Bu örnekte ValueEventListener, okuma işlemi iptal edilirse çağrılan onCancelled() yöntemini de tanımlar. Örneğin, istemcinin bir Firebase veritabanı konumundan okuma izni yoksa okuma işlemi iptal edilebilir. Bu yönteme, hatanın nedenini belirten bir DatabaseError nesnesi iletilir.

Verileri bir kez okuma

get() kullanarak bir kez okuma

SDK, uygulamanızın çevrimiçi veya çevrimdışı olmasına bakılmaksızın veritabanı sunucularıyla etkileşimleri yönetmek için tasarlanmıştır.

Genel olarak, arka uçtaki verilerde yapılan güncellemelerden haberdar olmak için verileri okumak üzere ValueEventListener yukarıda açıklanan teknikleri kullanmanız gerekir. Dinleyici teknikleri, kullanımınızı ve faturalandırmanızı azaltır. Ayrıca, kullanıcılarınıza çevrimiçi ve çevrimdışı oldukları sırada en iyi deneyimi sunmak için optimize edilmiştir.

Verilere yalnızca bir kez ihtiyacınız varsa veritabanındaki verilerin anlık görüntüsünü almak için get() kullanabilirsiniz. get() herhangi bir nedenle sunucu değerini döndüremiyorsa istemci, yerel depolama önbelleğini yoklar ve değer hâlâ bulunamazsa bir hata döndürür.

get() öğesinin gereksiz kullanımı bant genişliği kullanımını artırabilir ve performans kaybına yol açabilir. Bu durum, yukarıda gösterildiği gibi gerçek zamanlı bir dinleyici kullanılarak önlenebilir.

Kotlin

mDatabase.child("users").child(userId).get().addOnSuccessListener {
    Log.i("firebase", "Got value ${it.value}")
}.addOnFailureListener{
    Log.e("firebase", "Error getting data", it)
}

Java

mDatabase.child("users").child(userId).get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
    @Override
    public void onComplete(@NonNull Task<DataSnapshot> task) {
        if (!task.isSuccessful()) {
            Log.e("firebase", "Error getting data", task.getException());
        }
        else {
            Log.d("firebase", String.valueOf(task.getResult().getValue()));
        }
    }
});

Dinleyici kullanarak bir kez okuma

Bazı durumlarda, sunucuda güncellenmiş bir değer olup olmadığını kontrol etmek yerine yerel önbellekteki değerin hemen döndürülmesini isteyebilirsiniz. Bu gibi durumlarda, verileri yerel disk önbelleğinden hemen almak için addListenerForSingleValueEvent kullanabilirsiniz.

Bu, yalnızca bir kez yüklenmesi gereken ve sık sık değişmesi ya da aktif dinleme gerektirmesi beklenmeyen veriler için kullanışlıdır. Örneğin, önceki örneklerdeki blog uygulaması, kullanıcı yeni bir gönderi yazmaya başladığında profilini yüklemek için bu yöntemi kullanır.

Verileri güncelleme veya silme

Belirli alanları güncelleme

Bir düğümün belirli alt öğelerine diğer alt düğümleri üzerine yazmadan aynı anda yazmak için updateChildren() yöntemini kullanın.

updateChildren() çağrısı yaparken anahtar için bir yol belirterek alt düzeydeki alt değerleri güncelleyebilirsiniz. Veriler daha iyi ölçeklendirme için birden fazla konumda depolanıyorsa veri dağıtımı kullanarak bu verilerin tüm örneklerini güncelleyebilirsiniz. Örneğin, bir sosyal blog uygulaması aşağıdaki gibi bir Post sınıfına sahip olabilir:

Kotlin

@IgnoreExtraProperties
data class Post(
    var uid: String? = "",
    var author: String? = "",
    var title: String? = "",
    var body: String? = "",
    var starCount: Int = 0,
    var stars: MutableMap<String, Boolean> = HashMap(),
) {

    @Exclude
    fun toMap(): Map<String, Any?> {
        return mapOf(
            "uid" to uid,
            "author" to author,
            "title" to title,
            "body" to body,
            "starCount" to starCount,
            "stars" to stars,
        )
    }
}

Java

@IgnoreExtraProperties
public class Post {

    public String uid;
    public String author;
    public String title;
    public String body;
    public int starCount = 0;
    public Map<String, Boolean> stars = new HashMap<>();

    public Post() {
        // Default constructor required for calls to DataSnapshot.getValue(Post.class)
    }

    public Post(String uid, String author, String title, String body) {
        this.uid = uid;
        this.author = author;
        this.title = title;
        this.body = body;
    }

    @Exclude
    public Map<String, Object> toMap() {
        HashMap<String, Object> result = new HashMap<>();
        result.put("uid", uid);
        result.put("author", author);
        result.put("title", title);
        result.put("body", body);
        result.put("starCount", starCount);
        result.put("stars", stars);

        return result;
    }
}

Bir yayın oluşturmak ve aynı anda hem son etkinlikler feed'inde hem de yayını yapan kullanıcının etkinlik feed'inde güncellemek için blog uygulaması şu gibi bir kod kullanır:

Kotlin

private fun writeNewPost(userId: String, username: String, title: String, body: String) {
    // Create new post at /user-posts/$userid/$postid and at
    // /posts/$postid simultaneously
    val key = database.child("posts").push().key
    if (key == null) {
        Log.w(TAG, "Couldn't get push key for posts")
        return
    }

    val post = Post(userId, username, title, body)
    val postValues = post.toMap()

    val childUpdates = hashMapOf<String, Any>(
        "/posts/$key" to postValues,
        "/user-posts/$userId/$key" to postValues,
    )

    database.updateChildren(childUpdates)
}

Java

private void writeNewPost(String userId, String username, String title, String body) {
    // Create new post at /user-posts/$userid/$postid and at
    // /posts/$postid simultaneously
    String key = mDatabase.child("posts").push().getKey();
    Post post = new Post(userId, username, title, body);
    Map<String, Object> postValues = post.toMap();

    Map<String, Object> childUpdates = new HashMap<>();
    childUpdates.put("/posts/" + key, postValues);
    childUpdates.put("/user-posts/" + userId + "/" + key, postValues);

    mDatabase.updateChildren(childUpdates);
}

Bu örnekte, push() kullanılarak /posts/$postid adresindeki tüm kullanıcıların yayınlarını içeren düğümde bir yayın oluşturuluyor ve aynı anda getKey() ile anahtar alınıyor. Ardından anahtar, kullanıcının /user-posts/$userid/$postid adresindeki gönderilerinde ikinci bir giriş oluşturmak için kullanılabilir.

Bu yolları kullanarak, updateChildren() için tek bir çağrıyla JSON ağacındaki birden fazla konumda eşzamanlı güncellemeler yapabilirsiniz. Örneğin, bu örnekte yeni gönderi her iki konumda da oluşturulur. Bu şekilde yapılan eşzamanlı güncellemeler atomiktir: Tüm güncellemeler başarılı olur veya tüm güncellemeler başarısız olur.

Tamamlama geri çağırması ekleme

Verilerinizin ne zaman işlendiğini öğrenmek istiyorsanız tamamlanma işleyicisi ekleyebilirsiniz. Hem setValue() hem de updateChildren(), yazma işlemi veritabanına başarıyla kaydedildiğinde çağrılan isteğe bağlı bir tamamlama işleyicisi alır. Çağrı başarısız olursa dinleyiciye, başarısızlığın nedenini belirten bir hata nesnesi iletilir.

Kotlin

database.child("users").child(userId).setValue(user)
    .addOnSuccessListener {
        // Write was successful!
        // ...
    }
    .addOnFailureListener {
        // Write failed
        // ...
    }

Java

mDatabase.child("users").child(userId).setValue(user)
        .addOnSuccessListener(new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void aVoid) {
                // Write was successful!
                // ...
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // Write failed
                // ...
            }
        });

Verileri silin

Verileri silmenin en basit yolu, verilerin konumuna yapılan bir referansta removeValue() işlevini çağırmaktır.

Ayrıca null veya updateChildren() gibi başka bir yazma işlemi için değer olarak setValue() belirterek de silebilirsiniz. Bu tekniği updateChildren() ile birlikte kullanarak tek bir API çağrısında birden fazla alt öğeyi silebilirsiniz.

Dinleyicileri ayırma

Geri aramalar, Firebase veritabanı referansınızda removeEventListener() yöntemi çağrılarak kaldırılır.

Bir dinleyici bir veri konumuna birden fazla kez eklenmişse her etkinlik için birden fazla kez çağrılır ve tamamen kaldırmak için aynı sayıda ayırmanız gerekir.

Bir ebeveyn dinleyicide removeEventListener() çağrısı yapılması, alt düğümlerinde kayıtlı dinleyicileri otomatik olarak kaldırmaz. Geri çağırmayı kaldırmak için tüm alt dinleyicilerde de removeEventListener() çağrısı yapılmalıdır.

Verileri işlem olarak kaydetme

Eşzamanlı değişiklikler nedeniyle bozulabilecek verilerle (ör. artımlı sayaçlar) çalışırken işlem işlemi kullanabilirsiniz. Bu işleme iki bağımsız değişken iletirsiniz: bir güncelleme işlevi ve isteğe bağlı bir tamamlanma geri çağırması. Güncelleme işlevi, verilerin mevcut durumunu bağımsız değişken olarak alır ve yazmak istediğiniz yeni durumu döndürür. Başka bir istemci, yeni değeriniz başarıyla yazılmadan önce konuma yazarsa güncelleme işleviniz yeni geçerli değerle tekrar çağrılır ve yazma işlemi yeniden denenir.

Örneğin, örnek sosyal blog uygulamasında kullanıcıların gönderilere yıldız eklemesine ve yıldızları kaldırmasına izin verebilir, ayrıca bir gönderinin kaç yıldız aldığını aşağıdaki gibi takip edebilirsiniz:

Kotlin

private fun onStarClicked(postRef: DatabaseReference) {
    // ...
    postRef.runTransaction(object : Transaction.Handler {
        override fun doTransaction(mutableData: MutableData): Transaction.Result {
            val p = mutableData.getValue(Post::class.java)
                ?: return Transaction.success(mutableData)

            if (p.stars.containsKey(uid)) {
                // Unstar the post and remove self from stars
                p.starCount = p.starCount - 1
                p.stars.remove(uid)
            } else {
                // Star the post and add self to stars
                p.starCount = p.starCount + 1
                p.stars[uid] = true
            }

            // Set value and report transaction success
            mutableData.value = p
            return Transaction.success(mutableData)
        }

        override fun onComplete(
            databaseError: DatabaseError?,
            committed: Boolean,
            currentData: DataSnapshot?,
        ) {
            // Transaction completed
            Log.d(TAG, "postTransaction:onComplete:" + databaseError!!)
        }
    })
}

Java

private void onStarClicked(DatabaseReference postRef) {
    postRef.runTransaction(new Transaction.Handler() {
        @NonNull
        @Override
        public Transaction.Result doTransaction(@NonNull MutableData mutableData) {
            Post p = mutableData.getValue(Post.class);
            if (p == null) {
                return Transaction.success(mutableData);
            }

            if (p.stars.containsKey(getUid())) {
                // Unstar the post and remove self from stars
                p.starCount = p.starCount - 1;
                p.stars.remove(getUid());
            } else {
                // Star the post and add self to stars
                p.starCount = p.starCount + 1;
                p.stars.put(getUid(), true);
            }

            // Set value and report transaction success
            mutableData.setValue(p);
            return Transaction.success(mutableData);
        }

        @Override
        public void onComplete(DatabaseError databaseError, boolean committed,
                               DataSnapshot currentData) {
            // Transaction completed
            Log.d(TAG, "postTransaction:onComplete:" + databaseError);
        }
    });
}

İşlem kullanmak, birden fazla kullanıcı aynı anda aynı gönderiye yıldız verirse veya istemcide eski veriler varsa yıldız sayılarının yanlış olmasını önler. İşlem reddedilirse sunucu, mevcut değeri istemciye döndürür. İstemci, işlemi güncellenmiş değerle tekrar çalıştırır. Bu işlem, işlem kabul edilene veya çok fazla deneme yapılana kadar tekrarlanır.

Atomik sunucu tarafı artışları

Yukarıdaki kullanım alanında, veritabanına iki değer yazıyoruz: gönderiye yıldız ekleyen/kaldıran kullanıcının kimliği ve artırılmış yıldız sayısı. Kullanıcının yayını yıldızladığını zaten biliyorsak işlem yerine atomik artırma işlemi kullanabiliriz.

Kotlin

private fun onStarClicked(uid: String, key: String) {
    val updates: MutableMap<String, Any> = hashMapOf(
        "posts/$key/stars/$uid" to true,
        "posts/$key/starCount" to ServerValue.increment(1),
        "user-posts/$uid/$key/stars/$uid" to true,
        "user-posts/$uid/$key/starCount" to ServerValue.increment(1),
    )
    database.updateChildren(updates)
}

Java

private void onStarClicked(String uid, String key) {
    Map<String, Object> updates = new HashMap<>();
    updates.put("posts/"+key+"/stars/"+uid, true);
    updates.put("posts/"+key+"/starCount", ServerValue.increment(1));
    updates.put("user-posts/"+uid+"/"+key+"/stars/"+uid, true);
    updates.put("user-posts/"+uid+"/"+key+"/starCount", ServerValue.increment(1));
    mDatabase.updateChildren(updates);
}

Bu kodda işlem işlemi kullanılmadığı için çakışan bir güncelleme olduğunda otomatik olarak yeniden çalıştırılmaz. Ancak artırma işlemi doğrudan veritabanı sunucusunda gerçekleştiği için çakışma olasılığı yoktur.

Uygulamaya özgü çakışmaları (ör. kullanıcının daha önce yıldızladığı bir gönderiyi tekrar yıldızlaması) tespit edip reddetmek istiyorsanız bu kullanım alanı için özel güvenlik kuralları yazmanız gerekir.

Verilerle çevrimdışı çalışma

Bir istemcinin ağ bağlantısı kesilirse uygulamanız doğru şekilde çalışmaya devam eder.

Bir Firebase veritabanına bağlı her istemci, dinleyicilerin kullanıldığı veya sunucuyla senkronize tutulması için işaretlenen tüm verilerin kendi dahili sürümünü korur. Veriler okunduğunda veya yazıldığında önce verilerin bu yerel sürümü kullanılır. Ardından Firebase istemcisi, bu verileri uzaktaki veritabanı sunucularıyla ve diğer istemcilerle "en iyi çaba" prensibine göre senkronize eder.

Sonuç olarak, veritabanına yapılan tüm yazma işlemleri, sunucuyla herhangi bir etkileşim olmadan hemen yerel etkinlikleri tetikler. Bu sayede uygulamanız, ağ gecikmesinden veya bağlantıdan bağımsız olarak yanıt vermeye devam eder.

Bağlantı yeniden kurulduğunda uygulamanız, istemcinin mevcut sunucu durumuyla senkronize olması için uygun etkinlik kümesini alır. Bu işlem için özel kod yazmanız gerekmez.

Çevrimdışı davranış hakkında daha fazla bilgiyi Online ve çevrimdışı özellikler hakkında daha fazla bilgi başlıklı makalede bulabilirsiniz.

Sonraki adımlar