Günümüz projelerinde veriler sunucular üzerinde tutulmakta ve mobil uygulamalar bu verilere online olarak API çağrıları üzerinden erişebilmektedir. API çağrılarını daha sistematik ve fonksiyonel biçime getirmek amacıyla Square firması tarafından Retrofit kütüphanesi oluşturulmuştur. Bu kütüphane ile birlikte proje structure yapısı daha kolay anlaşılır bir hale dönüşmüştür. Retrofit e benzer işlevi gören bazı diğer kütüphanelerde bulunmaktadır. Ancak performans, kolay kullanım ve modüler kurgusu sayesinde Retrofit bir kaç adım öndedir.
Örnek Retrofit Projesi Oluşturalım
Yapacağımız örnek projede json formatta response dönen ücretsiz api çağrısından (Space Flgihts APIsi üzerinden) veriyi parse edip RecyclerView componenti kullanarak listelemeye çalışacağız.
1. File => New Project seçeneğine tıklayalım. Sonrasında açılan ekranda Empty Activity seçeneğini seçip Next butonuna tıklayalım.2. Bir sonraki ekranda uygulamanın ismini, paket adını ve kayıt edileceği dizini belirleyelim. İsterseniz uygulamanın adını SampleRetrofit, paket ismi olarak ise info.yazilimdersi.sampleretrofit olarak belirleyelim.
3. Bu işlemler sonrasında projemiz oluşturuldu. Şimdi sırada projemize ait genel klasörleme ve sınıf yapısının nasıl oluşturacağımız konusunda.
4. app dizini altında bulunan build.gradle dosyasını açıp Retrofit, GSON kütüphanelerini ekleyelim.
dependencies { ......... // gson kütüphanesini ekleyelim implementation 'com.google.code.gson:gson:2.8.6' // retrofit kütüphanelerini ekleyelim implementation 'com.squareup.retrofit2:retrofit:2.5.0' implementation 'com.squareup.retrofit2:converter-gson:2.5.0' }5. Projemizde internet üzerinden API çağrısı yapılacağı için AndroidManifest.xml dosyasında INTERNET iznini almamız gerekmektedir kullanıcıdan.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="info.yazilimdersi.sampleretrofit"> <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=".activity.MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
Model Sınıflarını Oluşturalım
Projemizde ücretsiz ve herhangi bir api keye ihtiyacımız olmayan Spaceflight News API servisini kullanacağız. Projenin başında model dizini altında oluşturduğumuz Article, Launch ve Event sınıflarını neden oluşturduğumuza bakalım.
Get Articles linkine tıklağınızda açılan sayfada Try Out butonuna basıp response dönen içeriği inceleyelim.
package info.yazilimdersi.sampleretrofit.model; import com.google.gson.annotations.SerializedName; import java.util.ArrayList; import java.util.List; public class Article { @SerializedName("id") private String id; @SerializedName("title") private String title; @SerializedName("url") private String url; @SerializedName("imageUrl") private String imageUrl; @SerializedName("newsSite") private String newsSite; @SerializedName("summary") private String summary; @SerializedName("publishedAt") private String publishedAt; @SerializedName("updatedAt") private String updatedAt; @SerializedName("featured") private Boolean featured; @SerializedName("launches") private List<Launch> launches = new ArrayList<Launch>(); @SerializedName("events") private List<Event> events = new ArrayList<Event>(); public String getTitle() { return title; } public String getSummary() { return summary; } public List<Launch> getLaunches() { return launches; } public String getImageUrl() { return imageUrl; } }7. Article sınıfı içerisinde tanımladığımız ve makale ile ilişkili herhangi bir event yada launch olup olmadığını belirlemede kullanılan Launch ve Event sınıflarının içerisine aşağıdaki kod parçacıklarını ekleyelim.
package info.yazilimdersi.sampleretrofit.model; import com.google.gson.annotations.SerializedName; public class Launch { @SerializedName("id") private String id; @SerializedName("provider") private String provider; }
package info.yazilimdersi.sampleretrofit.model; import com.google.gson.annotations.SerializedName; public class Event { @SerializedName("id") private String id; @SerializedName("provider") private String provider; }
Retrofit Instance Oluşturalım
Projemizde atılacak olan API requestlerinin ana yapısı olan Retrofit'e ait kurguyu api dizini altında bulunan ApiClient.java dosyası olarak belirledik. Burada Retrofit Builder sınıfını kullanarak API yapısının oluşturulması sağlanacaktır. Ayrıca API ya ait ana linkinde burada tanımlanması gerekmektedir.
import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class ApiClient { public static final String BASE_URL = "https://test.spaceflightnewsapi.net/api/v2/"; private static Retrofit retrofit = null; public static Retrofit getClient() { if (retrofit==null) { retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .build(); } return retrofit; } }
Endpointlerin Belirlenmesi
Endpointler proje süresince kullanılacak olan ve farklı sınıflardan da erişimi kolay olacak şekilde interface yapısına uygun şekilde ayarlanır. Ek olarak, yapılan API çağrılarında dönüş verisi her zaman Call<Article> gibi parametreleştirilmiş bir Call<T> nesnesidir.
import info.yazilimdersi.sampleretrofit.model.Article; import retrofit2.Call; import retrofit2.http.GET; public interface ApiInterface { @GET("articles") Call<List<Article>> getArticles(); }
Böylelikle Retrofit ile yapılan çağrıda https://test.spaceflightnewsapi.net/api/v2/articles linkine GET ile request atılmış olacaktır.
Retrofit kütüphanesinde HTTP requestlerine yönelik (GET, POST, PUT etc.) gibi annotationlar ve (@Query, @Path, @Body etc.) gibi özel annotationlar bulunmaktadır. Endpointin talebine uygun olarak bu tanımlamaları gerçekleştirmeniz gerekecektir. Kısaca specific annotationları özetleyelim;
- @Path: Endpoint linkinde iletilmesi gereken herhangi bir parametre olduğunda kullanılır Örneğin; /article/{id} ile request atılmak istenildiğinde @Path kullanılarak id verisi setlenebilir.
- @Query: Endpointe bağlı olarak iletilecek olan query parametrelerinde kullanılır. Örneğin; /articles?order=id&isfeatured=true tarzı requestleri sağlar.
- @Body: POST ile iletilecek olan body verisinde kullanılır.
- @Header: Atılacak olan requestde headerde iletilmesi gereken herhangi bir bilgi varsa kullanılır.
İlk API Çağrısının Yapılması
MainActivity.java sınıfı içerisinde ilk API çağrımızı gerçekleştirelim. Gerçekleştirilecek API çağrısı ile birlikte SpaceFlight API üzerinden yayımlanan makalelere erişmemiz gerekecektir.
import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import java.util.List; import info.yazilimdersi.sampleretrofit.R; import info.yazilimdersi.sampleretrofit.api.ApiClient; import info.yazilimdersi.sampleretrofit.api.ApiInterface; import info.yazilimdersi.sampleretrofit.model.Article; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class); Call<List<Article>> call = apiService.getArticles(); call.enqueue(new Callback<List<Article>>() { @Override public void onResponse(Call<List<Article>> call, Response<List<Article>> response) { List<Article> articles = response.body(); Log.d("SampleRetrofit", "Makale sayısı : " + articles.size()); } @Override public void onFailure(Call<List<Article>>call, Throwable t) { Log.e("SampleRetrofit", t.toString()); } }); } }
Retrofit kütüphanesi API requestini background thread üzerinden atıp dönen response ile birlikte failure veya success durumuna göre UI threadini bilgilendirecektir. Projeyi Debug ile çalıştırdığımızda aşağıdaki şekilde verilerin model içerisine nasıl enjekte edildiğini gözlemleyebilirsiniz.
RecyclerView ile Makalelerin Görüntülenmesi
Makaleler artık API üzerinden bize ulaşmaktadır. Sırada yapılacak işlem makalelerin önyüzde görüntülenmesini sağlayacak kodlamayı tanımlamaktır. Öncelikle build.gradle dosyasına RecyclerView'e ait dependecynin eklenmelidir. Ayrıca Glide kütüphanesini kullanarak makaleye ait resimleri yükleyeceğiz.
dependencies { ..... //recyclerview kütüphanesi implementation 'androidx.recyclerview:recyclerview:1.1.0' //glide kütüphanesi implementation 'com.github.bumptech.glide:glide:4.11.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' }
Hazırlanacak listelemede makaleye ait resim, başlık, içerik ve events sayısının görüntülenmesi sağlanacaktır. Bunun için layout içerisinde 1 adet ImageView, 3 adet TextView ayarlamamız gerekecektir.
8. colors.xml dosyasını açıp uygulamada kullanılacak renklere ait verileri aşağıdaki şekilde ekleyelim.<?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorPrimary">#3F51B5</color> <color name="colorPrimaryDark">#303F9F</color> <color name="colorAccent">#FF4081</color> <color name="orange">#FF3909</color> <color name="colorAccentDark">#00B482</color> <color name="colorBlack">#555555</color> <color name="colorWhite">#FFFFFF</color> <color name="colorGrey">#707070</color> <color name="colorGreyLight">#8A8A8A</color> </resources>9. list_item_article.xml dosyasını açıp uygulamada kullanılacak renklere ait verileri aşağıdaki şekilde ekleyelim.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/movies_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical" android:minHeight="72dp" android:orientation="horizontal" android:padding="16dp"> <ImageView android:id="@+id/articleImageView" android:layout_width="60dp" android:layout_height="60dp" android:layout_marginRight="16dp" android:layout_centerInParent="true" android:scaleType="centerCrop" /> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical"> <TextView android:id="@+id/titleTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top" android:paddingRight="16dp" android:textColor="@color/colorBlack" android:textSize="16sp" android:textStyle="bold" /> <TextView android:id="@+id/summaryTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:maxLines="3" android:paddingRight="16dp" android:textColor="@color/colorGreyLight" /> <TextView android:id="@+id/launchesTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/colorPrimaryDark" </LinearLayout> </LinearLayout>10. ArticlesAdapter.java sınıfı içerisine API den dönen makalelerin verileri işlenmelidir.
import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; import java.util.List; import info.yazilimdersi.sampleretrofit.R; import info.yazilimdersi.sampleretrofit.model.Article; public class ArticlesAdapter extends RecyclerView.Adapter<ArticlesAdapter.ArticleViewHolder> { private List<Article> articles; private Context context; public static class ArticleViewHolder extends RecyclerView.ViewHolder { LinearLayout articleLinearLayout; TextView titleTextView; TextView summaryTextView; TextView launchesTextView; ImageView articleImageView; public ArticleViewHolder(View v) { super(v); articleLinearLayout = (LinearLayout) v.findViewById(R.id.articleLinearLayout); titleTextView = (TextView) v.findViewById(R.id.titleTextView); summaryTextView = (TextView) v.findViewById(R.id.summaryTextView); launchesTextView = (TextView) v.findViewById(R.id.launchesTextView); articleImageView = (ImageView) v.findViewById(R.id.articleImageView); } } public ArticlesAdapter(List<Article> articles, Context context) { this.articles = articles; this.context = context; } @Override public ArticlesAdapter.ArticleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_article, parent, false); return new ArticleViewHolder(view); } @Override public void onBindViewHolder(ArticleViewHolder holder, final int position) { holder.titleTextView.setText(articles.get(position).getTitle()); holder.summaryTextView.setText(articles.get(position).getSummary()); holder.launchesTextView.setText(articles.get(position).getLaunches().size() + " launches"); Glide.with(context).load(articles.get(position).getImageUrl()).into(holder.articleImageView); } @Override public int getItemCount() { return articles.size(); } }11. Son olarak MainActivity.java sınıfı içerisine RecyclerView oluşturup parametrik olarak Article sınıfını iletmemiz gerekecektir.
public class MainActivity extends AppCompatActivity { private RecyclerView recyclerView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); recyclerView = (RecyclerView) findViewById(R.id.articlesRecyclerView); recyclerView.setLayoutManager(new LinearLayoutManager(this)); ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class); Call<List<Article>> call = apiService.getArticles(); call.enqueue(new Callback<List<Article>>() { @Override public void onResponse(Call<List<Article>> call, Response<List<Article>> response) { List<Article> articles = response.body(); recyclerView.setAdapter(new ArticlesAdapter(articles, getApplicationContext())); } @Override public void onFailure(Call<List<Article>>call, Throwable t) { Log.e("SampleRetrofit", t.toString()); } }); } }
Böylelikle Retrofit kullanarak oluşturduğumuz örnek projeyi tamamladık. Artık elimizde json dönen bir API üzerinden parsing gerçekleştirebilen bir kodlama bulunmakta. Projeye ait ekran görüntüsü şu şekildedir;