30 Ocak 2021 Cumartesi

Android Kütüphane Sistemi Mobil Uygulaması - 3 (Register İşlemi)

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

Register İşlemi

Kaydet işleminde senaryomuz aşağıda ki gibi olacaktır.

1.Zorunlu alan kontrolü yapılır.

2.Kayıt işlemi yapılır

    2.1 Kayıt başarılı ise kişiye bilgilendirme mesajı verilip login ekranına yönlendirilir.

    2.2 Kayıt başarısız ise kullanıcıya bilgilendirme mesajı verilir.


Şimdi ilk olarak register ekranının layout dosyasına göz atalım.

activity_register.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"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
style="@style/loginParent"
tools:context=".RegisterActivity">
<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:layout_height="match_parent"
android:layout_weight="3"
android:background="@drawable/login_shape_bk"
android:orientation="vertical">

<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:layout_marginLeft="30dp"
android:layout_marginRight="30dp"
android:orientation="vertical">

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="@string/registerTitle"
android:layout_gravity="center_horizontal"
android:textAlignment="center"
android:textColor="@color/whiteTextColor"
android:textSize="18sp"
android:textStyle="bold"></TextView>

<androidx.cardview.widget.CardView
style="@style/loginCard"
android:layout_gravity="center"
android:layout_marginTop="@dimen/loginViewsMargin"
android:layout_marginBottom="@dimen/loginViewsMargin"
android:background="@color/whiteCardColor"
android:elevation="5dp"
app:cardCornerRadius="@dimen/loginCardRadius">

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

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/registerProfilKullaniciAd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp">

<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/profilKullaniciAdi"
style="@style/modifiedEditText"
android:maxLines="1"/>
</com.google.android.material.textfield.TextInputLayout>

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

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/registerProfilAdText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp">

<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/profilAd"
style="@style/modifiedEditText"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/registerProfilSoyadText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp">

<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/profilSoyad"
style="@style/modifiedEditText"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/registerProfilEposta"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp">

<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
android:hint="@string/profilEpostaHint"
style="@style/modifiedEditText"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/registerProfilDogumTarih"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp">

<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusableInTouchMode="false"
android:hint="@string/profilDogumTarihi"
style="@style/modifiedEditText"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>

<libs.mjn.fieldset.FieldSetView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="10dp"
android:padding="8dp"
app:fsv_borderAlpha="0.75"
app:fsv_borderColor="@color/colorAccent"
app:fsv_borderRadius="12dp"
app:fsv_borderWidth="1dp"
app:fsv_legend="@string/cinsiyetLabel"
app:fsv_legendColor="@color/colorAccent"
app:fsv_legendFont="lobster.ttf"
app:fsv_legendPosition="left">

<RadioGroup
android:id="@+id/registerCinsiyetRadioGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">

<RadioButton
android:id="@+id/registerErkekRadioButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/cinsiyetErkekLabel"
android:buttonTint="@color/colorAccent"
android:theme="@style/KutSisRaidoButton" />

<RadioButton
android:id="@+id/registerKadinRadioButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="@string/cinsiyetKadinLabel"
android:buttonTint="@color/colorAccent"
android:theme="@style/KutSisRaidoButton" />
</RadioGroup>

</libs.mjn.fieldset.FieldSetView>

<Spinner
android:id="@+id/registerIlgiAlanlariSpinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
style="@style/spinner_style"/>

<CheckBox
android:id="@+id/registerHaberdarCheckBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/gelismeHabderdarLabel"
android:textStyle="italic"
android:buttonTint="@color/colorAccent"
android:theme="@style/KutSisCheckBox" />

<Button
android:id="@+id/profilKaydetButton"
android:text="@string/kaydetLabel"
android:background="@drawable/login_button_background"
android:textColor="@color/whiteTextColor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/loginViewsMargin"/>

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

Bu layout un çıktısı aşağıda ki gibidir.



Burada "İlgi alanlarınız" Spinner ' ı custom bir layout'a sahip olup seçim simgesi ise style.xml dosyasında setlenmiştir. Dilerseniz activity'nin kodlarını yazıp, zaten bu activity içerisinde dolan ve custom layout'a sahip olan bu spinner'ı anlatalım.

RegisterActivity.kt

class RegisterActivity : AppCompatActivity() {

private lateinit var ilgiAlanlariListe:ArrayList<IlgiAlanlariParametreModel>;
private var selectedIlgiAlanlar:ArrayList<Int> = ArrayList<Int>();
private lateinit var pd: Dialog;
private lateinit var kullaniciService:IKullaniciService;

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_register);

initializeComponents();

}

private fun initializeComponents() {
pd = Dialog(this);
val pdView: View = layoutInflater.inflate(R.layout.custom_progress_dialog,null);
pd.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT));
pd.setTitle(null);
pd.setContentView(pdView);
pd.setCancelable(false);

