Merhaba, bu yazıda Kotlin için MVVM, Retrofit, Dagger ve LiveData kavramlarını ele alarak, bu konuları örnek bir projede kullanarak anlatacağım. Projemiz bir firebase login işlemi olacak, tabii login işlemini firebase SDK kullanmadan, RestAPI aracılığı ile yapacağız. Öncelikle başlıkta belirttiğimiz kavramları açıklayayım.

MVVM (Model-View-ViewModel): Kotlin’de MVVM, uygulama bileşenlerini Model, View ve ViewModel olarak üç ana katmana ayıran bir yazılım mimari desenidir. Kodun düzenli, okunabilir ve kolay yönetilebilir olmasını sağlar.

Retrofit: Android ve Java için tip güvenli bir HTTP istemcisi sunan bir REST istemci kütüphanesidir. API’lerle kolay ve hızlı bir şekilde iletişim kurmayı sağlar. JSON ve diğer veri formatlarını otomatik olarak dönüştürür ve veri modelleri ile senkronize hale getirir.

Dagger: Java ve Android için hızlı ve etkili bir bağımlılık enjeksiyon (dependency injection) çerçevesidir. Bağımlılıkları yönetmeyi ve sağlamayı kolaylaştırır, böylece uygulamanın bileşenleri arasında düşük bağımlılık ve esneklik sağlar.

LiveData: Android Jetpack bileşenlerinden biridir ve gözlemlebilir (observable) veri tutucu sınıflar sağlar. LiveData, veri değişikliklerini otomatik olarak izler ve bu değişiklikleri UI bileşenleri ile senkronize eder. Ayrıca yaşam döngüsüne duyarlıdır, yani uygulamanın yaşam döngüsü boyunca bellek sızıntısı ve hatalarını önlemeye yardımcı olur.

Coroutines: Kotlin dilinde eşzamanlı ve asenkron programlamayı kolaylaştıran hafif ve ölçeklenebilir bir yapıdır. Bloklamayan süspansiyon (suspension) mekanizması sayesinde, daha az kaynak kullanarak büyük miktarda eşzamanlı işlemi gerçekleştirebilir. Bu yapı, kodun daha anlaşılır ve düzgün bir şekilde yazılmasına olanak tanırken, performans ve kaynak kullanımında da iyileştirmeler sunar.

Bu teknolojilerin bir arada kullanılması, Kotlin’de MVVM mimarisinin güçlendirilmesine ve daha etkili, düzenli ve test edilebilir uygulamaların geliştirilmesine olanak tanır.

Kotlin-MVVM

 

Şimdi, projeye başlamadan Firebase Auth REST API dokümantasyonunu buraya tıklayarak inceleyebilirsiniz. Projemizi oluşturduktan sonra MVVM yapısını belirleyelim.  data ve ui olmak üzere iki tane package oluşturalım. Ardından UI klasörü içerisinde activity ve viewmodel klasörlerimizi oluşturalım. Data klasörü içerisinde de di, helper, model, remote, repository klasörlerimizi oluşturalım. MVVM yapısı bu kadar 🙂 Bu işlemleri tamamladıktan sonra, ilgili bağımlılıklarımızı projeye ekleyelim. Öncelikle Project Level build.gradle dosyamızı açalım ve plugins’in üstüne aşağıdaki kodu ekleyelim.

 

buildscript {
    dependencies {
        // Dagger Hilt
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.42'
    }
}


Ardından, App Level build.gradle dosyamızı açalım ve aşağıdaki bağımlıkları ekleyelim.

    // Dagger Hilt
    implementation 'com.google.dagger:hilt-android:2.42'
    kapt 'com.google.dagger:hilt-compiler:2.42'

    // Retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'


    // ViewModel and LiveData
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
    implementation 'androidx.activity:activity-ktx:1.3.1'


