Retrofit ile Android Sözlük Uygulaması (Verilerin Sunucudan Alınması)

Merhaba değerli dostlar, GSON ile Android Sözlük Uygulaması isimli yazımda Android uygulamasına dahili olarak eklenen JSON uzantılı bir dosyadan JSON verilerinin nasıl alınacağından bahsetmiştim. Eğer internet ortamında bir sunucunuz bulunmuyorsa ve en az masrafla bir uygulama geliştirmek istiyorsanız bu yazı tam size göre.

Peki verileri sunucu ortamında olan geliştiriciler verilerini nasıl alabilir? İsterseniz GSON ve OkHttp kütüphanelerini birlikte kullanarak verilerinizi sunucu ortamından alabilirsiniz veya bu yazıda göstereceğim gibi Retrofit kütüphanesini kullanabilirsiniz. Uygulamaya geçmeden önce Retrofit ile ilgili bazı bilgiler vermek istiyorum.

UYARI: Burada verilen bilgiler Android Wear ve İleri Android Uygulamaları isimli kitabımdan alınmıştır. Daha fazla bilgi için bu kitabı tavsiye ediyorum. Bu kitap içerisinde Retrofit, OkHttp, Google Gson gibi ağ işlemlerinde sizlere yardımcı olacak ağ kütüphaneleri; Picasso, Image Loader ve Fresco gibi ön belleğe alma kütüphaneleri ve veritabanı kütüphaneleri gibi 3.parti kütüphaneler hakkında ayrıntılı bilgi alabilirsiniz. Özetle kitap tam olarak bir kütüphane cennetidir.

Retroft, resmi web sitesinde “A type-safe REST client for Android and Java.” olarak tanımlanmıştır. Yani Android ve Java için güvenlikli bir REST istemcisidir.

Retrofit Nedir?

Retroft bir ağ kütüphanesidir. Temel amacı ağ üzerinden JSON verilerini sorunsuz bir şekilde alınmasını sağlamaktır. JSON verilerini almakla birlikte, bir uygulama ile sunucu arasındaki tüm alışveriş işlemlerini yapabilir (Sunucudan veri almak veya sunucuya veri göndermek vb.). Volley kütüphanesi gibi olmasına rağmen, hız olarak daha iyi bir seviyededir. Bu kütüphane ile JSON verilerini ayrıştırmak için herhangi bir JSON ayrıştırıcıya ihtiyaç yoktur. Web servisleri için gereken tüm özelliklere sahip olan bir kütüphanedir. Bu kütüphane ile çalışırken verileri internet üzerinden almak için AsyncTask, HtpUrlConnecton veya JsonParser gibi hantal yapılara ihtiyacımız olmayacak. Uygulama geliştirirken Retroft için üç sınıfa ihtiyacımız olacak;

1) Model Sınıf (POJO – Plain Old Java Object veya Data Class): Sunucu tarafından döndürülen JSON verilerini işlemek için kullanılır. Bu sınıflar içinde JSON ile gelen verileri tutacak değişkenler bulunur.

2) Interface (Arayüz): Bildiğiniz gibi, Interface yapıları, sadece metot isimlerinin bulunduğu yapılardır. Burada tanımlanan tüm metotlar soyuttur. HTTP işlemlerinin tanımlandığı sınıftır. GET ve POST metotları ile çalışmak için arayüzler kullanılacaktır.

3) Retroft.Builder: Bu sınıf ile istekte bulunacağımız sunucu adresi belirtilir.

Retrofit Çalışma Mantığını Kavramak

Retrofit kütüphanesinin çalışma mantığı aşağıdaki gibidir.

Retrofit, http istekleri için OkHttp kütüphanesi kullanmaktadır. Android uygulama üzerinden, sunucuya gönderilen Request yani istekler ile sunucudan talepte bulunuruz. Sunucu bu istek karşısında, Response yani yanıt olarak bir JSON verisi gönderir. Her iki işlem için yani istek ve yanıt için Retrofit kütüphanesini kullanırız.  Gelen JSON verisi, Model yani POJO sınıfı ile işlenir.

