Android'de Veri Okuma ve Yazma

Bu dokümanda, Firebase verilerini okuma ve yazmayla ilgili temel bilgiler ele alınmaktadır.

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

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

Uygulamanızın Realtime Database'den nasıl veri okuduğu ve yazdığı hakkında konuşmadan önce, Realtime Database işlevini prototip haline getirmek ve test etmek için kullanabileceğiniz bir araç grubu olan Firebase Local Emulator Suite'den bahsedelim. Farklı veri modellerini denemek, güvenlik kurallarınızı optimize etmek veya arka uçla etkileşim kurmanın en uygun maliyetli yolunu bulmak için çalışıyorsanız canlı hizmetler dağıtmadan yerel olarak çalışabilmek iyi bir fikir olabilir.

Realtime Database emülatörü, Local Emulator Suite'un bir parçasıdır. Bu emülatör, uygulamanızın emülasyonlu veritabanı içeriğiniz ve yapılandırmanızın yanı sıra isteğe bağlı olarak emülasyonlu 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ü kullanmanın birkaç adımı vardır:

  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 çalıştırın.
  3. Uygulamanızın prototip kodundan her zamanki gibi bir Realtime Database platform SDK'sı veya Realtime Database REST API'si kullanarak çağrı yapma.

Realtime Database ve Cloud Functions ile ilgili ayrıntılı bir adım adım açıklamalı kılavuz mevcuttur. Local Emulator Suite girişine de göz atmanız önerilir.

DatabaseReference alma

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

Kotlin+KTX

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, verileri belirtilen bir referansa kaydetmek ve bu yoldaki mevcut verileri değiştirmek üzere setValue()'ü kullanabilirsiniz. Bu yöntemi kullanarak şunları yapabilirsiniz:

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

Java nesnesi kullanıyorsanız nesnenizin içeriği, iç içe yerleştirilmiş şekilde otomatik olarak alt konumlarla eşlenir. Java nesnesi kullanmak, genellikle kodunuzun daha okunaklı ve bakımı daha kolay olmasını sağlar. Örneğin, temel kullanıcı profiline sahip bir uygulamanız varsa User nesneniz aşağıdaki gibi görünebilir:

Kotlin+KTX

@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 aşağıdaki adımları uygulayın:

Kotlin+KTX

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, alt düğümler dahil olmak üzere belirtilen konumdaki verilerin üzerine yazar. Ancak yine de nesnenin tamamını yeniden yazmadan bir alt öğeyi güncelleyebilirsiniz. Kullanıcıların profillerini güncellemelerine izin vermek istiyorsanız kullanıcı adını aşağıdaki gibi güncelleyebilirsiniz:

Kotlin+KTX

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 DatabaseReference'ye ValueEventListener eklemek üzere addValueEventListener()yöntemini kullanın.

Dinleyici Etkinlik geri araması Tipik kullanım
ValueEventListener onDataChange() Bir yolun tüm içeriklerindeki değişiklikleri okuyup dinleyebilirsiniz.

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

Aşağıdaki örnekte, bir sosyal blog uygulamasının veritabanından bir yayınla ilgili ayrıntıları aldığı gösterilmektedir:

Kotlin+KTX

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ısı yapıldığında verilerin Java nesnesi 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 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() işlevini kullanarak bir kez okuma

SDK, uygulamanız çevrimiçi veya çevrimdışıyken veritabanı sunucularıyla olan 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 yukarıda açıklanan ValueEventListener tekniklerini kullanmanız gerekir. Dinleyici teknikleri, kullanımınızı ve faturalandırmanızı azaltır ve kullanıcılarınıza internete bağlanırken ve internete bağlanmazken en iyi deneyimi sunmak için optimize edilir.

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

get()'ün gereksiz kullanımı bant genişliği kullanımını artırabilir ve performans kaybına neden olabilir. Bu durum, yukarıda gösterildiği gibi anlık dinleyici kullanılarak önlenebilir.

Kotlin+KTX

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()));
        }
    }
});

Bir dinleyici kullanarak bir kez okuma

Bazı durumlarda, sunucuda güncel bir değer olup olmadığını kontrol etmek yerine yerel önbelleğe ait değerin hemen döndürülmesini isteyebilirsiniz. Bu 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 veya etkin 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 kullanıcının profilini yüklemek için bu yöntemi kullanır.

Verileri güncelleme veya silme

Belirli alanları güncelleme

