Jetpack Compose ile Firebase Google Sign-in

Ali Şamil Küçük
7 min readApr 13, 2022

Herkese selam! Bu yazımda sizlere Android’de Jetpack Compose kullanarak Firebase ile Google Sign-in işleminin nasıl gerçekleştirilebileceğini anlatacağım. Ayrıca yapacağım işlemleri Clean Architecture mimarisi ile gerçekleştireceğim. Clean Architecture yazıma bu linkten ulaşabilirsiniz! Hazırsanız başlayalım.

Google Sign-in işlemlerine geçmeden önce isterseniz gelin uygulamada bize yardımcı olacak kütüphanelerin kurulumuyla işe başlayalım.

Gerekli Kütüphanelerin Uygulamaya Eklenmesi

Öncelikle Hilt kütüphanesi ile işe başlayalım. Hilt bir dependency injection kütüphanesidir. Bu kütüphane ile depency injection işlemlerimizi otomatize hale getiriyoruz. Hilt’i uygulamamıza eklerken ilk önce proje seviyesindeki build.gradle dosyamıza bir path ekleyeceğiz daha sonra ise modül seviyesindeki build.gradle içerisine eklemeler yapacağız.

Hilt’i projemize ekledikten sonra sıra Coroutines kütüphanesine geliyor. Coroutines bizim asenkron olarak yapmamız gereken işlemlerimizde bize kolaylık sağlayacak. Bunun için modül seviyesindeki build.gradle dosyamıza gerekli eklemeleri yapıyoruz.

Son olarak ekleyeceğimiz kütüphane ise arayüz yazımında Jetpack Compose kullanacağımız için sayfa geçişlerini sağlamak adına Jetpack Compose Navigation kütüphanesi. Bunun için de yine modül seviyesindeki build.gradle dosyasına kütüphanemizi ekliyoruz.

Yapmamız gereken son bir iş daha kaldı. Hilt kütüphanesini uygulamamıza entegre etmek.

Hilt Entegrasyonu

İlk olarak Hilt kütüphanesi ile işlemlerimizi gerçekleştirirken Hilt’in bütün uygulamada çalışabilmesi için Application sınıfından kalıtım alan bir sınıf oluşturmalıyız ve bunun da başına @HiltAndroidApp notasyonunu eklemeliyiz.

Daha sonra bu sınıfımızı manifest dosyamızın içerisinde bulunan application taginde android:name değişkenine karşılık gelecek şekilde belirtmeliyiz.

Son olarak Jetpack Compose kullanarak arayüz işlemlerini gerçekleştirdiğimizden default olarak gelen MainActivity aktivitesinin başına da @AndroidEntryPoint notasyonunu ekliyoruz.

Hilt’i uygulamamıza eklediğimize göre artık başlayabiliriz. İlk olarak Firebase Authentication’ı uygulamamıza ekleyelim.

Firebase Authentication Kurulumunu Gerçekleştirmek

Firebase Authentication özelliğini projemizde kullanmaya başlayabilmek için öncelikle Firebase üzerinden bir proje oluşturmalı ve projemize Firebase tarafından sağlanan google-services.json dosyasını eklemeliyiz. Daha sonra ise Firebase Authentication kütüphanesinin gerekli dependecylerini build.gradle dosyalarına ekliyoruz.

İlk olarak proje seviyesindeki build.gradle dosyamızın içerisine google-services classpath ini ekliyoruz.

Daha sonra modül seviyesindeki build.gradle dosyamızın içerisine Firebase Authentication özelliğinin dependecylerini ekleyebiliriz. Ayrıca plugins kısmına google-services pluginini eklemeyi unutmayalım.

Firebase Authentication özelliğini uygulamamıza dahil ettiğimize göre artık hangi servis sağlayıcılarını kullanmak istediğimizi Firebase konsolundan seçebiliriz. Biz bu projede sadece Google Sign-in ile giriş işleminden yararlanacağımız için onu aktif hale getiriyoruz.

Firebase Authentication özelliğini uygulamamıza kazandırdığımıza göre hadi gelin Google Sign-in işlemini gerçekleştirmeye başlayalım.

Google Sign-in İle Giriş İşlemleri

Google Sign-in ile uygulamamıza giriş yeteneği kazandırmak için öncelikle Google Sign-in kütüphanesini uygulamamıza eklemeliyiz. Bunun için Google Sign-in kütüphanesini modül seviyesindeki build.gradle dosyamıza ekiyoruz.

