آموزش استفاده از Retrofit 2 در اندروید

 Retrofit 2, a type safe HTTP client for Android
Retrofit 2, a type safe HTTP client for Android

در این مقاله سعی میکنم روش استفاده از کتابخانه Retrofit 2 رو در اندروید به زبان ساده و تا جای ممکن با مثال شرح بدم.

اول مقدمه‌ای بر اینکه اصلا Retrofit چی هست و به چه دردی می‌خوره.

تو دنیای امروز تکنولوژی اکثر برنامه‌های موبایلی نیاز دارن تا با یک سرور در ارتباط باشن و یا دیتای مورد نظرشون ازش بگیرن یا براش یک سری اطلاعات ارسال کنن یا بالعکس. برای انجام این کار در اندروید روش‌های زیادی وجود داره.

انواع و اقسام کتابخانه برای این کار طراحی شده و هر کدوم قابلیت‌های خودشون رو دارن ولی چیزی که همه اینها به صورت مشترک ازش استفاده می‌کنن کلاس HttpUrlConnection هستش که پایه‌ای ترین کلاس برای ایجاد یه ارتباط با اینترنت و کلا هر جایی که زبون HTTP رو می‌فهمه و صحبت می‌کنه.

و اما مشکلی که استفاده از کلاس HttpUrlConnection داره اینه که استفادش راحت نیست و پیاده‌سازی سناریوهای مختلف و پیچیده‌تر از یک کانکشن ساده می‌تونه سخت و طاقت فرسا باشه. حالا فکر کنید که یک اپلیکیشن دارید که قرار به چندین اندپوینت مختلف درخواست بفرسته که از متد REST استفاده می‌کنند.

مشکلاتی ازین دست که هر کدوم از کتابخانه‌های مذکور سعی کردن به نحوی برطرفش بکنن.

و اما Retrofit...

رتروفیت که خودش از کتابخانه بسیار معروف OkHttp استفاده می‌کنه و هر دوی اون‌ها محصول شرکت Square هستن سعی می‌کنه پیاده سازی اپلیکیشن‌هایی که با اینترنت سر و کار دارن و با API ها مخصوصا اونهایی که با REST کار می‌کنن رو خیلی راحت و مثل آب خوردن کنه. برای این کار هم از Annotationها استفاده می‌کنه. چجوری؟! حالا میگم...

فرض کنید اپلیکیشنی قراره بنویسیم که می‌خواد لیستی از پست‌های یک وبلاگ رو نمایش بده. هر پست وبلاگ خصیصه‌هایی مثل شناسه یوزر `userId`، شناسه پست `id`، عنوان `title` و متن پست `body‍` رو درون خودش جای داده. نمونه JSON یک پست رو در زیر می‌بینید:

{
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
  }

فرض کنید سرور شما قراره لیستی از این پست‌ها رو به شما برگردونه. می‌تونید خروجی مورد نظر رو در این آدرس ببینید.

برای این که ما بتونیم از این JSON استفاده کنیم بهترین کار (و البته روش درست) اینه که یه کلاس Post به صورت زیر تو کدمون داشته باشیم.

public class Post {
    private int userId;
    private int id;
    private String title;
    private String body;
    
    public int getUserId() {
        return userId;
     }
     public void setUserId(int userId) {
         this.userId = userId;
     }
     public int getId() {
         return id;
     }
     public void setId(int id) {
     	this.id = id;
     }
     public String getTitle() {
     	return title;
     }
     public void setTitle(String title) {
     	this.title = title;
     }
     public String getBody() {
     	return body;
     }
     public void setBody(String body) {
         this.body = body;
     }
}

در این مرحله میریم سراغ خود Retrofit. قبل از هر چیزی باید وابستگی (dependency) Retrofit رو به گریدل خودتون اضافه کنید و پروژه خودتون رو Sync کنید.

compile 'com.squareup.retrofit2:retrofit:2.3.0'

در ادامه لازمه یک نمونه (instance) از Retrofit بسازیم. موقتا این کار رو داخل Activity انجام می‌دیم. گرچه به یاد داشته باشید که اینکار درست نیست و روش درست‌ترش اینه که برای اپلیکیشن‌های بزرگ داخل کلاس Application تعریف بشه و یا بهتر از اون از Dagger استفاده بشه. ولی خوب فعلا سراغشون نمی‌ریم.

public class MainActivity extends AppCompatActivity {
	Retrofit retrofit;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		retrofit = new Retrofit.Builder().build(); // فعلا به همین صورت ساده بسنده می‌کنیم.
		}
}