Diğer alt düğümlerin üzerine yazmadan bir düğümün belirli alt düğümlerine 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 öğe değerlerini güncelleyebilirsiniz. Veriler daha iyi ölçeklendirme için birden fazla konumda depolanıyorsa veri dağıtımını kullanarak bu verilerin tüm örneklerini güncelleyebilirsiniz. Örneğin, sosyal blog uygulamasında şuna benzer bir Post sınıfı olabilir:

Kotlin+KTX

@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;
    }
}

Blog uygulaması, bir yayın oluşturup aynı anda son etkinlik feed'inde ve yayın yapan kullanıcının etkinlik feed'inde güncellemek için aşağıdaki gibi bir kod kullanır:

Kotlin+KTX

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, /posts/$postid'daki tüm kullanıcıların yayınlarını içeren bir yayın oluşturmak için push() kullanılır ve getKey() ile anahtar aynı anda alınır. Anahtar daha sonra /user-posts/$userid/$postid adresindeki kullanıcının yayınlarında ikinci bir giriş oluşturmak için kullanılabilir.

Bu yolları kullanarak, updateChildren() çağrısını tek seferde yaparak JSON ağacındaki birden fazla konumda eşzamanlı güncellemeler yapabilirsiniz. Bu örnekte, yeni yayın her iki konumda da bu şekilde 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ğırma işlevi ekleme

Verilerinizin ne zaman bağlandığını bilmek istiyorsanız tamamlama dinleyicisi ekleyebilirsiniz. Hem setValue() hem de updateChildren(), yazma işlemi veritabanına başarıyla bağlandığında çağrılan isteğe bağlı bir tamamlama dinleyicisi alır. Çağrı başarısız olursa dinleyiciye, hatanın nedenini belirten bir hata nesnesi iletilir.

Kotlin+KTX

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, söz konusu verilerin konumuna ait bir referans üzerinde removeValue() işlevini çağırmaktır.

setValue() veya updateChildren() gibi başka bir yazma işleminin değeri olarak null'ü belirterek de silebilirsiniz. Tek bir API çağrısında birden fazla alt öğeyi silmek için bu tekniği updateChildren() ile kullanabilirsiniz.

Dinleyicileri kaldırma

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

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

Üst dinleyicide removeEventListener() çağrıldığında, alt düğümlerine kayıtlı dinleyiciler otomatik olarak kaldırılmaz. Geri çağırma işlevinin kaldırılması için removeEventListener(), alt dinleyicilerde de çağrılmalıdır.

Verileri işlem olarak kaydetme

Artımlı sayaçlar gibi eşzamanlı değişiklikler nedeniyle bozulabilen verilerle çalışırken işlem işlemi kullanabilirsiniz. Bu işleme iki bağımsız değişken gönderirsiniz: bir güncelleme işlevi ve isteğe bağlı bir tamamlama geri çağırma işlevi. 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. Yeni değeriniz başarıyla yazılmadan önce başka bir istemci 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 yayınlara yıldız eklemesine ve yıldızları kaldırmasına izin verebilir ve bir yayının aldığı yıldız sayısını aşağıdaki gibi takip edebilirsiniz:

Kotlin+KTX

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ı yayına yıldız atarsa 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 de işlemi güncellenmiş değerle tekrar çalıştırır. İşlem kabul edilene veya çok fazla deneme yapılana kadar bu işlem 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/yıldız işaretini kaldıran kullanıcının kimliği ve artan yıldız sayısı. Kullanıcının yayını favorilediğini zaten biliyorsak işlem yerine atomik artış işlemi kullanabiliriz.

Kotlin+KTX

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 kod bir işlem işlemi kullanmadığından, çakışan bir güncelleme varsa otomatik olarak yeniden çalıştırılmaz. Ancak artma işlemi doğrudan veritabanı sunucusunda gerçekleştiği için çakışma olasılığı yoktur.

Kullanıcının daha önce yıldızla işaretlediği bir yayını yıldızla işaretlemesi gibi uygulamaya özgü çakışmaların tespit edilip reddedilmesini 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 düzgün çalışmaya devam eder.

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

Sonuç olarak, veritabanına yapılan tüm yazma işlemleri, sunucuyla herhangi bir etkileşimden önce yerel etkinlikleri hemen tetikler. Böylece uygulamanız, ağ gecikmesinden veya bağlantısından etkilenmeden yanıt vermeye devam eder.

Bağlantı yeniden kurulduktan sonra uygulamanız, özel kod yazmak zorunda kalmadan istemcinin mevcut sunucu durumuyla senkronize edilmesi için uygun etkinlik grubunu alır.

Online ve çevrimdışı özellikler hakkında daha fazla bilgi başlıklı makalede çevrimdışı davranış hakkında daha fazla bilgi vereceğiz.

Sonraki adımlar