Firebase Firestore Database -2

By | 17 Temmuz 2021

Firebase Authentication dersinde kullanıcı ile ilgili işlemleri nasıl yapacağımızı, Firebase Firestore Database dersinde de verileri DocumentReference ve CollectionReference kullanarak nasıl yazdırıp çekebileceğimizi öğrenmiştik. Firestore konusunun 2. dersi olan bu derste verileri filtreleme, güncelleme ve silme gibi işlemleri nasıl yapabileceğimizle beraber veriler üzerinde işlem yapabilmek için model oluşturup bundan faydalanmayı öğreneceğiz. Aynı zamanda çoklu verileri RecyclerView içerisinde nasıl listeleyebiliriz onu da görmüş olacağız.

Not: Manifest dosyasında internet bağlantısı için gereken izni vermeyi unutmayın.

Gerekli firebase bağlantı işlemlerini yaptığını varsayaraktan örnek uygulamamıza geçelim hemen. Aşağıda uygulamamızın çıktıları yer almakta. Kodlar burada fazla alan kaplamasın diye gerekli alanları tek paylaştım. Tüm kodları ise BURADAN bulabilirsiniz.

Örnek uygulamanın çıktılarında bazı kullanıcı işlemlerinin yapıldığını görebilirsiniz. Bunların nasıl yapıldığını bu ders serimizin Authentication dersinde görmüştük zaten. Burada tekrara kaçmayalım.

Home bölümünde;

  • Kullanıcı kayıt ve güncelleme
  • Silme
  • İsme göre filtreleme ve listeleme
  • Yaşa göre filtreleme ve listeleme

Gibi işlemler yapılmakta.

Kodlardan önce Android Proje Dosya yapısı aşağıdaki gibi olacağını gösterelim.

HomeActivity.kt dosyası;

package com.mrcaracal.firebasefirestoredatabase2.view

import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.CollectionReference
import com.google.firebase.firestore.DocumentReference
import com.google.firebase.firestore.FirebaseFirestore
import com.mrcaracal.firebasefirestoredatabase2.R
import com.mrcaracal.firebasefirestoredatabase2.adapter.UserAdapter
import com.mrcaracal.firebasefirestoredatabase2.databinding.ActivityHomeBinding
import com.mrcaracal.firebasefirestoredatabase2.model.UserData
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap

private const val TAG = "HomeActivity"

class HomeActivity : AppCompatActivity() {