حالا برای اینکه بتونیم به اندپوینت‌های مختلف API دسترسی داشته باشیم، باید یک interface ایجاد کنیم که به ازای هر endpoint از APIمون یک متود داخلش تعریف می‌کنیم. فعلا فقط برای گرفتن لیستی از پست‌ها این کارو می‌کنیم.

public interface MyApi {
   @GET("https://jsonplaceholder.typicode.com/posts")
   Call<List<Post>> getPosts();
}

تکه کد بالا دو قسمت مهم داره. اولیش مربوط به کد `@GET` هست که میگه به آدرس مشخص شده داخل پرانتز یک درخواست GET ارسال بشه. قسمت مهم بعدی نیز خروجی متود getPosts هست. به خروجی متود دقت کنید. فعلا اون تیکه Call رو کار نداشته باشید و به داخلش توجه کنید `List<Post>`. اینجا مشخص میکنیم که چیزی که از سرور برای ما میاد فهرستی از پست‌ها خواهد بود. حالا اینکه Retrofit چطور این کار رو انجام میده تو مرحله بعد توضیح میدم.

رتروفیت برای اینکه بتونه دیتای دریافتی از سرور رو که به فرمت JSON هست به object جاوا تبدیل کنه نیاز به یک ابزار مناسب داره که خوب معروف‌ترین این ابزارها Gson هستش و البته به طور خاص‌تر ابزار retrofit-gson-converter که تنها لازمه خط زیر رو به فایل build.gradle ماژول appتون اضافه‌کنید.

compile 'com.squareup.retrofit2:converter-gson:2.3.0'

این کار گرچه لازمه اما کافی نیست. ما یه جوری باید به رتروفیت بفهمونیم که از این استفاده کنه. برای اینکار لازمه به اون تیکه کدی که رتروفیت رو میسازه یه خط اضافه کنیم. به این صورت:

retrofit = new Retrofit.Builder()
    .addConverterFactory(GsonConverterFactory.create())
    .build();

حالا دیگه رتروفیت می‌فهمه که باید از Gson استفاده کنه. اینکه Gson چطوری این تبدیلات رو انجام میده هم مربوط به بحث دیگریه.

و اما ادامه ماجرا...

حالا نیاز داریم interfaceی که بالاتر ساختیم رو یه جوری به رتروفیت متصل کنیم. برای اینکار از کد زیر استفاده می‌کنیم.

MyApi myApi = retrofit.create(MyApi.class);

حالا که یک نمونه از کلاس MyApi رو داریم خیلی راحت میتونیم از متودهای داخلش استفاده کنیم.

myApi.getPosts().enqueue(new Callback<List<Post>>() {
	@Override
	public void onResponse(Call<List<Post>> call, Response<List<Post>> response) {
		List<Post> posts = response.body(); // اینجا خیلی راحت فهرست پست‌ها که توسط Gson ساخته شده رو در اختیار داریم
	}
	
	@Override
	public void onFailure(Call<List<Post>> call, Throwable t) {
		// این متود هم فقط زمانی فرخوانی می‌شه که به هر دلیلی کانکشن ما با مشکل روبرو بشه
	}
});

خوب احتمالا باید بدونید که تو اندروید اجرای عملیات شبکه نیاز به هیچ وجه نباید روی thread اصلی برنامه اجرا بشه. به همین دلیل ما از متود enqueue استفاده می‌کنیم که این مشکل ما رو حل میکنه یعنی کانکشن زدن و خوندن دیتا رو روی Background Thread انجام میده.

خوب تا اینجا خیلی ساده سعی کردم استفاده از رتروفیت رو در پایه‌ای ترین حالت خودش توضیح بدم. یادتون باشه که این مثال در عمل بهتره که از ابزارهای حرفه‌ای تری استفاده بشه. برای مثال بهتره که وظیفه ساختن کلاس Retrofit و همچنین ایجاد نمونه از interface ها رو به کتابخونه‌ای مثل Dagger بسپریم.

همچنین میشه با استفاده از RxJava خیلی کد قشنگ‌تر، خواناتری داشته باشیم به علاوه اینکه میتونیم از قدرت Obervable ها نیز استفاده کنیم.

سعی می‌کنم تو پست‌های بعدی مثال این مقاله گسترش بدم و روش‌های بهتری که برای پیاده سازی Retrofit هست رو شرح بدم.

کد کامل این مقاله رو می‌تونید در آدرس گیتهاب زیر پیدا کنید و ازش الگو برداری کنید.

https://github.com/2hamed/RetrofitSampleApp

لطفا نظرات و انتقادات خودتون مطرح کنید و اگر توضیح بیشتری لازمه بگید که بگم.

پ.ن. نکاتی که فکر می‌کردم در این مطلب جا افتاده رو در پستی جداگانه نوشتم.