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 modelleri 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ğiniz ve yapılandırmanızı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 yalnızca birkaç adım yeterlidir:

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

Realtime Database ve Cloud Functions ile ilgili ayrıntılı bir adım adım 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>
  • Tanımlayan sınıfın bağımsız değişken içermeyen varsayılan bir oluşturucusu ve atanacak özellikler için herkese açık alıcıları varsa özel bir Java nesnesi iletin.

Java nesnesi kullanıyorsanız nesnenizin içeriği, alt konumlara iç içe yerleştirilmiş şekilde otomatik olarak 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() bu şekilde kullanıldığında, alt düğümler de dahil olmak üzere belirtilen konumdaki verilerin üzerine yazılır. 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ısı yaptığınızda false, getValue() çağrısı yaptığınızda ise null döndürür.

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

İşleyici, 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 çevrimiçi veya çevrimdışı olmasına bakılmaksızın veritabanı sunucularıyla etkileşimleri yönetecek şekilde 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() kullanabilirsiniz. get() herhangi bir nedenle sunucu değerini döndüremezse istemci, yerel depolama önbelleğini inceler ve değer hâlâ bulunamazsa bir 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 yöntem, 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 oluşturmaya 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ını yapan 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ın yayınlarını içeren düğümde bir yayın oluşturmak için 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ın her iki konumda da nasıl oluşturulduğu gösterilmektedir. 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 çok kez eklenmişse her etkinlik için birden çok 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şlem için iki bağımsız değişken belirtirsiniz: güncelleme işlevi ve isteğe bağlı 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 ve bir yayının kaç yıldız aldığını aşağıdaki gibi takip etmesine izin verebilirsiniz:

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ının aynı yayına aynı anda yıldız vermesi veya istemcinin eski verilere sahip olması durumunda yıldız sayılarının yanlış olmasını önler. İşlem reddedilirse sunucu, mevcut değeri istemciye döndürür ve istemci, 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.

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 yanıt vermeye devam eder.

Bağlantı yeniden kurulduktan sonra uygulamanız, istemcinin mevcut sunucu durumuyla senkronize olması için uygun etkinlik grubunu 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 bölümünde bulabilirsiniz.

Sonraki adımlar