Bu kısa bilgilendirmeden sonra geliştireceğimiz proje için takip edeceğimiz adımları tek tek belirtebiliriz.

  • Yeni bir Android projesi oluşturmak
  • Retrofit 2 kütüphanesini projeye eklemek.
  • AndroidManifest dosyasına INTERNET erişim izni eklemek.
  • Word isminde bir Data Class oluşturmak. Bu model sınıfımızdır.
  • Interface yapılarını oluşturmak
  • Uygulamanın activity_main.xml arayüz dosyasını hazırlamak. Yani arayüzünü tasarlamak.
  • Uygulamanın MainActivity.kt sınıfı için ihtiyacımız olan kodları yazmak.

Yeni bir proje oluşturmada bilgi sahibi olmak için listeden işaretli linke tıklamanız yeterlidir. Android projemizi oluşturduktan sonra sonraki adıma geçebiliriz.

Retrofit 2 Kütüphanesini Projeye Eklemek

Retrofit ile çalışmadan önce yapmamız gereken temel işlemlerden ilki uygulamaya bu kütüphaneyi eklemektir. Bu işlem diğer tüm kütüphanelerde olduğu gibi burada da aynıdır. Yapmanız gereken işlemler şu şekildedir.
Öncelikle uygulamanızın aşağıdaki yoluna sahip olan dosyasını açalım.

build.gradle (Module: app)

İşaretli dosyayı açınız ve aşağıdaki satırı ekleyiniz.

dependencies {
    ...
    implementation 'com.squareup.retrofit2:retrofit:2.6.2'
    implementation 'com.squareup.retrofit2:converter-gson:2.6.2'
}

Dikkat edilirse Retrofit ile birlikte Gson kütüphanesini de projeye ekledik. Ancak Gson ile ilgili tüm işlemleri Retrofit kütüphanesi bizim yerimize otomatik olarak gerçekleştirecek. Yani bu projede Gson kütüphanesi ile JSON parser işlemi arkaplanda yapılacaktır.

AndroidManifest Dosyasına INTERNET Erişim İzni Eklemek.

Aşağıda gösterilen AndroidManifest dosyasını açınız.

Dosyayı açtıktan sonra aşağıda verilen satırı gösterilen yere ekleyiniz.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.mas.retrofitapp">

    <!--İzini buraya eklenecek -->
    <uses-permission android:name="android.permission.INTERNET" />

    <application..>
    </application>

</manifest>

Bu işlemden sonra sunucudan veri almak için ihtiyacımız olan izni eklemiş oluruz.

Word İsimli Model Sınıfını Oluşturmak

POJO veya Model sınıfları, Retrofit ile çalışırken özellikle dikkat edilmesi gereken sınıflardır. Model sınıf ile JSON verisi arasında ciddi bir bağlantının sağlanması çok önemlidir. Bu uygulamada kullanacağımız JSON verisi aşağıdaki gibidir.

[
  {
    "id": 1,
    "word": "author",
    "meaning": "yazar"
  },
  {
    "id": 2,
    "word": "decision",
    "meaning": "karar, kararlılık"
  },
  {
    "id": 3,
    "word": "blackboard",
    "meaning": "kara tahta"
  },
  {
    "id": 4,
    "word": "wave",
    "meaning": "el sallamak, dalgalanmak"
  },
  {
    "id": 5,
    "word": "vital",
    "meaning": "önemli, enerji dolu, yaşamsal"
  },
  {
    "id": 6,
    "word": "shoot",
    "meaning": "ateş etmek, gol atmak, fotoğraf çekmek"
  },
  {
    "id": 7,
    "word": "importance",
    "meaning": "önem"
  },
  {
    "id": 8,
    "word": "target",
    "meaning": "hedef, amaç"
  },
  {
    "id": 9,
    "word": "tremendous",
    "meaning": "çok büyük"
  },
  {
    "id": 10,
    "word": "sunbathe",
    "meaning": "güneşlenmek"
  },
  {
    "id": 11,
    "word": "opinion",
    "meaning": "fikir, yargı"
  },
  {
    "id": 12,
    "word": "beach",
    "meaning": "plaj"
  },
  {
    "id": 13,
    "word": "union",
    "meaning": "birlik, sendika"
  },
  {
    "id": 14,
    "word": "solar",
    "meaning": "güneş"
  }
]

