Android Retrofit İle İnternetten Veri Çekelim (API)

Ego1st

Uzman üye
15 Mar 2018
1,109
25
Merhaba Sayın THT üyeleri, bu gün Retrofit ve RxJava kullanarak internetten nasıl veri çekebiliriz onu göreceğiz. Bunu yaparken MVVM yapısını kullanmayı ihmal etmeyeceğiz.

xRDuEc.png


Diyelim ki bir API’mız var ve bizim bu API’daki verileri almamız gerekiyor. Bu örnek için https://ctftime.org/api/v1/events/?limit=50&start=1600470626&finish=1611011426 'yi kullanacağız.

Şimdi Android Studio’da yeni bir proje oluşturalım.



xRDuEc.png


MVVM yapısını kullanmak ve en iyi şekilde yararlanmak için bir tane fragment oluşturalım



xRDuEc.png


Daha sonra oluşturduğumuz Fragmentı MainActivity’e ekliyoruz. Şimdi verileri çekerken kullanacağımız Retrofit ve RxJava’yı import edelim. build.gradle dosyamız bu şekilde olmalı.


Kod:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 29
    buildToolsVersion "30.0.2"

    defaultConfig {
        applicationId "com.ego1st.apideneme"
        minSdkVersion 23
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
}

def retrofitVersion = '2.9.0'
def rxJavaVersion = '2.1.1'

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.1'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

    implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
    implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
    implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion"

    implementation "io.reactivex.rxjava2:rxjava:$rxJavaVersion"
    implementation "io.reactivex.rxjava2:rxandroid:$rxJavaVersion"
}

xRDuEc.png


Şimdi kodlarımızı yazmaya başlayabiliriz, ilk olarak Modelimizi yazalım. CTFModel adlı bir sınıf oluşturalım ve bu sınıfı aşağıda da görüldüğü gibi bir data class'a çevirelim.


Kod:
package com.ego1st.apideneme

data class CTFModel (
    val url: String?,

    val hours: String?,
    val days: String?,

    val start: String?,
    val finish: String?,

    val title: String?,
    val description: String?,
    val name: String?
)

xRDuEc.png


Gördüğünüz gibi API'de bizle paylaşılan propertylerin isimlerini aynen girerek onları belirtiyoruz.

Şimdi API bağlantısı için bir Interface oluşturalım, ismine CTFApi diyorum.

xRDuEc.png


Kod:
package com.ego1st.apideneme

import io.reactivex.Single
import retrofit2.http.GET

interface CTFApi {

    //https://ctftime.org/api/v1/events/?limit=50&start=1600470626&finish=1611011426

     [USER=675520]Get[/USER]("events/?limit=50&start=1600470626&finish=1611011426")
    fun getCtfEvents(): Single<List<CTFModel>>
}

Gördüğünüz gibi API urlmizin BASE dediğimiz baş kısmını yazmadık, şimdi oluşturacağımız CTFService'de de orayı halledeceğiz. CTFService isimli bir sınıf oluşturalım

xRDuEc.png


Kod:
package com.ego1st.apideneme

import io.reactivex.Single
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory

class CTFService {

    private val BASE_URL = "https://ctftime.org/api/v1/"

    private val api = Retrofit.Builder()

        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .build()
        .create(CTFApi::class.java)

    fun getCtfEventData() : Single<List<CTFModel>> {
        return api.getCtfEvents()
    }
}

Gördüğünüz gibi burda Retrofit Builder ile CallAdapter ve Converterımızı belirtiyoruz.

xRDuEc.png


Şimdi RecyclerView ile verilerimizin sıralı bir şekilde gelmesini sağlayalım. Recycleri View'ı oluşturalım;



Daha sonra alacağımız veriler için şu layoutu kullanalım;