    private lateinit var binding: ActivityHomeBinding
    private lateinit var auth: FirebaseAuth
    private lateinit var documentReference: DocumentReference
    private lateinit var collectionReference: CollectionReference
    private lateinit var firestore: FirebaseFirestore
    private lateinit var datas: HashMap<String, String>
    val userDataArrayList: ArrayList<UserData> = ArrayList()
    private lateinit var userAdapter: UserAdapter
    private val COLLECTION_NAME = "EmployeeInformation"

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)
        binding = ActivityHomeBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)

        auth = FirebaseAuth.getInstance()
        firestore = FirebaseFirestore.getInstance()
        documentReference = firestore.collection(COLLECTION_NAME).document()
        collectionReference = firestore.collection(COLLECTION_NAME)
        userAdapter = UserAdapter(userDataArrayList)
        datas = HashMap()

        binding.recyclerview.layoutManager = LinearLayoutManager(this)
        binding.recyclerview.adapter = userAdapter

        binding.btnSaveUpdateByName.setOnClickListener {
            userDataArrayList.clear()
            val name = binding.edtSaveUpdateName.text.toString().lowercase(Locale.getDefault())
            val surname = binding.edtSaveUpdateSurname.text.toString().lowercase(Locale.getDefault())
            val age = binding.edtSaveUpdateAge.text.toString()

            if (name.equals("") || surname.equals("") || age.equals("")) {
                Log.i(TAG, "onCreate: Empty")
            } else {
                datas.put("name", name)
                datas.put("surname", surname)
                datas.put("age", age)

                documentReference = firestore.collection(COLLECTION_NAME).document(name + surname)
                saveAndUpdate()
            }
            binding.edtSaveUpdateName.setText("")
            binding.edtSaveUpdateSurname.setText("")
            binding.edtSaveUpdateAge.setText("")
        }

        binding.btnDeleteByName.setOnClickListener {
            userDataArrayList.clear()
            val nameSurname = binding.edtDeleteName.text.toString().lowercase(Locale.getDefault())
            if (nameSurname.equals("")) {
                Log.i(TAG, "onCreate: Empty")
            } else {
                delete(nameSurname)
            }
            binding.edtDeleteName.setText("")
        }

        binding.btnFilterName.setOnClickListener {
            val namefilt = binding.edtFilterName.text.toString().lowercase(Locale.getDefault())
            if (namefilt.equals("")) {
                Log.i(TAG, "onCreate: Empty")
            } else{
                filterName(namefilt)
            }
        }

        binding.btnFilterAge.setOnClickListener {
            val ageFilt = binding.edtFilterAge.text.toString()
            if (ageFilt.equals("")) {
                Log.i(TAG, "onCreate: Empty")
            } else{
                filterAge(ageFilt)
            }
        }
    }

    private fun filterAge(ageFilt: String) {
        collectionReference.whereEqualTo("age", ageFilt).addSnapshotListener { value, error ->
            userDataArrayList.clear()
            if (error != null){
                Log.i(TAG, "filterAge: " + error)
            }else{
                if(value != null){
                    if (!value.isEmpty){
                        val documents = value.documents
                        for (doc in documents){
                            val str_name = doc.get("name") as String
                            val str_surname = doc.get("surname") as String
                            val str_age = doc.get("age") as String

                            Log.i(TAG, "filterName: STR_NAME: "+str_name+ ": "+str_age)
                            val udata = UserData(str_name, str_surname, str_age)
                            userDataArrayList.add(udata)
                        }
                        userAdapter.notifyDataSetChanged()
                    }
                }
            }
        }
    }

    private fun filterName(namefilt: String) {
        collectionReference.whereEqualTo("name", namefilt).addSnapshotListener { value, error ->
            userDataArrayList.clear()
            if(error != null){
                Log.i(TAG, "filterName: " + error)
            }else{
                if (value != null){
                    if (!value.isEmpty){
                        val documents = value.documents
                        for (doc in documents){
                            val str_name = doc.get("name") as String
                            val str_surname = doc.get("surname") as String
                            val str_age = doc.get("age") as String

                            Log.i(TAG, "filterName: STR_NAME: "+str_name)
                            val udata = UserData(str_name, str_surname, str_age)
                            userDataArrayList.add(udata)
                        }
                        userAdapter.notifyDataSetChanged()
                    }
                }
            }
        }
    }

    private fun delete(nameSurname: String) {
        firestore.collection(COLLECTION_NAME).document(nameSurname).delete()
            .addOnSuccessListener {
                Log.i(TAG, "delete: Deleted")
            }
            .addOnFailureListener {
                Log.i(TAG, "delete: Failure")
            }
    }

    private fun saveAndUpdate() {
        documentReference.set(datas)
            .addOnSuccessListener {
                Log.i(TAG, "saveAndUpdate: Saved")
            }
            .addOnFailureListener {
                Log.i(TAG, "saveAndUpdate: Failure")
            }
    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        val menuInflater = menuInflater
        menuInflater.inflate(R.menu.options_menu, menu)
        return super.onCreateOptionsMenu(menu)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        if (item.itemId == R.id.logout) {
            auth.signOut()
            val intent = Intent(applicationContext, LoginActivity::class.java)
            startActivity(intent)
            finish()
        }
        return super.onOptionsItemSelected(item)
    }
}

Şimdi yavaştan neler yapmışız inceleyelim.

Xml içerisinde yerleştirdiğimiz componentlere erişim için ViewBinding yapısından faydalandık. Bunun için ayrı bir ders paylaşacağım. Paylaştığım zaman buraya tıklayarak erişmiş olacaksınız.

private lateinit var binding: ActivityHomeBinding

Firebase için gerekli olan sınıflarımız bunlar;

private lateinit var auth: FirebaseAuth
private lateinit var documentReference: DocumentReference
private lateinit var collectionReference: CollectionReference
private lateinit var firestore: FirebaseFirestore
  • FirebaseAuth ile kullanıcı işlemleri için
  • DocumentReference ile belli bir dosya üzerinde işlem yapmak için
  • CollectionReference ile koleksiyon içerisinde birçok dosya ile işlem yapmak için
  • Firestore ile de verilerimiz tutmak için kullanacağız.

Verileri nasıl kaydedip listeleyeceğimizi de aşağıdaki sınıflar sayesinde halledeceğiz.

private lateinit var datas: HashMap<String, String>
val userDataArrayList: ArrayList<UserData> = ArrayList()
private lateinit var userAdapter: UserAdapter
  • Veri kaydetmede HashMap
  • Verileri listelemede ArrayList ve kendi oluşturduğumuz UserAdapter’dan faydalanacağız.