Bu veri şu anda benim sunucumda yüklü. Merak etmeyin sizin için bu linki paylaşacağım. Bunu özellikle sunucusu olmayan arkadaşların bu uygulamayı test edebilmeleri için yapıyorum. JSON verilerinin linki aşağıdaki gibidir.

https://memetalisicak.com/word_list.php

JSON dosyamız hazır ise şimdi bu dosyada bulunan her bir JSON verisini Java nesnelerine dönüştürmemizi sağlayacak bir Data Class oluşturalım. Burası çok önemlidir. Yapacağınız bir hata uygulamanızın yanlış çalışmasına neden olur. Uygulamada kullanacağımız Word sınıfımızın kodları aşağıdaki gibidir.

package com.mas.retrofitapp

data class Word(val id:Int,
                val word:String,
                val meaning:String)

Şimdi gelelim dikkat etmeniz gereken konulara. Bu sınıfta kullandığımız değişken isimleri (id, word, meaning), JSON uzantılı dosyadaki değerler ile birebir aynıdır. Eğer buna dikkat etmezseniz uygulamanız hata vereceği gibi bazı verilere erişim de sağlanmayabilir.

Ayrıca veri tiplerinin de aynı olduğundan mutlaka emin olalım.

Interface Yapılarını Oluşturmak

Oluşturmamız gereken bir diğer sınıfmız da arayüzlerdir. Retrofit ile kullanılan metotları tanımladığımız ve diğer temel metotları oluşturduğumuz sınıflardır.

package com.mas.retrofitapp

import retrofit2.Call
import retrofit2.http.GET

interface WordService {
    @GET("word_list.php")
    fun getList(): Call<List<Word>>
}

@GET: Bu metot, URL bilgisi verilen sunucudan veri almak için kullanılır. Retrofit içinde önceden tanımlanan bir yapıdır. Bizim yaptığımız ise, bu yapıya erişmektir. Bu kısma URL bilgisinin sadece son kısmı eklenecektir. Bu uygulama için son kısım “word_list.php” dir.

getList(): Kelime bilgilerini almak için bu metodu kullanacağız.
Word ise, sunucudan gelen veriyi saklamak için kullanılacak olan yapıdır.Bu arayüz ile json array verisi (yani birden çok veri) alacağımız için, Call<List> tipinde bir metot oluşturduk.

activity_main.xml

Arayüz için birçok düzenleme yaptım. Özellikle values klasöründe bulunan styles ve colors gibi dosyalarda birçok düzenleme mevcuttur. Hepsini burada paylaşmak mümkün değil. O yüzden sadece uygulama arayüzünü göstermek istiyorum.

Uygulamanın en tepesinde bir adet TextView (toplam kelime sayısını), onun altında İngilizce kelime ve anlamı için birer adet Textview, bu iki kontrolün arasında kelimenin okunuşunu dinlemenizi sağlayan bir tane ImageView ve sayfanın en altında o anki kelimenin sırasını gösteren bir adet TextView kontrolü bulunmaktadır.

MainActivity.kt

Uygulamanın kalbi bu sınıftır. Buraya kadar anlattığımız bütün yapılar bu sınıfı içinde aktif bir şekilde kullanılmaktadır. Tüm kodları burada vermeden önemli olan kısımları sizlere kısaca aktarmak istiyorum. Öncelikle MainActivity sınıfına Callback<List<Word>> arayüzünü implement edelim ve bu arayüz ile ilgili metotları aşağıdaki gibi override edelim. 

package com.mas.retrofitapp

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.speech.tts.TextToSpeech
import android.widget.TextView
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

class MainActivity : AppCompatActivity(), Callback<List<Word>> {
    val host_name = "https://memetalisicak.com/"
    lateinit var words: List<Word>
    lateinit var word: Word
    var tvTotal: TextView? = null
    var tvWord: TextView? = null
    var tvMeaning: TextView? = null
    var tvCurrent: TextView? = null
    var oldTouchValue: Float = 0.0F
    var currentX: Float = 0.0F
    var i = 0
    lateinit var textToSpeech: TextToSpeech
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
    }

    override fun onResponse(call: Call<List<Word>>?, response: Response<List<Word>>?) {
        
    }

    override fun onFailure(call: Call<List<Word>>?, t: Throwable?) {
       
    }
}

