23 Ocak 2021 Cumartesi

Android Kütüphane Sistemi Mobil Uygulaması - 2 (Login İşlemi)

 Merhabalar. Android Kütüphane Sistemi Mobil Uygulaması yazı dizisinin ikinci yazısı ile yeniden sizlerleyim. Bu yazıda sisteme giriş işlemini yapacağız.Tekrar belirtmekte fayda olacak, uygulamanın kodlarına buradan erişebilirsiniz.

Login İşlemi

Burada ki senaryomuz aşağıda ki adımlarla ilerlemektedir.

  1. Kullanıcı adı ve şifre girilip Giriş butonuna basılır.
  2. Girdilerin dolu olup olmadıkları kontrol edilir.
  3. Web servise login olmak için gerekli istek gönderilir.
  4. Web servisden gelen isteğe göre aşağıda ki aksiyonlar alınır.   
    Giriş Başarılı;
        Giriş başarılı yani token alınmışsa bu token shared preferences'a yazılır. Bu token ile servisten kullanıcı bilgileri alınır ve uygulamanın Anasayfası açılır.

    Giriş Başarısız;
        Kullanıcıya servisten gelen hata mesajı gösterilir.
                    

İlk olarak layout dosyalarına bakalım;


activity_login.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
style="@style/loginParent"
tools:context=".LoginActivity">
<ScrollView
style="@style/parent">
<RelativeLayout
style="@style/parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:weightSum="12">

<LinearLayout
style="@style/parent"
android:background="@drawable/login_shape_bk"
android:orientation="vertical"
android:layout_weight="3">

<ImageView
style="@style/parent"
android:background="@drawable/ic_login_bk"
android:contentDescription="login background" />

</LinearLayout>

</LinearLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:orientation="vertical"
android:layout_marginTop="40dp"
android:layout_marginRight="30dp"
android:layout_marginLeft="30dp">

<TextView
style="@style/headerText"
android:layout_gravity="center"
android:text="@string/app_name"
android:layout_marginTop="40dp"/>

<include
android:id="@+id/loginLayoutId"
layout="@layout/layout_login"/>
</LinearLayout>
</RelativeLayout>
</ScrollView>
</RelativeLayout>


Yukarıda include edilmiş olan layout_login layoutu aşağıda ki gibidir.

layout_login.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="@style/loginCard"
app:cardCornerRadius="@dimen/loginCardRadius"
android:elevation="5dp"
android:layout_gravity="center"
android:layout_marginTop="@dimen/loginViewsMargin"
android:layout_marginBottom="@dimen/loginViewsMargin"
android:background="@color/whiteCardColor">

<LinearLayout
android:id="@+id/loginLayout"
style="@style/linearParent"
android:layout_gravity="center"
android:padding="@dimen/loginViewsMargin">

<TextView
style="@style/headerTextPrimary"
android:text="Giriş"
android:layout_marginTop="@dimen/loginViewsMargin"/>

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputKullaniciAdi"
style="@style/parent"
app:errorEnabled="true"
android:layout_marginTop="@dimen/loginViewsMargin">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/editTextKullaniciAdi"
style="@style/modifiedEditText"
android:maxLines="1"
android:hint="@string/login_email_hint"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputSifre"
style="@style/parent"
app:errorEnabled="true"
android:layout_marginTop="@dimen/loginViewsMargin">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/editTextSifre"
style="@style/modifiedEditText"
android:hint="@string/login_password_hint"
android:maxLines="1"
android:inputType="textPassword"/>
</com.google.android.material.textfield.TextInputLayout>

<Button
android:id="@+id/cirLoginButton"
style="@style/loginButton"
android:text="Giriş"
android:layout_width="match_parent"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/loginViewsMargin"/>

<TextView
style="@style/parent"
android:textAlignment="center"
android:textStyle="bold"
android:textColor="@color/primaryTextColor"
android:text="@string/login_signup_hint"
android:id="@+id/registerTextView"
android:layout_marginTop="@dimen/loginViewsMargin"/>

</LinearLayout>
</androidx.cardview.widget.CardView>


