Android'de Veri Okuma ve Yazma

Bu belgede Firebase verilerini okuma ve yazmayla ilgili temel bilgiler verilmektedir.

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

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

Uygulamanızın Realtime Database'den nasıl okuduğu ve Realtime Database'e nasıl yazıldığı hakkında konuşmadan önce, Realtime Database işlevinin prototipini oluşturmak ve test etmek için kullanabileceğiniz Firebase Local Emulator Suite adlı araçlardan bahsedelim. Farklı veri modelleri 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 çok iyi bir fikir olabilir.

Realtime Database emülatörü, Local Emulator Suite'in bir parçasıdır. Bu paket, uygulamanızın emüle edilmiş veritabanı içeriğiniz ve yapılandırmanızın yanı sıra isteğe bağlı olarak emüle edilen proje kaynaklarınızla (işlevler, diğer veritabanları ve güvenlik kuralları) etkileşimde bulunmasını sağlar.

Realtime Database emülatörünün kullanımı yalnızca birkaç adımdan oluşur:

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

Realtime Database ve Cloud Functions'ı içeren adım adım açıklamalı ayrıntılı bir kılavuz mevcuttur. Ayrıca Local Emulator Suite tanıtımı'na da göz atmalısınız.

Veritabanı Referansı Alma

Veritabanından veri okumak veya yazmak için DatabaseReference örneğinin olması gerekir:

Kotlin+KTX

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

Java

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

Verileri yazma

Temel yazma işlemleri

Temel yazma işlemlerinde, verileri belirtilen bir referansa kaydetmek ve söz konusu yoldaki mevcut tüm verileri değiştirmek için setValue() kullanabilirsiniz. Bu yöntemi şu amaçlarla kullanabilirsiniz:

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

Java nesnesi kullanıyorsanız nesnenizin içeriği iç içe yerleştirilmiş bir biçimde otomatik olarak alt konumlarla eşlenir. Java nesnesi kullanmak, genellikle kodunuzun daha okunabilir ve bakımını kolaylaştırır. Ö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() uygulamasına sahip bir kullanıcıyı aşağıdaki şekilde ekleyebilirsiniz:

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() bu şekilde kullanıldığında, tüm alt düğümler de dahil olmak üzere belirtilen konumdaki verilerin üzerine yazılır. Ancak, nesnenin tamamını yeniden yazmadan da alt öğeleri güncelleyebilirsiniz. Kullanıcıların profillerini güncellemelerine izin vermek isterseniz kullanıcı adını aşağıdaki şekilde 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

Verileri kalıcı işleyicilerle okuma

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

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

Etkinlik sırasında mevcut oldukları için belirli bir yoldaki içeriklerin statik anlık görüntüsünü okumak için onDataChange() yöntemini kullanabilirsiniz. Bu yöntem, işleyici eklendiğinde bir kez ve alt öğeler dahil olmak üzere veriler her değiştiğinde tetiklenir. Etkinlik geri çağırmasına, alt veriler de dahil olmak üzere söz konusu konumdaki tüm verileri içeren bir anlık görüntü iletilir. Veri yoksa anlık görüntü, exists() çağrısı yaptığınızda false, getValue() çağrısı yaptığınızda ise null değerini döndürür.

Aşağıdaki örnekte, veritabanından bir yayının ayrıntılarını alan sosyal blog uygulaması 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);

İşleyici, etkinlik sırasında veritabanında belirtilen konumda verileri içeren bir DataSnapshot alır. Anlık görüntüde getValue() çağrısı yapmak, verilerin Java nesne gösterimini döndürür. Konumda veri yoksa getValue() işlevinin çağrılması null sonucunu döndürür.

Bu örnekte ValueEventListener, okuma işlemi iptal edildiğinde çağrılacak 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 neden oluştuğunu belirten bir DatabaseError nesnesi iletilir.

Verileri bir kez oku

get() kullanarak bir kez okuma

SDK, uygulamanızın online veya çevrimdışı olması fark etmeksizin veritabanı sunucularıyla etkileşimleri yönetecek şekilde tasarlanmıştır.

Genel olarak, verilerde yapılan güncellemelerle ilgili olarak arka uçtan bildirim almak amacıyla verileri okumak için yukarıda açıklanan ValueEventListener tekniklerini kullanmanız gerekir. Dinleme teknikleri, kullanımınızı ve faturalandırmanızı azaltır, kullanıcılarınıza dijital ve çevrimdışı ortamda en iyi deneyimi sunmak için optimize edilmiştir.

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

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

Dinleyici kullanarak bir kez okuma

Bazı durumlarda, sunucuda güncellenmiş değeri kontrol etmek yerine yerel önbellekteki 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 beklenmeyen veya etkin dinleme gerektirmesi beklenen veriler için faydalıdır. Örneğin, önceki örneklerde bahsedilen blog uygulaması, yeni bir yayın yazmaya başlayan kullanıcının profilini yüklemek için bu yöntemi kullanır.

Verileri güncelleme veya silme

Belirli alanları güncelle

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

updateChildren() çağrısı yapılırken, anahtarın yolunu belirterek alt düzey alt değerleri güncelleyebilirsiniz. Veriler daha iyi ölçeklendirmek için birden fazla konumda depolanıyorsa veri yayma yöntemini kullanarak bu verilerin tüm örneklerini güncelleyebilirsiniz. Örneğin, bir sosyal blog uygulamasının ş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;
    }
}