Arayüze ait metotlar yukarıda gösterilen onResponse() ve onFailure() metotlarıdır. Ayrıca bağlantı kuracağımız sunucu adresini de host_name isimli değişkene atadık. Diğer değişkenleri de yine bu uygulama içinde kullanacağız. Şimdi uygulamada kullanacağımız tüm metotları buraya ekleyelim. Daha sonra önemli olan metotların görevlerini tek tek açıklayalım.

package com.mas.retrofitapp

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.speech.tts.TextToSpeech
import android.view.MotionEvent
import android.view.View
import android.widget.TextView
import android.widget.Toast
import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.*
import java.util.concurrent.TimeUnit

class MainActivity : AppCompatActivity(), Callback<List<Word>> {
    val host_name = "https://memetalisicak.com/"
    lateinit var words: List<Word>
    lateinit var word: Word
    var tvTotal: TextView? = null
    var tvWord: TextView? = null
    var tvMeaning: TextView? = null
    var tvCurrent: TextView? = null
    var oldTouchValue: Float = 0.0F
    var currentX: Float = 0.0F
    var i = 0
    lateinit var textToSpeech: TextToSpeech
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
    }

    private fun getView() {
        
    }

    private fun getWords() {
        
    }

    override fun onResponse(call: Call<List<Word>>?, response: Response<List<Word>>?) {
        
    }

    override fun onFailure(call: Call<List<Word>>?, t: Throwable?) {
        
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        
    }

    private fun showWord() {
        

    }

    public fun speech(v: View) {
        
    }
}

Kullanacağımız tüm metotları yukarıdaki gibidir. Öncelikle Activity sınıfı ile bizlere sunulan onCreate() metodunun içeriğini paylaşalım.

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        getView()
        textToSpeech = TextToSpeech(applicationContext, TextToSpeech.OnInitListener { s ->
            if (s != TextToSpeech.ERROR) {
                textToSpeech.language = Locale.US
            }
        })
        getWords()
    }

getView() metodu arayüzde bulunan kontrollere erişimi sağlamak için kullanılmaktadır. textToSpeech değişkeni ile kelimeleri seslendirmek için gerekli ayarları yaptık. Özellikle telaffuz dili olarak US yani Amerikan aksanını seçtik. Siz farklı bir aksan seçebilirsiniz. getWords() metodu ise sunucuda bulunan verileri almak için kullanılmaktadır. Bu metodun içeriği aşağıdaki gibidir.

private fun getWords() {
        val gson = GsonBuilder().setLenient().create()
        val client = OkHttpClient.Builder()
            .retryOnConnectionFailure(true)
            .connectTimeout(15, TimeUnit.SECONDS)
            .build()
        val retrofit = Retrofit.Builder()
            .baseUrl(host_name)
            .client(client)
            .addConverterFactory(GsonConverterFactory.create(gson))
            .build()
        val service: WordService = retrofit.create(WordService::class.java)
        val call = service.getList()
        call.enqueue(this)
    }

gson ve client değişkenlerini oluşturmak zorunda değiliz. Ancak olur ya ileride almanız gereken verilerin boyutu artarsa ve sunucu bağlantısında bir sorunda olursa bağlantının 15 saniyede bir yeniden kurulmasını isterseniz bu iki değişken işinize yarayabilir.

Retroft.Builder(): Retrofit nesnesi oluşturmak için kullanılır.
baseUrl(): verilerin alınacağı URL bilgisi burada belirtilir. GsonConverterFactory.create(): JSON verilerinin erişilebilir nesneler haline dönüştürmek için kullanılır.
Gson: Java nesnelerini JSON haline dönüştürmek için kullanılan bir java kütüphanesidir.

service ve call yapılarını sunucudan verileri almak için kullanırız. call ile kullanılan enqueue metodu ise verilerin asenkron olarak sunucudan alınması sağlar. Bu kodlar icra edildikten sonra sonuçları aşağıda verilen iki metot ile alabiliriz.