Yukarıda ki layout dosyasının görünümü aşağıda ki gibidir.



Bu ekranda kullandığımız TextInputLayout componenti material design componentli olup,bu kütüphanenin componentlerini kullanabilmeniz için build.gradle (Module:app) dosyasının dependencies kısmına aşağıda ki satırı eklemeniz gerekmektedir.

    implementation 'com.google.android.material:material:1.2.1'


Bu kütüphanenin componentleri bizlere hem daha modern bir görünüm hem de daha fazla özellikler sağlamaktadır. 

Artık yukarıda bahsettiğim akışı uygulayacağımız Activity'e geçebilliriz.

LoginActivity.kt
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);

  1. val pd: Dialog = Dialog(this);
  2. val pdView: View = layoutInflater.inflate(R.layout.custom_progress_dialog,null);
  3. pd.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT));
  4. pd.setTitle(null);
  5. pd.setContentView(pdView);
  6. pd.setCancelable(false);

  7. cirLoginButton.setOnClickListener {
  8. val kullaniciAdi:String = editTextKullaniciAdi.text.toString().trim();
  9. val sifre:String = editTextSifre.text.toString().trim();

  10. if(TextUtils.isEmpty(kullaniciAdi)){
  11. textInputKullaniciAdi.error = resources.getString(R.string.bosKullaniciAdiHata);
  12. return@setOnClickListener;
  13. }

  14. if(TextUtils.isEmpty(kullaniciAdi)) {
  15. textInputSifre.error= resources.getString(R.string.bosSifreHata);
  16. return@setOnClickListener;
  17. }

  18. pd.show();
  19. val kullaniciService:IKullaniciService = WebApiUtil.getKullaniciService();
  20. val user = AccountCredentials(kullaniciAdi,sifre);

  21. kullaniciService.login(user).enqueue(object:Callback<String>{
  22. override fun onFailure(call: Call<String>?, t: Throwable) {
  23. SimpleToast.error(applicationContext, t.message, "{fa-times-circle}");
  24. pd.dismiss();
  25. }

  26. override fun onResponse(call: Call<String>?, response: Response<String>?) {
  27. val token:String = response?.body().toString();
  28. if(token != null && !token.contains("hatali")){
  29. ProjectUtil.putStringDataToSharedPreferences(applicationContext,ProjectUtil.SHARED_PREF_FILE,"token",token);
  30. getKullaniciBilgi(applicationContext,pd,kullaniciService,token);
  31. }else{
  32. SimpleToast.error(applicationContext, resources.getString(R.string.hataliLogin), "{fa-times-circle}");
  33. pd.dismiss();
  34. }
  35. }
  36. });
  37. }

  38. textInputKullaniciAdi.editText!!.addTextChangedListener(TextInputErrorClearListener(textInputKullaniciAdi));
  39. textInputSifre.editText!!.addTextChangedListener(TextInputErrorClearListener(textInputSifre));

  40. registerTextView.setOnClickListener {
  41. ProjectUtil.activityYonlendir(this,RegisterActivity());
  42. }

  43. }

  44. private fun getKullaniciBilgi( c: Context,pd: Dialog,kullaniciService: IKullaniciService,token:String) {
  45. kullaniciService.getKullaniciBilgi("Bearer "+token.trim()).enqueue(object:Callback<KullaniciModel>{
  46. override fun onFailure(call: Call<KullaniciModel>?, t: Throwable?) {
  47. Log.e("FAIL",t?.message);
  48. pd.dismiss();
  49. }

  50. override fun onResponse(call: Call<KullaniciModel>?, response: Response<KullaniciModel>) {
  51. val kullanici: KullaniciModel = response.body() as KullaniciModel;
  52. writeToSPKullaniciBilgileri(kullanici,applicationContext);
  53. ProjectUtil.activityYonlendir(c,MainActivity());
  54. pd.dismiss();
  55. }
  56. });
  57. }

  58. private fun writeToSPKullaniciBilgileri(kullanici: KullaniciModel, applicationContext: Context) {
  59. ProjectUtil.putStringDataToSharedPreferences(applicationContext,ProjectUtil.SHARED_PREF_FILE,"adSoyad",kullanici.ad+" "+kullanici.soyad);
  60. ProjectUtil.putStringDataToSharedPreferences(applicationContext,ProjectUtil.SHARED_PREF_FILE,"eposta",kullanici.eposta);
  61. val sb:StringBuilder = StringBuilder();
  62. for(rol in kullanici.roller){
  63. sb.append(rol.rol.value).append(",");
  64. }
  65. ProjectUtil.putStringDataToSharedPreferences(applicationContext,ProjectUtil.SHARED_PREF_FILE,"rol",sb.toString());
  66. val sdf:SimpleDateFormat = SimpleDateFormat("dd.MM.yyyy");
  67. ProjectUtil.putStringDataToSharedPreferences(applicationContext,ProjectUtil.SHARED_PREF_FILE,"dogumTarih",sdf.format(kullanici.dogumTarihi));
  68. ProjectUtil.putStringDataToSharedPreferences(applicationContext,ProjectUtil.SHARED_PREF_FILE,"kullaniciAdi",kullanici.username);
  69. ProjectUtil.putStringDataToSharedPreferences(applicationContext,ProjectUtil.SHARED_PREF_FILE,"ad",kullanici.ad);
  70. ProjectUtil.putStringDataToSharedPreferences(applicationContext,ProjectUtil.SHARED_PREF_FILE,"soyad",kullanici.soyad);
  71. val ia:StringBuilder = StringBuilder();
  72. for(i in kullanici.ilgiAlanlari){
  73. ia.append(i.id).append(",");
  74. }
  75. ProjectUtil.putStringDataToSharedPreferences(applicationContext,ProjectUtil.SHARED_PREF_FILE,"ilgialanlar",ia.toString());
  76. ProjectUtil.putBooleanDataToSharedPreferences(applicationContext,ProjectUtil.SHARED_PREF_FILE,"haberdarmi",kullanici.haberdarmi);
  77. ProjectUtil.putStringDataToSharedPreferences(applicationContext,ProjectUtil.SHARED_PREF_FILE,"cinsiyet",kullanici.cinsiyet.value)
  78. }

  79. override fun onBackPressed() {
  80. super.onBackPressed();
  81. ProjectUtil.anaEkranaDon(this);
  82. }
}