Kod:
<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:layout_weight="1">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:orientation="horizontal">

                <TextView
                    android:id="@+id/event_title"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:fontFamily="sans-serif-light"
                    android:padding="10dp"
                    android:paddingStart="10dp"
                    android:paddingBottom="10dp"
                    android:text="Title"
                    android:textColor="@color/colorPrimary"
                    android:textSize="14sp"
                    android:textStyle="bold" />

                <TextView
                    android:id="@+id/event_organizer_name"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:fontFamily="sans-serif-light"
                    android:padding="10dp"
                    android:paddingStart="10dp"
                    android:paddingBottom="10dp"
                    android:text="Organizer Name"
                    android:textSize="14sp"
                    android:textStyle="bold" />

            </LinearLayout>

            <TextView
                android:id="@+id/event_description"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:fontFamily="sans-serif-light"
                android:padding="10dp"
                android:paddingStart="10dp"
                android:paddingTop="10dp"
                android:text="Description"
                android:textColor="#02D2D2"
                android:textSize="14sp" />

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:visibility="gone">

                <TextView
                    android:id="@+id/event_start"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:fontFamily="sans-serif-light"
                    android:padding="10dp"
                    android:paddingStart="10dp"
                    android:paddingBottom="10dp"
                    android:text="Start"
                    android:textSize="14sp"
                    android:textStyle="bold" />

                <TextView
                    android:id="@+id/event_finish"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:fontFamily="sans-serif-light"
                    android:padding="10dp"
                    android:paddingStart="10dp"
                    android:paddingBottom="10dp"
                    android:text="Finish"
                    android:textSize="14sp"
                    android:textStyle="bold" />

            </LinearLayout>

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:visibility="gone">

                <TextView
                    android:id="@+id/event_hours"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:fontFamily="sans-serif-light"
                    android:padding="10dp"
                    android:paddingStart="10dp"
                    android:paddingBottom="10dp"
                    android:text="Hours"
                    android:textSize="14sp"
                    android:textStyle="bold" />

                <TextView
                    android:id="@+id/event_days"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:fontFamily="sans-serif-light"
                    android:padding="10dp"
                    android:paddingStart="10dp"
                    android:paddingBottom="10dp"
                    android:text="Days"
                    android:textSize="14sp"
                    android:textStyle="bold" />

            </LinearLayout>

            <TextView
                android:id="@+id/event_url"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:autoLink="web"
                android:fontFamily="sans-serif-light"
                android:padding="10dp"
                android:paddingStart="10dp"
                android:paddingBottom="10dp"
                android:text="url"
                android:textSize="14sp"
                android:textStyle="bold"
                android:visibility="gone" />

            <ImageView
                android:id="@+id/imageView4"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                app:srcCompat=  [USER=252003]draw[/USER]able/line" />

        </LinearLayout>
    </LinearLayout>
</layout>

Buradaki drawable/line sizde çıkmayacaktır. Her bir sırayı birbirinden ayırmak için bir çizgi çizdim ve onu res -> drawable'ın içine attım.

xRDuEc.png


Layout ve API & Service & Model işlemlerimiz bittiğine göre şimdi Bir Adapter yazıp verileri RecyclerView'a aktaralım.

CTFAdapter isimli bir sınıf oluşturalım;


Kod:
package com.ego1st.apideneme

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.ctf_row.view.*

class CTFAdapter (private val ctfEventList : ArrayList<CTFModel>) : RecyclerView.Adapter<CTFAdapter.CtfEventHolder> () {
    class CtfEventHolder(val view: View) : RecyclerView.ViewHolder(view)
    private var sit = false

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CtfEventHolder {
        val inflater = LayoutInflater.from(parent.context)
        val view = inflater.inflate(R.layout.ctf_row, parent, false)
        return CtfEventHolder(view)
    }

    override fun onBindViewHolder(holder: CtfEventHolder, position: Int) {
        holder.view.event_title.text = ctfEventList[position].title
        holder.view.event_organizer_name.text = ctfEventList[position].name
        holder.view.event_description.text = ctfEventList[position].description

        holder.view.setOnClickListener {
            if(!sit) {
                holder.view.event_start.visibility = View.VISIBLE
                holder.view.event_finish.visibility = View.VISIBLE
                holder.view.event_hours.visibility = View.VISIBLE
                holder.view.event_days.visibility = View.VISIBLE
                holder.view.event_url.visibility = View.VISIBLE
                sit = true
            } else if(sit) {
                holder.view.event_start.visibility = View.GONE
                holder.view.event_finish.visibility = View.GONE
                holder.view.event_hours.visibility = View.GONE
                holder.view.event_days.visibility = View.GONE
                holder.view.event_url.visibility = View.GONE
                sit = false
            }
        }

        holder.view.event_start.text = ctfEventList[position].start
        holder.view.event_finish.text = ctfEventList[position].finish
        holder.view.event_hours.text = ctfEventList[position].hours
        holder.view.event_days.text = ctfEventList[position].days
        holder.view.event_url.text = ctfEventList[position].url
    }

    override fun getItemCount(): Int {
        return ctfEventList.size
    }

    fun updateCtfEventList(newCtfEventList: List<CTFModel>){
        ctfEventList.clear()
        ctfEventList.addAll(newCtfEventList)
        notifyDataSetChanged()
    }
}


Buradaki override edilen fonksyionları tek tek açıklayalım.

getItemCount = Bu fonksiyon RecyclerView'da kaç tane item yani row olacağını söyler burada CTF Listesinin büyüklüğünü verdik.