override fun onResponse(call: Call<List<Word>>?, response: Response<List<Word>>?) {
        words = response!!.body()!!
        tvTotal!!.text = (words.size).toString()
        tvTotal!!.text = "" + words.size
        showWord()
    }

    override fun onFailure(call: Call<List<Word>>?, t: Throwable?) {
        Toast.makeText(applicationContext, "Hata", Toast.LENGTH_SHORT).show()
    }

onResponse(): Sunucudan bir yanıt geldiğinde bu metot otomatik olarak
çağrılır. Alınan verilere response ismi ile erişim sağlayabiliriz. Bunun için response.body() ifadesini kullanmanız yeterlidir.
onFailure(): Sunucudan bir yanıt alınmadığı zaman bu metot otomatik olarak çağrılır. Özellikle bir hata meydana geldiği zaman bu metot ile hatanın ne olduğunu öğrenebiliriz. showWord() metodu ile alınan verileri arayüzde bulunan kontrollerde gösterebiliriz.

private fun showWord() {
        word = words!!.get(i)
        textToSpeech.speak(word!!.word, TextToSpeech.QUEUE_FLUSH, null)
        tvWord!!.text = word!!.word
        tvMeaning!!.text = word!!.meaning
        tvCurrent!!.text = "" + word!!.id

    }   

textToSpeech.speak() ile alınan İngilizce kelimenin okunuşu anında seslendirilir. Eğer kendiniz daha sonra tekrar dinlemek isterseniz arayüzde bulunan ImageView kontrolüne tıklayabilirsiniz. Bu kontrolün onClick özelliğine speech değerini verdik. Bu değer MainActivity sınıfında bulunan aşağıdaki metodun çağrılmasını sağlar. Bu şekilde kelimenin okunuşunu bir kez daha dinleyebilirsiniz.

public fun speech(v: View) {
        textToSpeech.speak(word!!.word, TextToSpeech.QUEUE_FLUSH, null)
    }

Son olarak uygulamaya dokunmatik özelliği kazandırmak için aşağıdaki kodları yazmamız gerekiyor.

 override fun onTouchEvent(event: MotionEvent?): Boolean {
        when (event!!.action) {
            MotionEvent.ACTION_DOWN -> oldTouchValue = event.getX()
            MotionEvent.ACTION_UP -> {
                currentX = event.getX()
                if (oldTouchValue!! < currentX!!) {
                    i--
                    if (i < 0) i = words!!.size - 1
                } else {
                    i++
                    if (i >= words!!.size) i = 0
                }
            }
        }
        showWord()
        return super.onTouchEvent(event)
    }

onTouchEvent() ile uygulama arayüzüne dokunduğunuz zaman uygulamanızın tepki vermesini sağlayabilirsiniz. ACTION_DOWN ile ekrana ilk dokunduğunuz yerin X koordinatı verisi alınır, ACTION_UP ile parmağınızı kaldırdığınız yerin X koordinatı alınır. Eğer ilk X değeri ikinci X değerinden küçükse sonraki kelime, aksi durumda önceki kelime gösterilir. Diyelim sola doğru sürekli hareket ettiniz ve ilk kelimeye döndünüz. Eğer yine sola doğru hareket ederseniz listedeki son kelime size gösterilir. Eğer sürekli olarak sağa doğru sürüklerseniz ve en son kelimeye geldikten sonra aynı harekete devam ederseniz bu kez listedeki ilk kelime size gösterilir.

Uygulamanın nasıl çalıştığını videodan inceleyebilirsiniz.

Uygulama ile ilgili bilmeniz gerekenleri sizlere aktarmaya çalıştım. Unutmayın! Burada anlatılanların verimli olması için uygulamayı bilgisayarınıza indirip deneme yanılma yaparak anlamaya çalışmanız size daha faydalı olacaktır. Ayrıca projeyi oluşturup kodlara bakmadan kendi başınıza yeniden yazmak konuyu anlamanızı hızlandıracaktır. Unutmayın köfte kokmadan aşçı olunmaz. Şimdilik bu kadar. Daha sonra görüşmek üzere sağlıcakla kalın!

Uygulama kodlarına erişmek için buraya tıklayınız.