Akışta ki 2. madde yani validation işlemini aşağıda ki kodlar yerine getirmektedir.

            if(TextUtils.isEmpty(kullaniciAdi)){
textInputKullaniciAdi.error = resources.getString(R.string.bosKullaniciAdiHata);
return@setOnClickListener;
}

if(TextUtils.isEmpty(kullaniciAdi)) {
textInputSifre.error= resources.getString(R.string.bosSifreHata);
return@setOnClickListener;
}


Burada return@setOnClickListener;  javascriptte ki return false gibi düşünebilirsiniz. Validation sağlanmadığı takdirde görünüm aşağıda ki gibi olacaktır.




Kullanıcı adı ve şifrenin eksiksiz bir şekilde girilmesinden sonra artık uygulamanın servis ile haberleşmesi başlayacaktır. Ben bu uygulamada web servis işlemleri için Retrofit kütüphanesini kullandım. Bu kütüphaneyi kullanabilmeniz için build.gradle (Module:app) dosyasının dependencies kısmına aşağıda ki satırları eklemeniz gerekmektedir. 

    implementation 'com.squareup.retrofit2:retrofit:2.1.0'
    implementation 'com.google.code.gson:gson:2.6.1'
    implementation 'com.squareup.retrofit2:converter-gson:2.1.0'

Retrofit kullanmak için öncelikle ihtiyaç duyduğumuz şey bir retrofit client classıdır. Bu class aşağıda ki gibidir.


RetrofitClientUtil.kt