activity_main.xml tasarımımızı aşağıdaki şekilde ayarlayalım.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/black"
    tools:context=".ui.activity.MainActivity">


    <LinearLayout
        android:id="@+id/linearLayout2"
        android:layout_width="match_parent"
        android:layout_height="180dp"
        android:layout_marginBottom="100dp"
        android:gravity="center"
        android:orientation="vertical"
        app:layout_constraintBottom_toTopOf="@+id/btnLogin"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintStart_toStartOf="parent">

        <EditText
            android:id="@+id/edEmail"
            android:layout_width="300dp"
            android:layout_height="60dp"
            android:layout_marginBottom="20dp"
            android:background="@drawable/edtext_bg_selector"
            android:drawableStart="@drawable/baseline_email_24"
            android:drawablePadding="10dp"
            android:drawableTint="@android:color/darker_gray"
            android:hint="E-Mail"
            android:inputType="textWebEmailAddress"
            android:padding="15dp" />

        <EditText
            android:id="@+id/edPass"
            android:layout_width="300dp"
            android:layout_height="60dp"
            android:background="@drawable/edtext_bg_selector"
            android:drawableStart="@android:drawable/ic_lock_lock"
            android:drawablePadding="10dp"
            android:drawableTint="@android:color/darker_gray"
            android:hint="Password"
            android:inputType="textPassword"
            android:padding="10dp" />


    </LinearLayout>

    <Button
        android:id="@+id/btnLogin"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="68dp"
        android:text="Login"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>


Şimdi artık sınıflarımızı ve servislerimizi yazmaya başlayabiliriz. Öncelikle giriş yapacağımız modeli oluşturmamız gerekir. Google Auth REST API dokümantasyonunu incelediğimizde giriş yapmak için “E-mail”, “Password” ve “returnSecureToken” parametrelerinin olduğunu görüyoruz. Oluşturduğumuz UserModel sınıfını aşağıdaki şekilde ayarlayabiliriz.

import com.google.gson.annotations.SerializedName

data class UserModel(
    val email : String,
    val password : String,
    @SerializedName("returnSecureToken") val returnSecureToken: Boolean = true
)


Burada returnSecureToken daima true dönmelidir.

Şimdi artık modül ve servislerimizi yazabiliriz. Öncelikle, oluşturduğumuz remote klasörü içerisinde NetworkModule sınıfı oluşturalım.  

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Singleton


@Module
@InstallIn(SingletonComponent::class)
class NetworkModule {
    @Provides
    @Singleton