kullaniciService = WebApiUtil.getKullaniciService();

initDatePicker();
fillIlgiAlanlariListe();

registerProfilKullaniciAd.editText!!.addTextChangedListener(TextInputErrorClearListener(registerProfilKullaniciAd));
registerSifreTextInput.editText!!.addTextChangedListener(TextInputErrorClearListener(registerSifreTextInput));
registerProfilAdText.editText!!.addTextChangedListener(TextInputErrorClearListener(registerProfilAdText));
registerProfilSoyadText.editText!!.addTextChangedListener(TextInputErrorClearListener(registerProfilSoyadText));
registerProfilDogumTarih.editText!!.addTextChangedListener(TextInputErrorClearListener(registerProfilDogumTarih));
registerProfilEposta.editText!!.addTextChangedListener(TextInputErrorClearListener(registerProfilEposta));

profilKaydetButton.setOnClickListener {
val kullaniciAdi:String = registerProfilKullaniciAd.editText!!.text.toString();
val sifre:String = registerSifreTextInput.editText!!.text.toString();
val ad:String = registerProfilAdText.editText!!.text.toString();
val soyad:String = registerProfilSoyadText.editText!!.text.toString();
val dogumTar:String = registerProfilDogumTarih.editText!!.text.toString();
val cinsiyet:Int = registerCinsiyetRadioGroup.checkedRadioButtonId;
val eposta:String = registerProfilEposta.editText!!.text.toString();
val haberdarMi:Boolean = registerHaberdarCheckBox.isChecked;

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

if(TextUtils.isEmpty(sifre)){
registerSifreTextInput.error = resources.getString(R.string.registerSifreHata);
return@setOnClickListener;
}

if(TextUtils.isEmpty(ad)){
registerProfilAdText.error = resources.getString(R.string.registerAdHata);
return@setOnClickListener;
}

if(TextUtils.isEmpty(soyad)){
registerProfilSoyadText.error = resources.getString(R.string.registerSoyadHata);
return@setOnClickListener;
}

if(TextUtils.isEmpty(eposta)){
registerProfilEposta.error = resources.getString(R.string.epostaHata);
return@setOnClickListener;
}

if(TextUtils.isEmpty(dogumTar)){
registerProfilDogumTarih.error = resources.getString(R.string.registerDogumtarihHata);
return@setOnClickListener;
}

if(cinsiyet == -1){
SimpleToast.warning(applicationContext, resources.getString(R.string.cinsiyetHata), "{fa-exclamation-triangle}");
return@setOnClickListener;
}

if(this.selectedIlgiAlanlar == null || this.selectedIlgiAlanlar.isEmpty()){
SimpleToast.warning(applicationContext, resources.getString(R.string.ilgiAlanHata), "{fa-exclamation-triangle}");
return@setOnClickListener;
}

val cal:Calendar = Calendar.getInstance();
val dtarArr = dogumTar.split(".");
cal.set(Calendar.YEAR,dtarArr[2].toInt());
cal.set(Calendar.MONTH,dtarArr[1].toInt()-1);
cal.set(Calendar.DAY_OF_MONTH,dtarArr[0].toInt());

val jsonObj: JSONObject = JSONObject();
val iaArr: JSONArray = JSONArray();
var iaJsonObj: JSONObject = JSONObject();

jsonObj.put("username",kullaniciAdi);
jsonObj.put("password",sifre);
jsonObj.put("ad",ad)
jsonObj.put("soyad",soyad);
jsonObj.put("dogumTarihi", ProjectUtil.formatDate(cal.time,"yyyy-MM-dd"));
jsonObj.put("eposta",eposta);
jsonObj.put("haberdarmi",haberdarMi);

if(cinsiyet == R.id.erkekRadioButton){
jsonObj.put("cinsiyet","E");
}else{
jsonObj.put("cinsiyet","K");
}

for(s in selectedIlgiAlanlar){
iaJsonObj.put("id",s);
iaArr.put(iaJsonObj);
iaJsonObj = JSONObject();
}

jsonObj.put("ilgiAlanlari",iaArr);

pd.show();

Log.d("JSON",jsonObj.toString());

kullaniciService.kullaniciKaydet(jsonObj.toString()).enqueue(object:
Callback<ResponseStatusModel> {
override fun onFailure(call: Call<ResponseStatusModel>?, t: Throwable) {
SimpleToast.error(applicationContext, resources.getString(R.string.kullaniciKayitHata), "{fa-times-circle}");
pd.dismiss();
}

override fun onResponse(call: Call<ResponseStatusModel>?, response: Response<ResponseStatusModel>) {
if(response.body() != null){
val respModel = response.body() as ResponseStatusModel;
SimpleToast.info(applicationContext, resources.getString(R.string.kullaniciKayitBasarili), "{fa-check}");
pd.dismiss();
ProjectUtil.activityYonlendir(applicationContext,LoginActivity());
}
}
});
}
}