onCreate içerisinde oluşturduğumuz aşağıdaki yapı ile artık xml içerisinde yerleştirdiğimiz componentlere binding.ornekEditText şeklinde erişebileceğiz.

binding = ActivityHomeBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)

En üstte lateinit var(daha sonradan tanımla) ile oluşturduklarımızı burada initialize ediyoruz.

auth = FirebaseAuth.getInstance()
firestore = FirebaseFirestore.getInstance()
documentReference = firestore.collection(COLLECTION_NAME).document()
collectionReference = firestore.collection(COLLECTION_NAME)
userAdapter = UserAdapter(userDataArrayList)
datas = HashMap()

RecyclerView içerisinde listelenecek olan verilerin ne şekilde ve nasıl listeleneceğini belirtiyoruz.

binding.recyclerview.layoutManager = LinearLayoutManager(this)
binding.recyclerview.adapter = userAdapter

Veri Kaydetme ve Güncelleme

binding.btnSaveUpdateByName.setOnClickListener {
            userDataArrayList.clear()
            val name = binding.edtSaveUpdateName.text.toString().lowercase(Locale.getDefault())
            val surname = binding.edtSaveUpdateSurname.text.toString().lowercase(Locale.getDefault())
            val age = binding.edtSaveUpdateAge.text.toString()

            if (name.equals("") || surname.equals("") || age.equals("")) {
                Log.i(TAG, "onCreate: Empty")
            } else {
                datas.put("name", name)
                datas.put("surname", surname)
                datas.put("age", age)

                documentReference = firestore.collection(COLLECTION_NAME).document(name + surname)
                saveAndUpdate()
            }
            binding.edtSaveUpdateName.setText("")
            binding.edtSaveUpdateSurname.setText("")
            binding.edtSaveUpdateAge.setText("")
        }

Kaydet ve güncelle butonuna basıldığında öncelikle recyclerview içerisindeki datayı aşağıdaki gibi temizliyoruz.

userDataArrayList.clear()

Bunun sebebini Filtreleme kısmında anlatacağım. Şimdilik “addSnapShotListener kullandığımızdan dolayı” diyerek geçiştireyim.

Daha sonradan kullanıcıdan istediğimiz verileri alıyor ve alırken de boş olup olmadığı gibi kontrolleri yapıyoruz. Aldığımız verileri HashMap içerisinde key-value ile tutuyoruz.

documentReference = firestore.collection(COLLECTION_NAME).document(name + surname)
saveAndUpdate()

Oluşturduğumuz documentReference’i burada kullandık. Dosya isminin nasıl olacağı ve hangi koleksiyon içerisinde tutulacağını…
Ardından saveAndUpdate() fonksiyonunu çağırarak kayıt işlemini yapıyoruz.

private fun saveAndUpdate() {
    documentReference.set(datas)
        .addOnSuccessListener {
            Log.i(TAG, "saveAndUpdate: Saved")
        }
        .addOnFailureListener {
            Log.i(TAG, "saveAndUpdate: Failure")
        }
}

set(datas) ile HashMap içerisine attığımız verileri documentReference ile belirttiğimiz yere kayıt işlemini yaptırıyoruz. İşlemin başarılı olup olmadığını da addOnSuccessListener ve addOnFailureListener ile kontrol ediyoruz.

Veri Silme

binding.btnDeleteByName.setOnClickListener {
    userDataArrayList.clear()
    val nameSurname = binding.edtDeleteName.text.toString().lowercase(Locale.getDefault())
    if (nameSurname.equals("")) {
        Log.i(TAG, "onCreate: Empty")
    } else {
        delete(nameSurname)
    }
    binding.edtDeleteName.setText("")
}

Delete butonuna basıldığında öncelikle recyclerview içerisinde listelediğimiz verilerin tekrara kaçmaması için

userDataArrayList.clear()

ile ArrayList’i temizledik. Daha sonradan kullanıcıdan istediğimizi alıyoruz. Kullanıcı AdSoyad birleşik girerek istenilen dosyayı silebilecek. Çünkü bir önceki adımda kayıt işlemlerini yaparken dosya isimlerini AdSoyad şeklinde oluşturan bir kod satırı eklemiştik. Aşağıda görülmekte.

documentReference = firestore.collection(COLLECTION_NAME).document(name + surname)

Herhangi bir sorun yoksa delete(nameSurname) fonksiyonuna kullanıcıdan aldığımız AdSoyad bilgisini parametre olarak gönderip çağırıyoruz.