    fun provideRetrofit() : Retrofit =
        Retrofit.Builder()
            .baseUrl("https://identitytoolkit.googleapis.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()

    @Provides
    @Singleton
    fun provideApiService(retrofit:Retrofit) : Services = retrofit.create(Services::class.java)
}


Burada, Dagger Hilt adlı bir bağımlılık enjeksiyon (dependency injection) kütüphanesini kullandık. Şimdi bu anotasyonların her birini açıklayalım.

@Module: Bu etiket, sınıfın bağımlılıkları sağlayan bir modül olduğunu belirtir. Bu modül, belirli bağımlılıkların nasıl oluşturulacağını ve sağlanacağını tanımlar.

@InstallIn: Bu etiket, modülün hangi Dagger Hilt bileşenine yüklenmesi gerektiğini belirtir. Bu örnekte, SingletonComponent::class ile modülün, tüm uygulama ömrü boyunca yaşayan ve tekil (singleton) bağımlılıklar sağlayan bir bileşene yüklenmesi gerektiği belirtilmiştir.

@Provides: Bu etiket, fonksiyonun bir bağımlılığı sağlamak için kullanılacağını belirtir. Dagger Hilt, bu işaretli fonksiyonları kullanarak bağımlılıkları oluşturur ve bunları enjekte etmeye ihtiyaç duyulan yerlere sağlar.

@Singleton: Bu etiket, bağımlılığın tekil (singleton) olarak sağlanması gerektiğini belirtir. Bu, bağımlılığın sadece bir kez oluşturulacağı ve tüm uygulama ömrü boyunca aynı örneğin kullanılacağı anlamına gelir.

Bu örnekte, NetworkModule adlı bir modül oluşturulur ve tüm uygulama ömrü boyunca yaşayan (singleton) bir Retrofit ve ApiService sağlar. provideRetrofit ve provideApiService fonksiyonları, Dagger Hilt tarafından kullanılarak bu bağımlılıkların nasıl oluşturulacağını tanımlar. Bu bağımlılıklar daha sonra uygulama içinde ihtiyaç duyulan yerlere enjekte edilebilir. Yani, servisi direkt çağırmak yerine direk dagger aracılığı ile çağıracağız ve tek bir yerden yönetmiş olacağız. Servisteki herhangi bir değişiklik tüm çağırdığımız yerde etkilenmeyecektir. Buraya tıklayarak dependency injection konusunu tam olarak inceleyebilrsiniz.

Şimdi ise servisimizi oluşturalım. Google Auth REST Api için kullanacağımız parametre “v1/accounts:signInWithPassword?key=/*YOUR KEY HERE*/” bu olacak. Key’inizi Firebase’in Project Settings kısmında bulabilirsiniz. Aşağıdaki resimde kırmızı ile kare içine aldığım kısım projenize ait olan Web API Key’dir.

 

firebasewebapikey


Ardından aşağıdaki şekilde Servis Interfacesini oluşturalım.

import com.example.retrofitdeapp.data.model.UserModel
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.POST

interface Services {
 @POST("v1/accounts:signInWithPassword?key=/*YOUR KEY HERE*/")
 suspend fun login(@Body userModel: UserModel) : Response<UserModel>
}


API ile iletişim kurmak için kullanılacak metodları tanımladık.

@POST: Bu etiket, bu fonksiyonun bir POST isteği yapacağını belirtir. İçinde verilen URL son eki ise isteğin gönderileceği tam URL’yi oluşturmak için kullanılır.

“v1/accounts:signInWithPassword?key=/*YOUR KEY HERE*/”: Bu kısım, API isteğinin gönderileceği URL son ekini içerir. Burada yer alan /*YOUR KEY HERE*/ kısmı, gerçek API anahtarınızla değiştirilmelidir.

suspend fun login(@Body userModel: UserModel) : Response<UserModel>: Bu kısım, login adında bir fonksiyon tanımlar. Bu fonksiyon, bir UserModel alır ve API’ye POST isteği yaparak, bir Response<UserModel> döndürür. Bu fonksiyonun önünde suspend kelimesi kullanılarak, bu fonksiyonun Kotlin’in eşzamanlılık modeli olan “coroutines” ile çalıştırılacağını belirtir. Bu sayede, fonksiyon asenkron olarak çalışacak ve işlemin tamamlanmasını beklemeden diğer işlemlere devam edebilirsiniz.

@Body userModel: UserModel: Buradaki @Body etiketi, daha önce bahsettiğim gibi, isteğin gövdesine (body) eklenecek veriyi belirtir. Bu örnekte, userModel nesnesi API isteğinin gövdesine eklenecektir.

Özetle, bu Services interface’i, Retrofit ile kullanılacak bir API isteği tanımlar. Bu istek, UserModel alır ve API’ye POST isteği yaparak, başarılı bir şekilde tamamlandığında bir UserModel içeren bir yanıt döndürür.

Bunu da oluşturduktan sonra di klasörümüze gelerek artık DaggerHilt için gerekli olan base (temel) sınıfımızı oluşturalım. Bu sınıfın ismini genellikle “HiltApplication” olarak belirleriz.

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class HiltApplication : Application() {
}


Bu kod, Dagger Hilt bağımlılık enjeksiyon (dependency injection) kütüphanesi için Android uygulamasının temel sınıfını (base class) tanımlar. Dagger Hilt, uygulamanın bağımlılık grafiğini yöneten ve bağımlılıkları sağlayan bir kütüphanedir.

@HiltAndroidApp: Bu etiket, sınıfın Dagger Hilt tarafından kullanılacak olan temel Android uygulama sınıfı olduğunu belirtir. Bu anotasyon sayesinde, Dagger Hilt uygulamanın bağımlılık grafiğini oluşturur ve yönetir.

class HiltApplication : Application() Bu kısım, HiltApplication adında yeni bir sınıf tanımlar ve Android’in Application sınıfından türetilmiştir. Bu sınıf, uygulamanın yaşam döngüsü boyunca Dagger Hilt bağımlılık enjeksiyonunun başlatılması ve yönetilmesi için kullanılır.

Bu sınıf, uygulamanızda Dagger Hilt kullanarak bağımlılık enjeksiyonunun başlatılması ve yönetilmesi için temel bir yapı sağlar. Bu sayede, uygulamanızda bağımlılıklarınızı daha düzenli, esnek ve test edilebilir bir şekilde yönetebilirsiniz. Ayrıca, AndroidManifest.xml dosyasında aşağıdaki isim değişikliğini yapmayı unutmayın.

android:name=".data.di.HiltApplication"


Şimdi ise repository klasörüne gelerek LoginRepository sınıfını oluşturalım.

import com.example.retrofitdeapp.data.remote.Services
import com.example.retrofitdeapp.data.model.UserModel
import retrofit2.Response
import javax.inject.Inject

class LoginRepository @Inject constructor(private val apiService : Services) {
    suspend fun loginUser(userModel: UserModel) : Response<UserModel>{
        return apiService.login(userModel)
    }
}


LoginRepository adında bir sınıf tanımladık. Bu sınıf, kullanıcı giriş işlemleri için kullanılacak bir Repository sınıfıdır. Repository sınıfları, veri kaynağı ile iletişim kurarak veri almayı ve veri işlemlerini gerçekleştirmeyi sağlar. Bu örnekte, LoginRepository sınıfı, API servisini kullanarak kullanıcı girişi yapmayı sağlar.

@Inject constructor(private val apiService : Services) Bu kısım, Dagger Hilt bağımlılık enjeksiyon kütüphanesi ile LoginRepository sınıfına bağımlılıklarını enjekte etmek için kullanılır. Bu örnekte, Services adlı bir API servisi enjekte edilir. @Inject etiketi, Dagger Hilt’a bu sınıfın bir bağımlılık olarak enjekte edilmesi gerektiğini belirtir.

suspend fun loginUser(userModel: UserModel) : Response<UserModel> Bu kısım, loginUser adında bir fonksiyon tanımlar. Bu fonksiyon, bir UserModel alır ve API servisi aracılığıyla kullanıcı girişi yaparak, başarılı bir şekilde tamamlandığında bir UserModel içeren bir yanıt (Response<UserModel>) döndürür. Bu fonksiyonun önünde suspend kelimesi kullanılarak, bu fonksiyonun Kotlin Coroutines ile çalıştırılacağını belirtir. Bu sayede, fonksiyon asenkron olarak çalışacak ve işlemin tamamlanmasını beklemeden diğer işlemlere devam edebilirsiniz.

return apiService.login(userModel) Bu satır, apiService (enjekte edilen Services nesnesi) üzerindeki login fonksiyonunu çağırır ve aldığı userModel nesnesini parametre olarak iletir. Bu sayede, API isteği gerçekleştirilir ve dönen sonuç, loginUser fonksiyonunun dönüş değeri olarak kullanılır.

Özetle, bu LoginRepository sınıfı, kullanıcı girişi yapmak için API servisini kullanarak işlemleri gerçekleştiren bir Repository sınıfıdır. Bu sınıf, Dagger Hilt ile bağımlılık enjeksiyonunu kullanarak API servisini enjekte eder ve kullanıcı girişi yaparken asenkron çalışma sağlamak için Kotlin Coroutines ile çalışır.

Giriş yaptığımızda dönecek olan giriş başarılı işlemini ViewModel içerisinde yönetmek için LoginResult adında bir class oluşturalım. 

sealed class Result<out T> {
    object Loading : Result<Nothing>()
    data class Success<out T>(val data: T) : Result<T>()
    data class Error(val exception: Throwable) : Result<Nothing>()
}


Burda, Kotlin’de genel bir Result sınıfı tanımladık. Bu sınıf, işlemlerin sonuçlarını temsil etmek için kullanılabilir ve başarılı sonuçlar, hatalar ve yüklenme durumu gibi farklı durumları modelleyebilir. Bu örnekte, Result sınıfı “sealed class” (mühürlü sınıf) olarak tanımlanmıştır. Mühürlü sınıflar, belirli bir sayıda alt sınıf tanımlanabilen ve bu sayede daha güçlü bir tür kontrolü sağlayan sınıflardır.

sealed class Result<out T> Bu kısım, Result adında bir mühürlü sınıf tanımlar. Bu sınıf, dışarıya açık (covariant) bir tür parametresi olan T ile birlikte gelir. T, başarılı işlemlerde döndürülecek verinin türünü temsil eder.

object Loading : Result<Nothing>() Bu kısım, Result sınıfının bir alt sınıfı olan Loading adında bir nesne tanımlar. Bu nesne, işlemin yüklenme durumunu temsil etmek için kullanılır. Nothing türündeki parametreyle, Loading durumunun başarılı bir işlem sonucu içermeyeceğini belirtir.

data class Success<out T>(val data: T) : Result<T>() Bu kısım, Result sınıfının başarılı işlemleri temsil eden Success adında bir alt sınıf tanımlar. Bu veri sınıfı, dışarıya açık (covariant) bir tür parametresi olan T ile birlikte gelir ve başarılı işlemin sonucunu içeren data adında bir özellik tanımlar.

data class Error(val exception: Throwable) : Result<Nothing>() Bu kısım, Result sınıfının hatalı işlemleri temsil eden Error adında bir alt sınıf tanımlar. Bu veri sınıfı, Throwable türünde bir exception özelliği içerir ve hatayı temsil etmek için kullanılır. Nothing türündeki parametreyle, Error durumunun başarılı bir işlem sonucu içermeyeceğini belirtir.

Bu Result sealed sınıfı, işlemlerin sonuçlarını daha güçlü bir tür kontrolü ile temsil etmek için kullanılabilir ve API istekleri, veritabanı işlemleri gibi farklı alanlarda kullanılabilir. Bu sayede, başarılı işlemler, hatalar ve yüklenme durumu gibi farklı durumlar daha düzenli ve güvenli bir şekilde yönetilebilir. Burada da Login işlemindeki dönen sonucu kontrol etmek için kullanıyoruz.

Artık, LoginViewModelimizi yazabiliriz. viewmodel klasörü içerisinde LoginViewModel sınıfımızı oluşturalım.

import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.retrofitdeapp.data.model.UserModel
import com.example.retrofitdeapp.data.repository.LoginRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
import com.example.retrofitdeapp.data.helper.Result



@HiltViewModel
class LoginViewModel @Inject constructor(private val loginRepository: LoginRepository) : ViewModel() {