class RetrofitClientUtil {

companion object{

fun getClient(): Retrofit{
return Retrofit.Builder()
.baseUrl(ProjectUtil.API_URL)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
}

fun getClient(c:Context): Retrofit{
val httpClient: OkHttpClient.Builder = OkHttpClient.Builder();
httpClient.addInterceptor(object:Interceptor{
override fun intercept(chain: Interceptor.Chain): Response {
val request:Request = chain.request().newBuilder().addHeader("Authorization",
"Bearer "+ProjectUtil.getStringDataFromSharedPreferences(c,ProjectUtil.SHARED_PREF_FILE,"token")?.trim()).build();
return chain.proceed(request);
}
});
return Retrofit.Builder()
.baseUrl(ProjectUtil.API_URL)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build();
}
}
}

Not : Shared Preferences işlemleri uygulamada sık kullanılan işlemlerden olup , ProjectUtil classında statik metod haline getirilmiştir.


Burada 2 farklı getClient metodu yazmaktaki maksadım bazı requestlere token göndermeden ulaşabilme ihtiyacından kaynaklıdır. Hatta login metodu bunlardan biridir. İleride tekrar bu kısma değineceğiz. getClient(c:Context) metodunda ki context bilgisi shared preferences dan bilgi almak için olup bu bilgi token bilgisidir. Bu token ise başarılı login işlemi sonucu web servisin bize döndüğü JWT (JSON Web Token)'dir.  Akabinde yazacağımız servis classlarımızdan instance almak için proje boyunca aşağıda ki Util classını kullanacağız.

WebApiUtil.kt

class WebApiUtil {

companion object{

fun getKullaniciService():IKullaniciService{
return RetrofitClientUtil.getClient().create(IKullaniciService::class.java);
}

fun getKullaniciService(c:Context):IKullaniciService{
return RetrofitClientUtil.getClient(c).create(IKullaniciService::class.java);
}

fun getParametreService(c:Context):IParametreService{
return RetrofitClientUtil.getClient(c).create(IParametreService::class.java);
}

fun getKitapService(c:Context):IKitapIslemService{
return RetrofitClientUtil.getClient(c).create(IKitapIslemService::class.java);
}
}
}


Artık kullanıcı işlemlerini yaptığımız servise geçebiliriz.

IKullaniciService.kt
interface IKullaniciService {

@POST("login")
fun login(@Body user:AccountCredentials):Call<String>;

@GET("api/kullanici/bilgi")
fun getKullaniciBilgi(@Header("Authorization") token:String):Call<KullaniciModel>;

@GET("api/kullanici/resim")
fun getKullaniciResim():Call<KullaniciResimModel>;

@Headers("Content-Type: application/json")
@POST("api/kullanici/guncelle")
fun kullaniciGuncelle(@Body jsonStr: String):Call<ResponseStatusModel>;

@Multipart
@POST("api/kullanici/resim/yukle")
fun kullaniciResimGuncelle(@Part file:MultipartBody.Part , @Part("username") username:RequestBody):Call<ResponseStatusModel>;

@Headers("Content-Type: application/json")
@POST("api/kullanici/kaydet")
fun kullaniciKaydet(@Body jsonStr: String):Call<ResponseStatusModel>;

@Headers("Content-Type: application/json")
@GET("api/parametre/kitapturler")
fun getKitapturListe(): Call<ArrayList<IlgiAlanlariParametreModel>>;

}

POST isteği göndereceğimiz login metodu ilk metod olup web servis tarafında ki POJO ile eşleşmesine dikkat etmemiz gereken yani @Body ' de belirttiğim data class aşağıda ki gibidir.

AccountCredentials.kt
data class AccountCredentials(
@SerializedName("username")
@Expose
var username:String,

@SerializedName("password")
@Expose
var password:String ){
}

Aşağıda ki kod satırları ile login işlemimizi tamamlayalım;

val kullaniciService:IKullaniciService = WebApiUtil.getKullaniciService();
val user = AccountCredentials(kullaniciAdi,sifre);

kullaniciService.login(user).enqueue(object:Callback<String>{
override fun onFailure(call: Call<String>?, t: Throwable) {
SimpleToast.error(applicationContext, t.message, "{fa-times-circle}");
pd.dismiss();
}

override fun onResponse(call: Call<String>?, response: Response<String>?) {
val token:String = response?.body().toString();
if(token != null && !token.contains("hatali")){
ProjectUtil.putStringDataToSharedPreferences(applicationContext,ProjectUtil.SHARED_PREF_FILE,"token",token);
getKullaniciBilgi(applicationContext,pd,kullaniciService,token);

}else{
SimpleToast.error(applicationContext, resources.getString(R.string.hataliLogin), "{fa-times-circle}");
pd.dismiss();
}
}
});