Daha sonra ise sistemli ve temiz bir kod yazmak istediğimizden bir presentation paketi açıyoruz ve bunun içine de login paketi açarak işlemimize devam ediyoruz.

Paketlerimizi açtıktan sonra ise LoginScreen dosyamızı yaratıyoruz. LoginScreen dosyamızın içinde giriş işlemlerini yaparken kullandığımız görünümlerimiz yani Composeble’larımız ve giriş fonksiyonlarımız bulunacak. Bunun için ilk önce girişte kullanacağımız butonu Scaffold yapısı içerisinde tanımlıyoruz ve daha sonradan oluşturacağımız fonksiyonu çağırmak için içerisine bir onClick özelliği ekliyoruz.

Butonumuzu tanımladıktan sonra giriş fonksiyonumuzu tanımlamaya başlayabiliriz. Ama buna başlamadan önce LoginScreen Composable fonksiyonumuzun içerisinde bir context ve Google Sign-in isteğimizi yaptıktan sonra geriye bir access token değeri alabilmek için launcher değişkenlerimizi oluşturalım.

Bunları da oluşturduğumuza göre artık fonksiyonumuzu tanımlamaya başlayabiliriz. Öncelikle fonksiyonumuzun içerisinde bir context ve launcher değişkenine ihtiyacımız olduğu için parametre olarak onları istiyoruz. Daha sonra ise bir lateinit GoogleSignInClient objesi oluşturuyoruz ve işlemlerimize başlıyoruz.

Daha sonra ise bir GoogleSignInOptions objesi oluşturuyoruz ve bundan bir idToken isteğinde bulunuyoruz. İsteği yaparken GoogleSignInOptions’a isteğimizin sıradan bir istek olduğunu belirtiyoruz ve isteği yaparken kullandığımız OAuth 2.0 client idsini veriyoruz. Bu id’ye google-services.json dosyasının içersinden erişebilirsiniz.

Yaptığımız isteğin kullanıcı tarafından görülebilmesi için ise isteği bir intent yapısı içerisinde kullanıcıya sunuyoruz ve bu sayede kullanıcı Google hesabı ile giriş yapıyor ve biz de idTokenimize erişiyoruz. Bunu gerçekleştirirken daha evvel oluşturduğumuz launcher objesinden yararlanıyoruz.

Bütün bu işlemleri gerçekleştirdikten sonra aşağıdaki ekran ile karşılaşıyor isek işlemleri doğru bir şekilde gerçekleştirmişiz demektir.

Google Sign-in ile tokenimizi aldığımıza göre artık bu tokeni Firebase Authentication ile kaydetme aşamasına geçebiliriz.

Data Layer İşlemleri

Data katmanında daha önceden de bahsettiğimiz gibi network ve database işlemlerini gerçekleştiriyoruz. Ancak bu uygulamamızda Firebase işlemlerini gerçekleştirdiğimizden dolayı data katmanının altında firebase adında da bir pakete ihtiyacımız var.

Firebase paketinde öncelikle FirebaseAuthLoginSource interfaceimizi oluşturuyoruz. Daha sonra bu interfacei FirebaseAuthLoginSourceProvider sınıfına implemente edeceğiz. Bunu SOLID’in Open Closed prensipi nedeniyle yapıyoruz. Yaptığımız bu işlem sayesinde FirebaseAuthLoginSourceProvider sınıfımız genişletilebilir olacak.

FirebaseAuthLoginSource interfaceimizde loginWithCredential adında bir fonksiyon oluşturuyoruz. Bu fonksiyon sayesinde Google API’den aldığımız tokenler ile bir credential oluşturup bunları Firebase’e kaydedebileceğiz.

Fonksiyonumuzun suspend olması yani biz çağırana kadar beklemesi çok önemli. Çünkü bu işlemi diğer arkaplan işlemlerini bloklamaması için asenkron olarak gerçekleştireceğiz ve ihtiyaç duyduğumuzda bu fonksiyonu bir coroutine scope içerisinden çağıracağız.

Fonksiyonumuzu kullanırken bir credentiala ihtiyacımız olacağından bunu parametre olarak alıyoruz ve fonksiyondan nullable bir FirebaseUser nesnesinin geri dönüleceğini söylüyoruz.