    private val _loginResult = MutableLiveData<Result<Unit>>(Result.Loading)
    val loginResult : LiveData<Result<Unit>> = _loginResult
        fun loginUser(userModel: UserModel){

        viewModelScope.launch {
            val response = loginRepository.loginUser(userModel)
            if(response.isSuccessful){
                Log.e("LOGIN", "Login Successfull")
                _loginResult.value = Result.Success(Unit)
            }

            else
            {
                Log.e("LOGIN", "Login Failed")
                Log.e("Gelen yanıt:",response.toString())
                _loginResult.value = Result.Error(Exception("Login Failed"))

            }
        }
    }
}


@HiltViewModel:
Bu etiket, Dagger Hilt bağımlılık enjeksiyon kütüphanesinin bu sınıfı bir ViewModel olarak kabul etmesini sağlar ve gerekli bağımlılıkların enjekte edilmesini sağlar.

class LoginViewModel @Inject constructor(private val loginRepository: LoginRepository) : ViewModel() Bu kısım, LoginViewModel adında bir sınıf tanımlar ve ViewModel sınıfından türetilir. LoginRepository bağımlılığını enjekte etmek için @Inject anotasyonu kullanılır.

_loginResult ve loginResult: Bu iki özellik, kullanıcı girişi sonucunu temsil eden Result<Unit> türünde bir LiveData nesnesi içerir. _loginResult adındaki MutableLiveData özelliği, ViewModel içinde kullanılırken, loginResult adındaki LiveData özelliği dışarıya açık olarak kullanılabilir.

fun loginUser(userModel: UserModel): Bu kısım, kullanıcı girişi yapmak için kullanılacak bir fonksiyon tanımlar. Bu fonksiyon, UserModel nesnesi alır ve kullanıcı girişi işlemine başlar.

viewModelScope.launch: Bu satır, ViewModel kapsamında bir Kotlin Coroutine başlatır. Bu sayede, asenkron bir işlem başlatılır ve işlem tamamlanana kadar beklemeden diğer işlemlere devam edilir.

val response = loginRepository.loginUser(userModel) Bu satır, enjekte edilen loginRepository’nin loginUser fonksiyonunu çağırarak API’den gelen yanıtı alır.

if(response.isSuccessful) ve else blokları: Bu kısımda, API yanıtının başarılı olup olmadığı kontrol edilir. Başarılı bir yanıt alındığında, girişin başarılı olduğu belirtilir ve _loginResult değeri Result.Success(Unit) olarak güncellenir. Başarılı olmayan bir yanıt alındığında ise, hata mesajları loglanır ve _loginResult değeri bir Result.Error ile güncellenir.

Bu sınıf, kullanıcı girişi yaparken asenkron çalışmayı sağlamak için Kotlin Coroutines ve ViewModel kapsamını kullanır. Kullanıcı girişi işlemi başarılı olduğunda, _loginResult değeri Result.Success(Unit) olarak güncellenir ve başarısız olduğunda bir Result.Error ile güncellenir. Bu sayede, UI katmanı loginResult değerine göre gerekli güncellemeleri yapabilir ve kullanıcıya geri bildirim sağlayabilir. Belki burada neden bu işlemleri nesneyi oluşturacağımız yerde yapmıyoruz diye sorabilirsiniz. Ancak, burada MVVM yapısına uygun clean code yazdığımızdan dolayı UI’da yapılmaması gereken işlemleri viewmodel içerisinde yapıyoruz. Şimdi MainActivity içerisine gelerek son adımı tamamlayalım.

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import com.example.retrofitdeapp.ui.viewmodel.LoginViewModel
import androidx.activity.viewModels
import androidx.lifecycle.LifecycleOwner
import com.example.retrofitdeapp.data.model.UserModel
import com.example.retrofitdeapp.databinding.ActivityMainBinding
import dagger.hilt.android.AndroidEntryPoint
import com.example.retrofitdeapp.data.helper.Result



@AndroidEntryPoint
class MainActivity : AppCompatActivity(), LifecycleOwner {