Kullanıcı adı ve şifreyi input text girdilerinden aldık. Servise isteği gönderdik , son aşamada gelen cevap başarılı yani token içeren bir response ise kullanıcı bilgilerini almak için getKullaniciBilgi metodunu çağırdık. Başarısız ise hata toast mesajı bastık. SimpleToast ta projede kullandığım ayrı bir kütüphane olup bu kütüphaneyi kullanabilmeniz için build.gradle (Module:app) dosyasının dependencies kısmına aşağıda ki satırları eklemeniz gerekmektedir. 

    implementation 'com.github.Pierry:SimpleToast:v1.7'

Burada dikkat edilmesi gereken diğer bir husus ise işlem sonucu ne olursa olsun (olumlu ya da olumsuz) ProgressDialog u dismiss edişimdir.

Giriş işlemi başarısız ise aşağıda ki gibi bir hata mesajı karşımıza çıkacaktır.



Giriş işlemi başarılı ise kullanıcı bilgilerinin alınıp shared prereferences a yazıldığı ardından da anasayfaya gönderme işleminin yapıldığı metod çalışır. Fakat daha önemlisi ise alınan token sürekli kullanılacağı için ilk olarak Shared Preferences'a token yazılır.

ProjectUtil.putStringDataToSharedPreferences(applicationContext,ProjectUtil.SHARED_PREF_FILE,"token",token);

Daha sonra ise aşağıda ki metod çalışır.

private fun getKullaniciBilgi( c: Context,pd: Dialog,kullaniciService: IKullaniciService,token:String) {
kullaniciService.getKullaniciBilgi("Bearer "+token.trim()).enqueue(object:Callback<KullaniciModel>{
override fun onFailure(call: Call<KullaniciModel>?, t: Throwable?) {
Log.e("FAIL",t?.message);
pd.dismiss();
}

override fun onResponse(call: Call<KullaniciModel>?, response: Response<KullaniciModel>) {
val kullanici: KullaniciModel = response.body() as KullaniciModel;
writeToSPKullaniciBilgileri(kullanici,applicationContext);
ProjectUtil.activityYonlendir(c,MainActivity());
pd.dismiss();
}
});
}

Bu metod bize web servisten kullanıcı bilgilerini döner. Dönen bu kullanıcı bilgilerini tutan KullaniciModel data class ı aşağıda ki gibidir. (Bu data class ın fieldları servisten dönen json parametreleri ile eşleşmelidir yani aynı isimde olmalıdır.) 

KullaniciModel.kt
data class KullaniciModel(
@SerializedName("username")
@Expose
var username:String,

@SerializedName("password")
@Expose
var password:String,

@SerializedName("ad")
@Expose
var ad:String,

@SerializedName("soyad")
@Expose
var soyad:String,

@SerializedName("dogumTarihi")
@Expose
var dogumTarihi:Date,

@SerializedName("resim")
@Expose
var resim:String,

@SerializedName("eposta")
@Expose
var eposta:String,

@SerializedName("cinsiyet")
@Expose
var cinsiyet:CinsiyetModel,

@SerializedName("haberdarmi")
@Expose
var haberdarmi:Boolean,


@SerializedName("ilgiAlanlari")
@Expose
var ilgiAlanlari:List<IlgiAlanlariParametreModel>,

@SerializedName("roller")
@Expose
var roller:List<RolModel>

) {
}
Alınan kullanıcı bilgileri ise writeToSPKullaniciBilgileri metodu ile Shared Preferences a yazılır daha sonrada anasayfa ya yönlendirme yapılır. 
Soru,istek ve önerilerinizi mesutemrecelenk@gmail.com adresine yazabilirsiniz. Bir sonraki yazıda görüşmek üzere hoşça kalın...

Hiç yorum yok:

Yorum Gönder