Interfaceimizi tanımladığımıza göre FirebaseAuthLoginSourceProvider sınıfımıza geçebiliriz.

Bu sınıfımızı tanımlarken bir adım önce oluşturduğumuz FirebaseAuthLoginSource interfaceini sınıfımıza implemente ediyoruz ve loginWithCredential fonksiyonumuzu override ediyoruz.

Fonksiyonumuzu override ettikten sonra FirebaseAuth instanceımızı oluştururarak FirebaseAuth’un bir özelliği olan signInWithCredential metodunu çağırıyoruz ve fonksiyonumuzun parametresi olan authCredentialı bu metoda gönderiyoruz. Bundan sonra bu metoda Coroutine kütüphanesinin bir metodu olan await() ile bu işlem gerçekleşene kadar beklemesini söylüyoruz.

Daha sonra da instanceımızdan currentUser methodu ile şu anda uygulamada mevcut kullanıcı varsa o kullanıcıyı geriye döndürüyoruz yoksa da bir FirebaseAuthException hatası geriye dönüyoruz.

Bu işlemden sonra Firebase işlemlerini bitirmiş olduk. Şimdi yaptıklarımızı repository ile bir üst katmana taşıyalım.

Repositoryi verilere ulaşırken tek kaynak olması açısından kullandığımızı söylemiştik. Bu uygulamamızda repository tek bir fonksiyon içeriyor bundan dolayı repository kullanımının gerekli olmadığını düşünmüş olabilirsiniz. Ancak clean architecture kullanırken sadece şu an yaptığımız işlemler açısından olaylara bakamayız. İleride daha fazla işleme dolayısıyla daha fazla fonksiyona ihtiyaç duyabiliriz.

FirebaseRepositorymizi oluşturduktan sonra öncelikle constructorından firebaseSocialLoginSourceProvider sınıfımızın nesnesini alıyoruz ve işleme başlıyoruz.

Repositorimizin içinde de daha evvel olduğu gibi bir suspend loginWith Credential fonsiyonu oluşturuyoruz ve parametre olarak yine authCredentialı alıyoruz. Oluşturduğumuz bu fonksiyonu constructorından aldığımız nesne ile eşliyoruz. Nesnemizden çağırdığımız metodumuza da fonksiyondan aldığımız parametreyi veriyoruz.

Böylelikle repositoryde yapmamız gereken işlemlerin de sonuna geldik. Sırada iş akışlarını sağladığımız domain katmanı var.

Domain Layer İşlemleri

Domain katmanı daha evvel söylediğimiz gibi data katmanından gelen verileri belirli koşullara göre sınıflandırıp bunları viewmodele bir akış halinde sunmamızı sağlayan katmandır. Bu akışı sağlarken sealed classları kullanabiliriz. İlk olarak sealed classımızı tanımlayalım ve işlemimize öyle devam edelim.

Sealed classımızı common paketinin altında tanımlayacağız.

Öncelikle işleme AuthenticationState enum classımzı tanımlayarak başlayalım. Burada authentication işlemlerini gerçekleştirirken kullanacağımız durumları tanımlıyoruz.

Daha sonra FirebaseAuthenticationResult sealed classımızı oluşturmaya başlayabiliriz. Bu sınıfı tanımlarken içerisine bir adet state değeri, işlemin hata ile karşılaşma durumunda kullanılmak üzere ise bir exception değeri ekliyoruz ve durumlara göre de sealed classımızın içerisinde Success,Failure ve InProgress classlarını tanımlıyoruz.

Sealed classımızı tanımladığımıza göre işlemlerimizi durumlara göre sınıflandırmamızı sağlayan use caseimizi yazmaya başlayabiliriz.

Öncelikle FirebaseAuthUseCase adında bir sınıf oluşturuyoruz ve constructordan firebaseRepository nesnesini alıyoruz. Daha sonra ise invoke fonksiyonumuzu oluşturuyoruz ve bu fonksiyonu suspend olarak tanımlayıp parametre olarak bir credential istiyoruz.

Invoke fonksiyon kullanmamızın sebebi ise her aksiyon için ayrı use case yazığımızdan dolayı use caseimiz tek bir fonksiyon içeriyor. Sınıfımızdan fonksiyonumuza ulaşmaya çalışırken daha uzun kod yazmamak için sınıfımızın adını yazdıktan sonra nokta bırakıp invoke dediğimizde fonksiyonumuza direkt olarak ulaşmış oluyoruz.