    private lateinit var binding: ActivityMainBinding
    private val loginViewModel : LoginViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)

        setContentView(binding.root)

        binding.apply{

            btnLogin.setOnClickListener()
            {
                if(edEmail.text.toString().isNullOrBlank() || edPass.text.toString().isNullOrBlank())
                {
                    Toast.makeText(applicationContext,"Please enter valid e-mail and password!", Toast.LENGTH_SHORT).show()
                }

                else
                {
                    val newLogin = UserModel(edEmail.text.toString(), edPass.text.toString())
                    loginViewModel.loginUser(newLogin)

                }

            }
        }

        loginViewModel.loginResult.observe(this) { result ->
            when (result) {
                is Result.Loading -> {
                    // Loading....
                }
                is Result.Success -> {
                    Toast.makeText(applicationContext, "Login Succesful!", Toast.LENGTH_SHORT).show()
                }
                is Result.Error -> {
                    Toast.makeText(applicationContext, "Incorrect E-Mail or Password!", Toast.LENGTH_SHORT).show()
                }
            }
        }


    }
}


Burada UI için yapılan şeyleri anlatmama gerek yok sanırım. 🙂 loginViewModel içerisinde oluşturulan loginResult LiveDatasını dinliyoruz. Bu dinleme işlemini butona tıklarken yapmanıza gerek yok, çünkü zaten arkaplanda asenkron çalışıyor. Loading kısmına dokunmadım, burada kullanıcı giriş yaparken kullanıcının verilerini kullanıcıyı 1-2 saniye bekletip hemen o sırada ilgili verileri çekebilirsiniz. 

MVVM, Retrofit, Dagger, LiveData, Coroutines yapılarının hepsini bir projede anlatmış olduk, bir taşta 5 kuş vurma misali 🙂 Burada belki soracağınız soru bir diğer soru “Firebase SDK veriyorken neden bunca şey ile uğraştık?” olabilir. Her API’ye özel SDK olmayabilir. Örneğin, kendi yazdığınız bir API servisinin SDK yazımıyla uğraşmayabilirsiniz. Bu durumda bu beş kavrama aynı şekilde ihtiyacınız olacaktır. 

Buraya tıklayarak projenin tamamının GitHub reposuna ulaşabilirsiniz. Anlamadığınız yerleri mutlaka yorum olarak bekliyorum. İyi çalışmalar.