private fun fillIlgiAlanlariListe() {
pd.show();
kullaniciService.getKitapturListe().enqueue(object:
Callback<ArrayList<IlgiAlanlariParametreModel>> {
override fun onFailure(call: Call<ArrayList<IlgiAlanlariParametreModel>>?, t: Throwable) {
SimpleToast.error(applicationContext, resources.getString(R.string.kitapTurListeHata), "{fa-times-circle}");
pd.dismiss();
}

override fun onResponse(call: Call<ArrayList<IlgiAlanlariParametreModel>>?, response: Response<ArrayList<IlgiAlanlariParametreModel>>) {
ilgiAlanlariListe = response.body() as ArrayList<IlgiAlanlariParametreModel>;
ilgiAlanlariListe.add(IlgiAlanlariParametreModel(0,"",ilgiAlanlariListe.get(0).durum,ilgiAlanlariListe.get(0).olusturan,ilgiAlanlariListe.get(0).resim));
val ilgiAlanlariListe2 = ilgiAlanlariListe.sortedWith(compareBy({it.id}));
ilgiAlanlariListe.clear();
for(i in ilgiAlanlariListe2){
ilgiAlanlariListe.add(i);
}
val adapter = IlgiAlanlariSpinnerAdapter(application,ilgiAlanlariListe,null,selectedIlgiAlanlar);
registerIlgiAlanlariSpinner.adapter = adapter;
pd.dismiss();
}
});
}

private fun initDatePicker() {
val cal: Calendar = Calendar.getInstance();
val yil: Int = cal.get(Calendar.YEAR);
val ay: Int = cal.get(Calendar.MONTH);
val gun: Int = cal.get(Calendar.DAY_OF_MONTH);

registerProfilDogumTarih.editText!!.setOnClickListener { it: View ->
var datePicker = DatePickerDialog(it.context, R.style.DatePickerTheme,
DatePickerDialog.OnDateSetListener { datePicker, y, a, g ->
registerProfilDogumTarih.editText!!.setText("$g.${a + 1}.$y");
}, yil, ay - 1, gun
);

datePicker.setTitle(resources.getString(R.string.selectTarih));
datePicker.setButton(
DialogInterface.BUTTON_POSITIVE,
resources.getString(R.string.ayarlaLabel),
datePicker
);
datePicker.setButton(
DialogInterface.BUTTON_NEGATIVE,
resources.getString(R.string.iptalLabel),
datePicker
);

datePicker.show();
}
}
}


Burada ilgi alanları spinner ı yukarıdan da anlaşılacağı üzere fillIlgiAlanlariListe metodu ile başlamaktadır. Şimdi bu metodu yakından inceleyelim.

private fun fillIlgiAlanlariListe() {
pd.show();
kullaniciService.getKitapturListe().enqueue(object:
Callback<ArrayList<IlgiAlanlariParametreModel>> {
override fun onFailure(call: Call<ArrayList<IlgiAlanlariParametreModel>>?, t: Throwable) {
SimpleToast.error(applicationContext, resources.getString(R.string.kitapTurListeHata), "{fa-times-circle}");
pd.dismiss();
}

override fun onResponse(call: Call<ArrayList<IlgiAlanlariParametreModel>>?, response: Response<ArrayList<IlgiAlanlariParametreModel>>) {
ilgiAlanlariListe = response.body() as ArrayList<IlgiAlanlariParametreModel>;
ilgiAlanlariListe.add(IlgiAlanlariParametreModel(0,"",ilgiAlanlariListe.get(0).durum,ilgiAlanlariListe.get(0).olusturan,ilgiAlanlariListe.get(0).resim));
val ilgiAlanlariListe2 = ilgiAlanlariListe.sortedWith(compareBy({it.id}));
ilgiAlanlariListe.clear();
for(i in ilgiAlanlariListe2){
ilgiAlanlariListe.add(i);
}
val adapter = IlgiAlanlariSpinnerAdapter(application,ilgiAlanlariListe,null,selectedIlgiAlanlar);
registerIlgiAlanlariSpinner.adapter = adapter;
pd.dismiss();
}
});
}


kullaniciService 'den bir önceki yazımda bahsetmiştim. Bu servisteki getKitapturListe metodu bize kitap türlerini yani ilgi alanlarını dönüyor. Bu servis başarılı bir sonuç dönünce kitap türlerine aşağıda ki eleman ekleniyor.

    ilgiAlanlariListe.add(IlgiAlanlariParametreModel(0,"",ilgiAlanlariListe.get(0).durum,ilgiAlanlariListe.get(0).olusturan,ilgiAlanlariListe.get(0).resim));