Fonksiyonumuzu yazdıktan sonra ilk iş fonksiyonu bir flowa eşitliyoruz. Flow bize asenkron işlemler gerçekleştirirken canlı olarak veri trafiği sağlama yeteneği kazandırıyor ve bu kullandığımız verileri emit metodu sayesinde flow içerisinde yayabiliyoruz.

Flow içerisinde yaptığımız işlemin sonucunda herhangi bir hata ile karşılaşırsak bu hatayı kullanıcıya göstermek adına try catch bloğumuzu yazıyoruz.

Yazdığımız try bloğunun içerisine repository nesnemizden loginWithCredential fonksiyonunu çağırıp sonuç boş değil ise sealed classımızın Success değerini emit ediyoruz. Hata olmadığı halde boş döner ise ve catch bloğuna girer ise de Failure değerini emit ediyoruz. Ayrıca işleme başlamadan önce flowumuza InProgress değerini emit ediyoruz.

Böylelikle domain katmanında yapmamız gerek işlemler de sona eriyor. Bu aşamadan sonra Firebaseden dönen değişkenler ile oluşturduğumuz akışı viewmodel içerisinde kullanacağız.

Presentation Layer İşlemleri

Aslında presentation katmanında kod yazmaya başlamıştık görünümleri ve giriş işlemlerindeki token alma süreçlerini tamamlamıştık ancak Viewmodelimizi oluşturmamıştık. Şimdi Viewmodelimizi oluşturalım.

Öncelikle LoginViewModel adında bir sınıf oluşturuyoruz ve bu sınıfta ViewModelden kalıtım alıyoruz. Daha sonra hiltin bu sınıfı bir viewmodel olarak tanıyabilmesi için sınıfımızın başına @HiltViewModel notasyonu ekliyoruz. LoginViewModelimizi kurduktan sonra constructordan firebaseAuthUseCase nesnemizi alıyoruz ve işleme başlıyoruz.

Daha sonra Compose ile oluşturduğumuz görünümümüz ile canlı veri alışverişini sağlamak için authStateimizi tanımlıyoruz. Daha sonra ise loginWithCredential fonksiyonumuzu tanımlayıp parametre olarak bir AuthCredential nesnesi istiyoruz.

Fonksiyonumuzun içerisinde daha evvel tanımladığımız suspend fonksiyonları kullanabilmek için viewModelScope kod bloğumuzu oluşturuyoruz ve içerisinde firebaseAuthUseCase nesnemizin yardımı ile invoke fonksiyonumuzu çağırıyoruz. Fonksiyondan dönen verilere collect metodu vasıtasıyla erişiyoruz ve eriştiğimiz verileri ise useState olarak tanımladığımız mutableState imize eşitliyoruz.

Viewmodelimizi de tanımladığımıza göre LoginScreen içerinde yazmış olduğumuz Google Sign-in metodundan dönen tokenleri bir credentiala çevirip viewmodele gönderme işlemine başlayalım.

LoginScreen Son Düzenlemeler

İlk olarak viewmodelimizi Composable fonksiyonumuzun içerisinde tanımlayarak başlayalım.

Viewmodelimizi tanımladıktan sonra ilk iş Google Sign-in işlemini gerçekleştirirken kullandığımız ve tokeni aldığımız launcher bloğunun içerisinde tokenden bir credential oluşturalım ve viewmodele gönderelim.

Böylelikle GoogleSignIn işlemini tamamlamış olduk. Son olarak bu işlemin sonucunda sayfa değiştirmek, oluşabilecek hatayı yazdırmak ve işlem devam ederken ekranda bir progress bar göstermek için CheckLoginState adında bir Composable fonksiyon yazalım ve bunu LoginScreenden çağıralım.

İşlemin başarılı olması durumunda MainScreen sayfasına geçiş yapılmasını istediğimizden dolayı bu sayfamızı da şimdi tanımlıyoruz.

Son olarak MainActivity içerisinde ekranlar arasında geçiş yapabilmek için bir Composable Navigation fonksiyonu oluşturuyoruz ve bunu da MainActivity içerisinden çağırıyoruz.

Eveet son aşamayı da böylece tamamlamış olduk. Umarım faydalı olabilmişimdir.

Projenin Github Linki

--

--