updateCtfEventList = Bu fonksiyon bütün listeyi temizleyip yeniden alır.

onBindViewHolder = Gelen verileri RecyclerView'daki alanlarla eşitler. Verileri bağladığımız kısım budur.

onCreateViewHolder = ViewHolder yani RecyclerView'ı tutan Holder başladığında ne olacağını söyler, buradaki örnekte görüntüyü eşitliyoruz.

xRDuEc.png


Canlı olarak verilerin takibini yapabilmek için ViewModel da ekliyoruz


CtfViewModel isimli bir sınıf oluşturalım;

Kod:
package com.ego1st.apideneme

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.observers.DisposableSingleObserver
import io.reactivex.schedulers.Schedulers

class CtfViewModel(): ViewModel {

    private val ctfService = CTFService()
    private val diposable = CompositeDisposable()

    val ctfEvents = MutableLiveData<List<CTFModel>>()
    val ctfError = MutableLiveData<Boolean>()
    val ctfLoading = MutableLiveData<Boolean>()

    fun *******Data(){
        getDataFromAPI()
    }

    private fun getDataFromAPI(){
        ctfLoading.value = true

        diposable.add(
            ctfService.getCtfEventData()
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeWith(object : DisposableSingleObserver<List<CTFModel>>() {
                    override fun onSuccess(t: List<CTFModel>) {
                        ctfEvents.value = t
                        ctfError.value = false
                        ctfLoading.value = false
                    }

                    override fun onError(e: Throwable) {
                        ctfError.value = true
                        ctfLoading.value = false
                        println(e)
                    }

                }

                )
        )
    }

    override fun onCleared() {
        super.onCleared()

        diposable.clear()
    }
}

Canlı veri tutmanın olayı asıl burada başlıyor. Gördüğünüz gibi Events, Error ve Loading isimli üç durumumuz var. Veriler çekilme kullanıcı istek attığı an Loading true oluyor. onSuccess yani başarılı olduğunda diğerleri false bu sefer events true oluyor. onError'da ise error true oluyor. Bunlar her daim canlı tutabilmemiz için çok önemli ve kullanışlılar.

onCleared() metodu ise disposable yani kullan atılabilir olarak belirttiğimiz işlemi temizliyor ve sonlandırıyor.

xRDuEc.png


Fragment'ımızın içi biraz daha karışık ona isterseniz tane tane bakalım.

Öncelikle fragment layoutumuzun içine bunları yazıyoruz.


Kod:
<?xml version="1.0" encoding="utf-8"?>

<layout 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">

    <androidx.swipe*******layout.widget.Swipe*******Layout
        android:id="@+id/swipe*******Ctf"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ShowFragment">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/ctfEventList"
                android:layout_width="0dp"
                android:layout_height="0dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <TextView
                android:id="@+id/errorTextCtf"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Hata! Tekrar dene"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                android:visibility="invisible"/>

            <ProgressBar
                android:id="@+id/mainProgressBarCtf"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                android:visibility="invisible"/>

        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.swipe*******layout.widget.Swipe*******Layout>

</layout>

Gördüğünüz gibi layoutumuzu Swipe ******* Layout yani üstten çekince yenileyen layout olarak değiştirdik ve ek olarak bir recycler view bir textview bir de progress bar ekledik
RecyclerView veriler gelirse gösterilecek, TextView hata olursa gösterilecek, ProgressBar ise yüklenirken gösterilecek

xRDuEc.png


Şimdi fragmentımızı yazmaya başlayabiliriz ama fragment biraz karışık o yüzden açıklayarak ve part part gideceğim.


Kod:
class ShowFragment : Fragment() {

    private lateinit var ctfViewModel: CtfViewModel
    private val ctfEventAdapter = CTFAdapter(arrayListOf())

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        ctfViewModel = ViewModelProviders.of(this).get(CtfViewModel::class.java)

        return inflater.inflate(R.layout.fragment_show, container, false)
    }

Az önce kodladığımı CTF View Model'i tanımladık ve onCreateView() altında layoutumuzu eşitledik.

xRDuEc.png


Kod:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        ctfViewModel.*******Data()

        ctfEventList.layoutManager = LinearLayoutManager(context)
        ctfEventList.adapter = ctfEventAdapter

        swipe*******Ctf.setOn*******Listener {

            ctfEventList.visibility = View.GONE
            errorTextCtf.visibility = View.GONE
            mainProgressBarCtf.visibility = View.VISIBLE

            ctfViewModel.*******Data()

            swipe*******Ctf.is*******ing = false
        }

        observeLiveData()
    }