Bu elemanın eklenme amacı defaultta seçiniz gibi boş bir eleman gelmesini istememden kaynaklıdır.

Daha sonra liste id ye göre sort edilip custom layout un uygulanacağı adapter çağırılır ve spinner'a setlenir. Android adapterlar hakkında buradan bilgi alabilirsiniz. Adapterımız aşağıda ki gibi olacaktır.

IlgiAlanlariSpinnerAdapter.kt

class IlgiAlanlariSpinnerAdapter(val context:Context,var ilgiAlanListe:ArrayList<IlgiAlanlariParametreModel>,var kisiIlgiAlanIdListe:ArrayList<Int>?,var selectedIlgiAlanIdListe:ArrayList<Int>):BaseAdapter() {

private val inflater: LayoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater;

override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val view: View;
if (convertView == null) {
view = inflater.inflate(R.layout.ilgi_alanlari_spinner, parent, false);
} else {
view = convertView;
}

val cb:CheckBox = view.findViewById(R.id.ilgiAlanCheckBox) as CheckBox;
val ia:IlgiAlanlariParametreModel = getItem(position) as IlgiAlanlariParametreModel;
val tw:TextView = view.findViewById(R.id.tvIlgiAlanCombo) as TextView;

if(ia.id == 0){
tw.visibility = View.VISIBLE;
cb.visibility = View.GONE;
}else{
cb.visibility = View.VISIBLE;
cb.setText(ia.aciklama);
tw.visibility = View.GONE;
}


if(kisiIlgiAlanIdListe != null && kisiIlgiAlanIdListe!!.size>0){
for (i in kisiIlgiAlanIdListe!!){
if(controlIsListedeMevcut(i,ia)){
cb.isChecked = true;
selectedIlgiAlanIdListe.remove(ia.id);
selectedIlgiAlanIdListe.add(ia.id);
}
}
}

cb.setOnCheckedChangeListener { compoundButton, b ->
if(b && ia.id>0){
selectedIlgiAlanIdListe.add(ia.id);
}else{
selectedIlgiAlanIdListe.remove(ia.id);
if(kisiIlgiAlanIdListe != null){
val res = kisiIlgiAlanIdListe!!.find { id -> id == ia.id};
if(res != null && res>0){
val ia: StringBuilder = StringBuilder();
kisiIlgiAlanIdListe!!.remove(res);
ProjectUtil.removeFromSharedPreferences(context,ProjectUtil.SHARED_PREF_FILE,"ilgialanlar");
for(i in kisiIlgiAlanIdListe!!){
ia.append(res).append(",");
}
ProjectUtil.putStringDataToSharedPreferences(context,ProjectUtil.SHARED_PREF_FILE,"ilgialanlar",ia.toString());
}
}

}
}

return view;
}

override fun getItem(position: Int): Any {
return ilgiAlanListe.get(position);
}

override fun getItemId(position: Int): Long {
return position.toLong();
}

override fun getCount(): Int {
return ilgiAlanListe.size;
}

private fun controlIsListedeMevcut(id:Int,ia:IlgiAlanlariParametreModel):Boolean{
if(ia.id == id){
return true;
}
return false;
}
}


Bu adapter da kullandığımız ilgi_alanlari_spinner layoutu aşağıda ki gibidir.

ilgi_alanlari_spinner.xml

<?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="wrap_content">

<CheckBox
android:id="@+id/ilgiAlanCheckBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="40dp"
android:theme="@style/KutSisCheckBox"
android:buttonTint="@color/colorAccent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvIlgiAlanCombo" />

<TextView
android:id="@+id/tvIlgiAlanCombo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/ilgiAlanCombo"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Adapter  spinnera setlendiği zaman aşağıda ki gibi görünüm elde edilecektir.


Bu adaptera geçilen kisiIlgiAlanIdListe parametresinin null geçilmesinin sebebi kişinin daha yeni kayıt olduğu için henüz ilgi alanlarının olmayışından kaynaklanmaktadır. Bu parametre profil güncelleme ekranında dolu olarak gönderilecektir.

Ekranda ki validasyonlar tıpkı login ekranında yapıldığı gibi yapılmaktadır. Tüm bilgiler doldurulduktan sonra kaydet butonuna basılır ve servisin başarılı dönmesi durumunda yani sisteme kayıt işleminin başarıyla gerçekleştiği durumda bilgi mesajı verilip kişi giriş ekranına yönlendirilir.

Soru,istek ve önerilerinizi mesutemrecelenk@gmail.com adresine yazabilirsiniz. Bir sonraki yazıda görüşmek üzere hoşça kalın...

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...