private fun delete(nameSurname: String) {
    firestore.collection(COLLECTION_NAME).document(nameSurname).delete()
        .addOnSuccessListener {
            Log.i(TAG, "delete: Deleted")
        }
        .addOnFailureListener {
            Log.i(TAG, "delete: Failure")
        }
}

Çok basit bir şekilde delete() kullanarak istediğimiz dosyayı siliyoruz.

İsme Göre Filtreleme

binding.btnFilterName.setOnClickListener {
    val namefilt = binding.edtFilterName.text.toString().lowercase(Locale.getDefault())
    if (namefilt.equals("")) {
        Log.i(TAG, "onCreate: Empty")
    } else{
        filterName(namefilt)
    }
}

Name Filter butonuna tıklandığında gerekli kontrolden sonra herhangi bir sıkıntı yok ise filterName(namefilt) fonksiyonuna kullanıcıdan aldığımız veriyi gönderiyoruz.

private fun filterName(namefilt: String) {
    collectionReference.whereEqualTo("name", namefilt).addSnapshotListener { value, error ->
        userDataArrayList.clear()
        if(error != null){
            Log.i(TAG, "filterName: " + error)
        }else{
            if (value != null){
                if (!value.isEmpty){
                    val documents = value.documents
                    for (doc in documents){
                        val str_name = doc.get("name") as String
                        val str_surname = doc.get("surname") as String
                        val str_age = doc.get("age") as String

                        Log.i(TAG, "filterName: STR_NAME: "+str_name)

                        val udata = UserData(str_name, str_surname, str_age)
                        userDataArrayList.add(udata)
                    }
                    userAdapter.notifyDataSetChanged()
                }
            }
        }
    }
}

Şu ana kadar belli bir dosya üzerinde işlemler yaptığımızdan documentReference kullandık. Ama burada birçok dosya içerisinde arama yapacağımız için yani bir koleksiyon içerisindeki bütün dosyaları inceleyeceğimiz için collectionReference kullandık.
whereEqualTo(name, namefilt) ile o koleksiyon içerisinde name değişkeninde değeri namefilt’e eşit olan her şeyi al dedik.
addSnapshotListener ile de herhangi bir veri değiştiği zaman anlık olarak gerekli olan tüm yerlere yansıtılmasını sağladık. Daha önceden size neden aşağıdaki işlemi yaptığımızı açıklayacağım demiştim.

userDataArrayList.clear()

addSnapshotListener ile tüm verileri sürekli takip ediyoruz ve herhangi bir değişiklik olduğunda RecyclerView içerisinde listelediğimiz verilere ek olarak yenileri eklenecek ve böylelikle sadece bir veri değişse bile geri kalanlar kendisini tekrara düşürecekti. Bunu önlemek için öncelikle listeyi temizledik ve ardından diğer işlemleri yaptırdık. Konumuza tekrardan geri dönecek olursak…

addSnapShotListener kullandığımız lambda fonksiyonunda bize value ve error verilmekte. Bunun sayesinde verilere erişebilir ve varsa hataları gözlemleyebileceğiz.

Eğer herhangi bir sıkıntı yok ise;

val documents = value.documents
for (doc in documents){
    val str_name = doc.get("name") as String
    val str_surname = doc.get("surname") as String
    val str_age = doc.get("age") as String

    Log.i(TAG, "filterName: STR_NAME: "+str_name+ ": "+str_age)
    val udata = UserData(str_name, str_surname, str_age)
    userDataArrayList.add(udata)
}
userAdapter.notifyDataSetChanged()

value.documents ile document değişkenine verileri aktardık. Daha sonrasında for each döngüsü ile her birinin içine tek tek girip değerleri istediğimiz değişkenlere aktardık.

Aşağıdaki gibi içleri dolu olan değişkenleri bizim oluşturduğumuz modele aktardık ve bunu her seferinde oluşturduğumuz ArrayListe aktardık. Kaç tane veri gelecekse ArrayList’in içi o kadar dolacak.

val udata = UserData(str_name, str_surname, str_age)
userDataArrayList.add(udata)

userDataArrayList’e ekelenen bu dataları daha sonradan UserAdapter sınıfına parametre olarak göndereceğiz. Bu kısma aşağıda tekrardan değineceğiz.

Tüm bu işlemlerden sonra listelediğimiz verilerin kendini senkronize etmesi için

userAdapter.notifyDataSetChanged()

kullandık.

Kendi oluşturup kullandığımız UserData sınıfı;

class UserData(val name: String, val surname: String, val age: String) {
}

