Android'de Veri Okuma ve Yazma

Bu belgede, Firebase verilerini okuma ve yazmayla ilgili temel bilgiler yer almaktadır.

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 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 üzerinde nasıl okuma ve yazma yaptığından bahsetmeden önce, Realtime Database işlevinin prototipini oluşturmak ve test etmek için kullanabileceğiniz bir dizi araçtan bahsedelim: Firebase Local Emulator Suite. Farklı veri modellerini deniyor, güvenlik kurallarınızı optimize ediyor veya arka uçla etkileşim kurmanın en uygun maliyetli yolunu bulmaya çalışıyorsanız canlı hizmetleri dağıtmadan yerel olarak çalışmak iyi bir fikir olabilir.

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

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

Realtime Database ve Cloud Functions ile ilgili ayrıntılı bir açıklamayı inceleyebilirsiniz. Local Emulator Suite girişine de göz atmanız önerilir.

DatabaseReference alma

Veritabanından veri okumak veya veritabanı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();

Verileri yazma

Temel yazma işlemleri

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

  • Mevcut JSON türlerine karşılık gelen kart türleri şunlardır:
    • String
    • Long
    • Double
    • Boolean
    • Map<String, Object>
    • List<Object>
  • Sınıfı, bağımsız değişken içermeyen varsayılan bir oluşturucuya ve atanacak özellikler için herkese açık alıcılara sahipse özel bir Java nesnesi iletin.

Java nesnesi kullanıyorsanız nesnenizin içeriği otomatik olarak iç içe yerleştirilmiş şekilde alt konumlara eşlenir. Java nesnesi kullanmak, kodunuzu daha okunabilir ve bakımı daha kolay hale getirir. Örneğin, temel bir kullanıcı profiline sahip 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 aşağıdaki adımları uygulayabilirsiniz:

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() yöntemini bu şekilde kullanmak, alt düğümler de dahil olmak üzere belirtilen konumdaki verilerin üzerine yazar. Ancak nesnenin tamamını yeniden yazmadan 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

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 çağırma Tipik kullanım
ValueEventListener onDataChange() Bir yolun tüm içeriğindeki değişiklikleri okuma ve dinleme.

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

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

get() kullanarak bir kez okuma

SDK, uygulamanızın internete bağlı olup olmamasından bağımsız olarak veritabanı sunucularıyla etkileşimleri yönetmek için tasarlanmıştır.

Genellikle, arka uçtan gelen veri güncellemeleriyle ilgili bildirim almak 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 çevrimiçi ve çevrimdışı olduklarında en iyi deneyimi sunacak şekilde 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() yöntemini kullanabilirsiniz. get() herhangi bir nedenle sunucu değerini döndüremezse istemci, yerel depolama önbelleğini inceler ve değer hâlâ bulunamazsa hata döndürür.

get() yönteminin 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 veya etkin dinleme gerektirmesi beklenmeyen veriler için kullanışlıdır. Örneğin, önceki örneklerdeki blog uygulaması, kullanıcı yeni bir yayın 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() yöntemini çağırırken anahtar için bir yol belirterek alt düzey alt değerleri güncelleyebilirsiniz. Veriler daha iyi ölçeklendirme 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ında aşağıdaki gibi bir Post sınıfı 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;
    }
}

Blog uygulaması, bir yayın oluşturmak ve bu yayını aynı anda son etkinlik feed'ine ve yayınlayan kullanıcının etkinlik feed'ine güncellemek için aşağıdaki 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, /posts/$postid adresindeki tüm kullanıcılar için yayınları içeren düğümde bir yayın oluşturmak üzere push() kullanılır ve aynı anda getKey() ile anahtar alınır. 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, JSON ağacındaki birden fazla konumda aynı anda güncelleme yapmak için updateChildren() yöntemini tek bir kez çağırabilirsiniz. Bu örnekte, yeni yayın her iki konumda da oluşturulmaktadır. Bu şekilde yapılan eşzamanlı güncellemeler atomiktir: Güncellemelerin ya hepsi başarılı olur ya da hepsi başarısız olur.

Tamamlama geri çağırma işlevi ekleme

Verilerinizin ne zaman kaydedildiğini öğrenmek istiyorsanız bir tamamlama işleyicisi 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 dinleyicisi alır. Çağrı başarısız olursa dinleyiciye hatanı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 kolay yolu, verilerin konumuna referans vererek removeValue() yöntemini çağırmaktır.

setValue() veya updateChildren() gibi başka bir yazma işlemi için değer 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 ayırma

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

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

Üst dinleyicide removeEventListener() yöntemini çağırmak, alt düğümlerinde kayıtlı dinleyicileri otomatik olarak kaldırmaz. Geri çağırmayı kaldırmak için alt dinleyicilerde de removeEventListener() yönteminin çağrılması gerekir.

Verileri işlem olarak kaydetme

Artımlı sayaçlar gibi eşzamanlı değişiklikler nedeniyle bozulabilecek verilerle çalışırken işlem operasyonu kullanabilirsiniz. Bu işleme iki bağımsız değişken verirsiniz: 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, sosyal blog uygulaması örneğinde kullanıcıların yayınlara yıldız ekleyip kaldırmasına izin verebilir ve bir yayının 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ı yayına 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 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 tekrarlanır.

Atomik sunucu tarafı artışları

Yukarıdaki kullanım alanında veritabanına iki değer yazıyoruz: yayına yıldız ekleyen/yıldızı kaldıran kullanıcının kimliği ve artırılan 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

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 operasyonu kullanılmadığından, ç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.

Kullanıcının daha önce yıldız eklediği bir yayına tekrar yıldız eklemesi gibi uygulamaya özel çakışmaları tespit edip 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.

Bir Firebase veritabanına bağlı her istemci, dinleyicilerin kullanıldığı veya sunucuyla senkronize tutulmak üzere işaretlenen tüm verilerin kendi dahili sürümünü saklar. 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, sunucuyla herhangi bir etkileşimden önce yerel etkinlikleri hemen tetikler. Bu sayede uygulamanız, ağ gecikmesi veya bağlantı durumundan bağımsız olarak her zaman yanıt verir.

Bağlantı yeniden kurulduğunda uygulamanız uygun etkinlik grubunu alır. Böylece istemci, herhangi bir özel kod yazmaya gerek kalmadan mevcut sunucu durumuyla senkronize olur.

Çevrimdışı davranış hakkında daha fazla bilgiyi Online ve çevrimdışı özellikler hakkında daha fazla bilgi bölümünde bulabilirsiniz.

Sonraki adımlar