Bir yayın oluşturmak ve aynı anda bunu en son etkinlik feed'i ve yayınlayan kullanıcının etkinlik feed'iyle güncellemek için blog uygulaması 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 alanındaki tüm kullanıcılar için yayınları içeren düğümde bir yayın oluşturmak ve getKey() ile aynı anda anahtarı almak üzere push() kullanılmaktadır. Bu anahtar daha sonra kullanıcının /user-posts/$userid/$postid adresindeki yayınlarında ikinci bir giriş oluşturmak için kullanılabilir.

Bu yolları kullanarak, tek bir updateChildren() çağrısı yaparak JSON ağacındaki birden fazla konumda eş zamanlı güncellemeler yapabilirsiniz (örneğin, bu örneğin yeni yayını her iki konumda da nasıl oluşturduğu). Bu şekilde yapılan eş zamanlı güncellemeler son derece önemlidir: ya tüm güncellemeler başarılı olur ya da başarısız olur.

Tamamlama Geri Çağırması Ekle

Verilerinizin ne zaman kaydedildiğini öğrenmek istiyorsanız tamamlama dinleyici ekleyebilirsiniz. Hem setValue() hem de updateChildren(), yazma işlemi veritabanına başarıyla uygulandığında çağrılan isteğe bağlı bir tamamlama işleyiciyi kullanır. Çağrı başarısız olursa dinleyiciye hatanın neden oluştuğunu 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 referans vererek removeValue() öğesini çağırmaktır.

setValue() veya updateChildren() gibi başka bir yazma işlemi için null değerini belirterek de silme işlemini yapabilirsiniz. Bu tekniği, tek bir API çağrısında birden çok alt öğeyi silmek için updateChildren() ile kullanabilirsiniz.

Dinleyicileri ayır

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

İşleyici bir veri konumuna birden çok kez eklenmişse her etkinlik için birden çok kez çağrılır ve tamamen kaldırmak için aynı sayıda çıkarmanız gerekir.

Bir üst işleyicide removeEventListener() çağrısı yapıldığında, alt düğümlerde kayıtlı işleyiciler otomatik olarak kaldırılmaz. Geri çağırmanın kaldırılması için tüm alt işleyicilerde de removeEventListener() çağrılmalıdır.

Verileri işlem olarak kaydet

Artımlı sayaçlar gibi eşzamanlı değişikliklere göre bozulabilecek verilerle çalışırken işlem işlemi kullanabilirsiniz. Bu işleme bir güncelleme işlevi ve bir isteğe bağlı tamamlama geri çağırması olmak üzere iki bağımsız değişken verirsiniz. 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ı uygulamasında, kullanıcıların yayınlara yıldız eklemesine ve yıldızlarını kaldırmasına ve bir yayının kaç yıldız aldığını aşağıdaki şekilde takip etmesine olanak tanıyabilirsiniz:

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

Bir işlem kullanmak, birden çok kullanıcının aynı yayına aynı anda yıldız göstermesi veya istemcinin eski verileri varsa yıldız sayılarının yanlış belirlenmesini önler. İşlem reddedilirse sunucu geçerli değeri istemciye döndürür. İstemci de 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 tekrar eder.

Atomik sunucu tarafı artışları

Yukarıdaki kullanım örneğinde veritabanına iki değer yazıyoruz: yayına yıldız veren/yıldızı kaldıran kullanıcının kimliği ve artan yıldız sayısı. Kullanıcının yayına yıldız eklediğini zaten biliyorsak işlem yerine atomik artırma 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şlemini kullanmaz. Bu nedenle, çakışan bir güncelleme olduğunda otomatik olarak yeniden çalıştırılmaz. Bununla birlikte, artırma 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ız eklediği bir yayına yıldız eklemesi gibi uygulamaya özgü çakışmaları tespit etmek ve reddetmek istiyorsanız bu kullanım alanı için özel güvenlik kuralları yazmanız gerekir.

Verilerle çevrimdışı çalışma

İstemcinin ağ bağlantısı kesilirse uygulamanız düzgün şekilde çalışmaya devam eder.

Firebase veritabanına bağlı her istemci, işleyicilerin kullanılmakta olduğu veya sunucuyla senkronize edilmek üzere işaretlenen tüm verilerin kendi dahili sürümlerini korur. Veriler okunurken veya yazılırken öncelikle verilerin bu yerel sürümü kullanılır. Firebase istemcisi daha sonra bu verileri uzak veritabanı sunucuları ve diğer istemcilerle "en iyi çaba" temelinde senkronize eder.

Sonuç olarak, veritabanına yapılan tüm yazma işlemleri yerel etkinlikleri sunucuyla herhangi bir etkileşimden hemen önce tetikler. Bu, uygulamanızın ağ gecikmesi veya bağlantıdan bağımsız olarak yanıt vermeye devam edeceği anlamına gelir.

Bağlantı yeniden kurulduktan sonra uygulamanız, uygun etkinlik grubunu alır. Böylece, istemci herhangi bir özel kod yazmak zorunda kalmadan mevcut sunucu durumuyla senkronize edilir.

Online ve çevrimdışı özellikler hakkında daha fazla bilgi bölümünde çevrimdışı davranış hakkında daha fazla bilgi vereceğiz.

Sonraki adımlar