onViewCreated altında öncelikle verilerimizi yeniledik daha sonra layoutumuzun LinearLayoutManager olduğunu söyledik adapter'ımızı da ctfEventAdapter olarak tanımladık.
Ve bir setOn*******Listener koyduk. Bunun içinde liste ve hata mesajının gitmesini progress barın görünmesini ayarladık ve daha sonra verileri yeniledik. yenileme kodu çalıştıktan sonra layout *******ini false olarak ayarladık.
Listener'ın dışında ise observeLiveData dedik ve canlı olarak verileri gözlemlemeye başladık.

xRDuEc.png


Şimdi observeLiveData'nın kodlarına bakalım


Kod:
private fun observeLiveData() {

        ctfViewModel.ctfEvents.observe(viewLifecycleOwner, Observer {ctfEvents ->

            ctfEvents?.let {
                ctfEventList.visibility = View.VISIBLE
                errorTextCtf.visibility = View.GONE
                mainProgressBarCtf.visibility = View.GONE

                ctfEventAdapter.updateCtfEventList(ctfEvents)
            }
        })

        ctfViewModel.ctfError.observe(viewLifecycleOwner, Observer { e ->


            e?.let {
                if(it){
                    ctfEventList.visibility = View.GONE
                    errorTextCtf.visibility = View.VISIBLE
                    mainProgressBarCtf.visibility = View.GONE
                } else {
                    ctfEventList.visibility = View.VISIBLE
                    errorTextCtf.visibility = View.GONE
                    mainProgressBarCtf.visibility = View.VISIBLE
                }
            }
        })

        ctfViewModel.ctfLoading.observe(viewLifecycleOwner, Observer { loading ->

            loading?.let {

                if(it){
                    ctfEventList.visibility = View.GONE
                    errorTextCtf.visibility = View.GONE
                    mainProgressBarCtf.visibility = View.VISIBLE
                } else {
                    mainProgressBarCtf.visibility = View.GONE
                }
            }
        })
    }

Burada kısaca teker teker hepsine bir gözlemleyeci atayıp veri geldi mi yükleniyor mu ve hata var mı şeklinde kontroller yapıyoruz eğer biri olduysa diğerleriNİ görünmez yapıp güncel olanı görünür hale getiriyoruz.

xRDuEc.png


Ve tabi ki AndroidManifest'in altında internet iznini ekliyoruz


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

    <uses-permission android:name="android.permission.INTERNET"/>
    
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

API'den veri çekerken herhangi bir sorunla karşı karşıya kalırsanız bana her zaman yazabilirsiniz, İyi forumlar :)

Dostlar yeni farkettim ******* yazan yerlerde R efresh yazıyor
 
Son düzenleme:

Mapzilla

Adanmış Üye
23 Eyl 2016
7,205
13
Cevap: Android Retrofit İle İnternetten Veri Çekelim (API) | Ego1st

Kotlin de hiç sevmem
 

'Kyexun

Katılımcı Üye
16 Haz 2020
771
144
Cevap: Android Retrofit İle İnternetten Veri Çekelim (API) | Ego1st

Ellerinize Sağlık :))
 

SiyahYunus

Katılımcı Üye
4 Ocak 2020
560
2
Cevap: Android Retrofit İle İnternetten Veri Çekelim (API) | Ego1st

Eline sağlık
 

M3m0ry

Kıdemli Üye
3 Haz 2017
4,410
124
3
xD
Cevap: Android Retrofit İle İnternetten Veri Çekelim (API) | Ego1st

Ellerine sağlık emek verilmiş :))
 
Üst

Turkhackteam.org internet sitesi 5651 sayılı kanun’un 2. maddesinin 1. fıkrasının m) bendi ile aynı kanunun 5. maddesi kapsamında "Yer Sağlayıcı" konumundadır. İçerikler ön onay olmaksızın tamamen kullanıcılar tarafından oluşturulmaktadır. Turkhackteam.org; Yer sağlayıcı olarak, kullanıcılar tarafından oluşturulan içeriği ya da hukuka aykırı paylaşımı kontrol etmekle ya da araştırmakla yükümlü değildir. Türkhackteam saldırı timleri Türk sitelerine hiçbir zararlı faaliyette bulunmaz. Türkhackteam üyelerinin yaptığı bireysel hack faaliyetlerinden Türkhackteam sorumlu değildir. Sitelerinize Türkhackteam ismi kullanılarak hack faaliyetinde bulunulursa, site-sunucu erişim loglarından bu faaliyeti gerçekleştiren ip adresini tespit edip diğer kanıtlarla birlikte savcılığa suç duyurusunda bulununuz.