Veri listeleme işleminde oluşturup faydalandığımız UserAdapter sınıfını inceleyelim şimdi. Bununla ilgili bir dersimiz vardı ama içerik Java programlama diline göre hazırlanmıştı. O derse buradan ulaşabilirsiniz. Tekrara düşmemek adına fazla detaya inmeyeceğim. üstte verdiğim bağlantıdan recyclerview’ın nasıl kullanıldığını öğrenebilirsiniz. Tek fark programlama dilinin farklı olması. Onu da çözersiniz 🙂

Kodlara geçelim hemen.

UserAdapter Sınıfı;

package com.mrcaracal.firebasefirestoredatabase2.adapter

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.mrcaracal.firebasefirestoredatabase2.databinding.RowDesignBinding
import com.mrcaracal.firebasefirestoredatabase2.model.UserData

class UserAdapter(private val userList: ArrayList<UserData>) : RecyclerView.Adapter<UserAdapter.UserHolder>() {

    class UserHolder(val binding: RowDesignBinding) : RecyclerView.ViewHolder(binding.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserHolder {
        val binding = RowDesignBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return UserHolder(binding)
    }

    override fun onBindViewHolder(holder: UserHolder, position: Int) {
        holder.binding.rowName.text = userList.get(position).name
        holder.binding.rowSurname.text = userList.get(position).surname
        holder.binding.rowAge.text = userList.get(position).age
    }

    override fun getItemCount(): Int {
        return userList.size
    }
}

Kodlara bakıldığı zaman sınıfımız bir arraylist paramtresi almakta. Bu parametreyi HomeActivity.kt içerisinde gönderiyoruz demiştik.

userAdapter = UserAdapter(userDataArrayList)
binding.recyclerview.layoutManager = LinearLayoutManager(this)
binding.recyclerview.adapter = userAdapter

Görüldüğü gibi userDataArrayList isminde bir parametreyi buradan gönderiyoruz. Bu aslında bizim firebase’den çektiğimiz her bir veriyi içerisinde tutan ArrayListimiz. Tekrardan Adapter sınıfımıza dönecek olursak sınıfımız içerisinde 3 fonksiyon ve 1 sınıfımız bulunmakta.

getItemCount() ile parametre olarak gelen ArrayList içerisinde kaç data var ise o kadar tekrar etmesini sağlıyoruz.

override fun getItemCount(): Int {
        return userList.size
    }

onBindViewHolder() ile listeleyeceğimiz her bir datanın nerede nereye yazdırılacağı gibi işlemleri yapıyoruz. Kısacası her bir satırın içeriği belirlenir.

override fun onBindViewHolder(holder: UserHolder, position: Int) {
        holder.binding.rowName.text = userList.get(position).name
        holder.binding.rowSurname.text = userList.get(position).surname
        holder.binding.rowAge.text = userList.get(position).age
    }

onCreatViewHolder() ile listeleme işlemi için her bir satır için temsil edilecek olan ara yüzü belirler.

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserHolder {
        val binding = RowDesignBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return UserHolder(binding)
    }

Elemanlarımıza eriştiğimiz sınıfımız…

class UserHolder(val binding: RowDesignBinding) : RecyclerView.ViewHolder(binding.root)

Her bir satırın nasıl olacağını, tasarım doyasından da bahsetmek gerekir.

res>layout>row_design.xml yolunda oluşturduğumuz dosyanın içeriği aşağıda görsel ve xml’i yer almakta.

row_design.xml dosyası;

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:padding="3dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <androidx.appcompat.widget.LinearLayoutCompat
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/row_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:hint="Name"/>

        <TextView
            android:id="@+id/row_surname"
            android:layout_weight="1"
            android:layout_marginLeft="15dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:hint="Surname"/>

        <TextView
            android:id="@+id/row_age"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:hint="Age"/>

    </androidx.appcompat.widget.LinearLayoutCompat>

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_marginTop="10dp"
        android:background="@color/black"/>

</LinearLayout>

Recyclerview konusu başlı başına bir ders olduğundan detaya inmek istemedim. Buradan daha detaylı dersi inceleyebilirsiniz.

Yaşa Göre Filtreleme

Bir önceki başlığımızda İsme göre filtreleme yaparken yaptığımız her şeyi tekrar ettik. Tek farkı kullanıcıdan aldığımız değerin farklı olması ve age değişkenine göre bu değeri aratmamızdır.

Farklı sorgu ve filtreleme işlemleri için buradaki bağlantıya tıklayarak firebase’in kendi dökümanından çok daha fazla detayı öğrenebilirsiniz.

Firebase Firestore Database dersimizin 2. dersinin sonuna geldik. Bir sonraki dersimizde görüşmek üzere.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir