Android'de Veri Okuma ve Yazma

Bu belge Firebase verilerini okuma ve yazmanın temellerini kapsar.

Firebase verileri bir FirebaseDatabase referansına yazılır ve referansa eşzamansız bir dinleyici eklenerek alınır. Dinleyici, verinin başlangıç ​​durumu için bir kez ve veri değiştiğinde tekrar tetiklenir.

(İsteğe bağlı) Firebase Local Emulator Suite ile prototip yapın ve test edin

Uygulamanızın Gerçek Zamanlı Veritabanından nasıl okuduğu ve ona nasıl yazdığı hakkında konuşmadan önce, Gerçek Zamanlı Veritabanı işlevselliğini prototiplemek ve test etmek için kullanabileceğiniz bir dizi aracı tanıtalım: Firebase Local Emulator Suite. 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 harika bir fikir olabilir.

Gerçek Zamanlı Veritabanı öykünücüsü, uygulamanızın öykünülmüş veritabanı içeriğiniz ve yapılandırmanızın yanı sıra isteğe bağlı olarak öykünülmüş proje kaynaklarınızla (işlevler, diğer veritabanları ve güvenlik kuralları) etkileşim kurmasını sağlayan Yerel Öykünücü Paketinin bir parçasıdır.

Gerçek Zamanlı Veritabanı öykünücüsünü kullanmak yalnızca birkaç adımı içerir:

  1. Öykünücüye 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 çalıştırılıyor.
  3. Her zamanki gibi bir Realtime Database platformu SDK'sını kullanarak veya Realtime Database REST API'yi kullanarak uygulamanızın prototip kodundan çağrı yapmak.

Gerçek Zamanlı Veritabanı ve Bulut İşlevlerini içeren 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ı Alın

Veritabanı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 yaz

Temel yazma işlemleri

Temel yazma işlemleri için, verileri belirli bir referansa kaydetmek ve o yoldaki mevcut verileri değiştirmek için setValue() kullanabilirsiniz. Bu yöntemi şu amaçlarla kullanabilirsiniz:

  • Mevcut JSON türlerine karşılık gelen geçiş türleri aşağıdaki gibidir:
    • String
    • Long
    • Double
    • Boolean
    • Map<String, Object>
    • List<Object>
  • Özel bir Java nesnesini, onu tanımlayan sınıfın hiçbir argüman almayan ve atanacak özellikler için genel alıcılara sahip varsayılan bir yapıcısı varsa iletin.

Bir Java nesnesi kullanıyorsanız, nesnenizin içeriği otomatik olarak alt konumlarla iç içe bir şekilde eşlenir. Bir Java nesnesi kullanmak aynı zamanda kodunuzu daha okunaklı ve bakımı kolay hale getirir. Ö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 bir kullanıcıyı aşağıdaki gibi 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() işlevinin bu şekilde kullanılması, alt düğümler de dahil olmak üzere belirtilen konumdaki verilerin üzerine yazar. Ancak nesnenin tamamını yeniden yazmadan bir alt öğeyi yine de güncelleyebilirsiniz. Kullanıcıların profillerini güncellemesine izin vermek istiyorsanız 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 oku

Kalıcı dinleyicilerle verileri okuyun

Bir yoldaki verileri okumak ve değişiklikleri dinlemek için, DatabaseReference öğesine bir ValueEventListener eklemek üzere addValueEventListener() yöntemini kullanın.

Dinleyici Olay geri araması Tipik kullanım
ValueEventListener onDataChange() Bir yolun tüm içeriğindeki değişiklikleri okuyun ve dinleyin.

Belirli bir yoldaki içeriklerin, olay anında mevcut olan statik anlık görüntüsünü okumak için onDataChange() yöntemini kullanabilirsiniz. Bu yöntem, dinleyici eklendiğinde bir kez tetiklenir ve çocuklar da dahil olmak üzere veriler her değiştiğinde tekrar tetiklenir. Olay geri çağrısına, alt veriler de dahil olmak üzere o konumdaki tüm verileri içeren bir anlık görüntü iletilir. Veri yoksa, anlık görüntü, exists() öğesini çağırdığınızda false , getValue() öğesini çağırdığınızda ise null döndürür.

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

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, olay anında veritabanında belirtilen konumdaki verileri içeren bir DataSnapshot alır. Anlık görüntüde getValue() işlevinin çağrılması, verilerin Java nesne temsilini döndürür. Konumda hiçbir veri yoksa getValue() işlevinin çağrılması null değerini döndürür.

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

Verileri bir kez okuyun

Get() kullanarak bir kez okuyun

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

Genel olarak, arka uçtan verilerdeki güncellemelerden haberdar olmak amacıyla verileri okumak için yukarıda açıklanan ValueEventListener tekniklerini kullanmalısınız. Dinleyici teknikleri, kullanımınızı ve faturalandırmanızı azaltır ve kullanıcılarınıza çevrimiçi ve çevrimdışıyken 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() komutunu kullanabilirsiniz. Herhangi bir nedenle get() sunucu değerini döndüremezse, istemci yerel depolama önbelleğini araştıracak ve değer hala bulunamazsa bir hata döndürecektir.

get() işlevinin gereksiz kullanımı, bant genişliği kullanımını artırabilir ve performans kaybına neden olabilir; bu, yukarıda gösterildiği gibi gerçek zamanlı bir 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 okuyun

Bazı durumlarda, sunucuda güncelleştirilmiş bir değer olup olmadığını 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 aktif dinleme gerektirmeyen veriler için kullanışlıdır. Örneğin, önceki örneklerdeki blog uygulaması, 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üncelle

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

updateChildren() öğesini çağırırken, anahtar için bir yol belirterek alt düzey alt değerleri güncelleyebilirsiniz. Veriler daha iyi ölçeklendirmek için birden fazla konumda depolanıyorsa, veri yayma özelliğini 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 gönderi oluşturmak ve onu aynı anda en son etkinlik akışına ve gönderiyi gönderen kullanıcının etkinlik akışına güncellemek için blog uygulaması şuna benzer 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 örnek /posts/$postid adresindeki tüm kullanıcılar için gönderileri içeren düğümde bir gönderi oluşturmak ve getKey() ile aynı anda anahtarı almak için push() işlevini kullanır. Anahtar daha sonra kullanıcının /user-posts/$userid/$postid adresindeki gönderilerinde ikinci bir giriş oluşturmak için kullanılabilir.

Bu yolları kullanarak, JSON ağacındaki birden çok konuma updateChildren() çağrısıyla eşzamanlı güncellemeler gerçekleştirebilirsiniz; örneğin bu örnekte, her iki konumda da yeni gönderi nasıl oluşturulur. Bu şekilde yapılan eşzamanlı güncellemeler atomiktir: ya tüm güncellemeler başarılı olur ya da tüm güncellemeler başarısız olur.

Tamamlama Geri Araması Ekleme

Verilerinizin ne zaman kaydedildiğini bilmek istiyorsanız bir tamamlama dinleyicisi ekleyebilirsiniz. Hem setValue() hem de updateChildren() yazma işlemi veritabanına başarıyla tamamlandığında çağrılan isteğe bağlı bir tamamlama dinleyicisini alı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 sil

Verileri silmenin en basit yolu, söz konusu verinin konumuna ilişkin bir referans üzerinden 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 ayırın

Geri aramalar, 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 olay için birden çok kez çağrılır ve onu tamamen kaldırmak için aynı sayıda ayırmanız gerekir.

Bir üst dinleyicide removeEventListener() işlevinin çağrılması, alt düğümlerinde kayıtlı dinleyicileri otomatik olarak kaldırmaz; Geri çağırmayı kaldırmak için herhangi bir alt dinleyicide de removeEventListener() çağrılmalıdır.

Verileri işlem olarak kaydet

Artan sayaçlar gibi eş zamanlı değişiklikler nedeniyle bozulabilecek verilerle çalışırken işlem işlemini kullanabilirsiniz. Bu işleme iki argüman verirsiniz: bir güncelleme işlevi ve isteğe bağlı bir tamamlama geri çağrısı. Güncelleme fonksiyonu verinin mevcut durumunu argüman 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 yeniden ç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 ve bir gönderinin kaç yıldız aldığını aşağıdaki gibi takip etmesine izin verebilirsiniz:

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şlemin kullanılması, birden fazla kullanıcının aynı gönderiye aynı anda yıldız vermesi veya istemcinin eski verileri olması durumunda yıldız sayımlarının hatalı olmasını önler. İşlem reddedilirse, sunucu mevcut değeri istemciye döndürür ve istemci, işlemi güncellenmiş değerle yeniden çalıştırır. Bu, işlem kabul edilene veya çok fazla deneme yapılana kadar tekrarlanır.

Atomik sunucu tarafı artışları

Yukarıdaki kullanım durumunda veritabanına iki değer yazıyoruz: gönderiye yıldız veren/yıldızını kaldıran kullanıcının kimliği ve artan yıldız sayısı. Kullanıcının gönderiye başrol oynadığını zaten biliyorsak, işlem yerine atomik artış işlemini 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 olması durumunda 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.

Bir kullanıcının daha önce yıldız eklediği bir gönderiye yıldız eklemesi gibi uygulamaya özel çakışmaları tespit etmek ve reddetmek istiyorsanız, o kullanım durumu için özel güvenlik kuralları yazmalısınız.

Verilerle çevrimdışı çalışma

Bir istemcinin ağ bağlantısını kaybederse uygulamanız düzgün çalışmaya devam edecektir.

Firebase veritabanına bağlı her istemci, üzerinde dinleyicilerin kullanıldığı veya sunucuyla senkronize tutulması için işaretlenen verilerin kendi dahili sürümünü tutar. Veri okunurken veya yazılırken ilk önce verinin bu yerel sürümü kullanılır. Firebase istemcisi daha sonra bu verileri uzak veritabanı sunucularıyla ve diğer istemcilerle "en iyi çabayı" esas alarak senkronize eder.

Sonuç olarak, veritabanına yapılan tüm yazma işlemleri, sunucuyla herhangi bir etkileşime girmeden hemen önce yerel olayları tetikler. Bu, uygulamanızın ağ gecikmesine veya bağlantısına bakılmaksızın yanıt vermeye devam edeceği anlamına gelir.

Bağlantı yeniden kurulduğunda uygulamanız uygun olay kümesini alır, böylece istemci herhangi bir özel kod yazmak zorunda kalmadan mevcut sunucu durumuyla senkronize edilir.

Çevrimiçi ve çevrimdışı yetenekler hakkında daha fazla bilgi edinin bölümünde çevrimdışı davranış hakkında daha fazla konuşacağız.

Sonraki adımlar