همونطور که میدونید، رتروفیت طیف وسیعی از کاربردها رو ارائه میکنه و درنتیجه تنظیمات بسیار زیادی هم میتونه داشته باشه. بسیاری از برنامههای بزرگ نیازمند تنظیمات خاصی هستن (مثلاً برای اعتبارسنجی با کمک OAuth). برای دستیابی به یک پروژهی تمیز و پایدار، میخوایم ایدهی خودمون رو درقالب یک کلاینت پایدار اندروید مطرح کنیم: کلاس ServiceGenerator
پس با ما در ادامهی این تاپیک همراه باشین.
کلاس RServiceGenerator
حتماً میدونید که شئ Retrofit و سازندهی اون، قلب هر درخواستی هستن که توسط اونها شما تنظیمات رو انجام میدین و درخواستهاتون رو آماده میکنین، فرمت پاسخها رو مشخص میکنین، اعتبارسنجی، احراز هویت، گزارشگیری و مدیریت خطاها و... رو مدیریت میکنید.
متأسفانه توسعهدهندگان بسیاری رو دیدم که فقط این بخشها رو Copy/Paste میکنن، بجای اینکه اونها رو در یک کلاس تمیز و مجزا قرار بدن. کلاس ServiceGenerator ما قراره راهحل این مشکل شما باشه که براساس ایدهی
Bart Kiers طراحی شده.
اجازهبدین با کد ساده شروع کنیم. این کد فعلاً فقط یک متد برای ایجاد یک کلاینت سادهی REST برای کلاس/رابط ارائهشده عمل میکنه که درنهایت یک کلاس سرویس رو برای اون رابط برمیگردونه:
public class ServiceGenerator {
private static final String BASE_URL = "https://api.github.com/";
private static Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setLenient().create()));
private static Retrofit retrofit = builder.build();
private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
public static <S> S createService(Class<S> serviceClass) {
return retrofit.create(serviceClass);
}
}
همونطور که میبینید، کلاس ServiceGenerator از کلاس سازندهی Retrofit برای ایجاد یک کلاینت REST جدید باکمک API ارائهشده در نشانی که با BASE_URL مشخصشده، استفاده کرده. برای مثال، ما از API سایت GitHub استفاده کردیم که بهوضوح شما باید اون رو با URL موردنظر خودتون برای API جایگزین کنید.
متد createService یک پارامتر serviceClass میگیره که همون رابط حاشیهنویسیشدهی شما برای درخواستهای API هست. خروجی این متد هم یک شئ از کلاسی هست که میتونین متدهای تعریفشده داخل رابطتون رو ازطریق اون فراخوانی کنید.
چرا هر چیزی رو بصورت static تعریف کردیم؟
شاید تعجب کرده باشین که چرا ما از فیلدها و متدهای استاتیک در کلاس ServiceGenerator استفاده کردیم؟ درحقیقت یک دلیل ساده برای اینکار وجود داره: ما میخوایم از اشیاء یکسان (OkHttpClient و Retrofit و...) در تمام برنامه برای بازکردن یک اتصال استفاده کنیم که تمام درخواستها و پاسخها رو شامل کشکردن (Caching) و خیلی امکانات دیگه، مدیریت کنن. استفاده از یک شئ OkHttpClient به شما اجازه میده که از اتصال بازشدهی قبلی استفاده کنید. برای اینکار، یا باید شئ OkHttpClient رو به این کلاس ازطریق Dependency Injection تزریق کنیم و یا از فیلد static کمک بگیریم. همونطور که میبینید، ما فیلد static رو انتخاب کردیم و دلیلش هم اینه که از این شئ قراره توی این کلاس استفاده کنیم. بخاطر همین تمام فیلدها و متدها رو استاتیک تعریف کردیم.
بعلاوه، با این روش میتونیم برای افزایش سرعت، کمی از حافظهی ارزشمند دستگاههای موبایل رو نگهداریم تا هردفعه مجبور نباشیم اشیاء یکسانی رو بارها و بارها بسازیم.
استفاده از ServiceGenerator
همونطور که بنظر میرسه، برای ایجاد یک شئ جدید، خیلی راحت میتونیم از متد createService برای ایجاد هر تعداد شئ برای هر رابطی که دوست داشته باشیم، بارها و بارها در برنامه استفاده کنیم:
GitHubClient client = ServiceGenerator.createService(GitHubClient.class);
تمام مراحل آمادهسازی رو به کلاس ServiceGenerator منتقل میکنیم. متأسفانه در اغلب موارد، کلاس ServiceGenerator نمیتونه اینقدر ساده باقی بمونه. بنابراین کد بالا فقط به شما یک نقطهی شروع میده و شما نیاز به سازگارکردنش با نیازهای خودتون دارین. برای مثال، در دو قسمت بعد سعی میکنیم بخشی از تغییرات ممکن رو بعنوان مثال ارائه کنیم.
آمادهسازی برای گزارشگیری
یکیاز مهمترین نیازهای هر برنامهنویسی اینه که بدونه چهنوع دادههایی واقعاً توسط رتروفیت ارسال و دریافت میشه. گزارشگیری در نسخهی 2 کتابخانهی Retrofit توسط یک رهگیر بهاسم HttpLoggingInterceptor انجام میشه. بنابراین نیاز دارین که یک شئ از این رابط رو به OkHttpClient معرفی کنین. برای مثال میتونین اینشکلی کار کنید:
public class ServiceGenerator {
private static final String BASE_URL = "https://api.github.com/";
private static Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setLenient().create()));
private static Retrofit retrofit = builder.build();
private static HttpLoggingInterceptor logging = new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY);
private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
public static <S> S createService(Class<S> serviceClass) {
if (!httpClient.interceptors().contains(logging)) {
httpClient.addInterceptor(logging);
builder.client(httpClient.build());
retrofit = builder.build();
}
return retrofit.create(serviceClass);
}
}
چند نکته هست که باید حواستون بهشون باشه. اول اینکه مطمئن بشین که تصادفاً چندبار رهگیر رو اضافه نمیکنید. ما اینکار رو با متد contains روی خروجی متد interceptors انجام دادیم. اگه رهگیر موردنظرمون داخلشون باشه، متد contains نتیجهی true برمیگردونه. دوم اینکه باید اطمینان حاصل کنید که توی هر درخواست، شئ retrofit دوباره ساخته نمیشه. بخاطر همین هم ما فقط درصورتیکه رهگیر جدیدی اضافه بشه، متد build سازندهی Retrofit رو صدا میزنیم تا هردفعه شئ جدیدی ساخته نشه. درغیر اینصورت تمام هدف کلاس ServiceGenerator با شکست مواجه میشه.
آمادهسازی برای احراز هویت
نیازمندهایهای ورود کاربران بستهبه سیستم احراز هویتی که استفاده میکنید، میتونه متفاوت باشه. انواع مختلف احراز هویت مثل Basic Authentication یا Token Authentication یا OAuth یا حتی Hawk Authentication وجود دارن. با اینکه جزئیات تاحدودی برای هر پیادهسازی احراز هویت متفاوته، باز هم تغییرات رو باید در کلاس ServiceGenerator انجام بدین. درمورد هرکدوم از این سیستمهای احرازهویت و نحوهی کار با اونها در رتروفیت بعداً مقالات مفصلی خواهیم گذاشت، اما فعلاً به تغییری که توی متد createService ایجاد میکنیم تا از Hawk Authentication استفاده کنیم، دقت کنید:
public class ServiceGenerator {
private static final String BASE_URL = "https://api.github.com/";
private static Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setLenient().create()));
private static Retrofit retrofit = builder.build();
private static HttpLoggingInterceptor logging = new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY);
private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
public static <S> S createService(Class<S> serviceClass, final HawkCredentials credentials) {
if (credentials != null) {
HawkAuthenticationInterceptor interceptor = new HawkAuthenticationInterceptor(credentials);
if (!httpClient.interceptors().contains(interceptor)) {
httpClient.addInterceptor(interceptor);
builder.client(httpClient.build());
retrofit = builder.build();
}
}
if (!httpClient.interceptors().contains(logging)) {
httpClient.addInterceptor(logging);
builder.client(httpClient.build());
retrofit = builder.build();
}
return retrofit.create(serviceClass);
}
}
متد createService ما الآن یک پارامتر دوم داره که اطلاعات احراز هویت HawkCredentitals رو دریافت میکنه. اگه یک مقدار غیر null بفرستین، رهگیر احراز هویت Hawk لازم رو میسازه و اونرو به کلاینت Retrofit اضافه میکنه. همونطور که مشاهده میکنید، لازمه که در این حالت شئ Retrofit دوباره ساخته بشه (با کمک متد build سازنده) تا برای درخواست بعدی، تغییرات ما اعمال شده باشه.
قدم بعدی چیه؟
در این آموزش یاد گرفتین که چطور ایجاد یک مرکز برای تولید کلاینت رتروفیت در پروژهی شما میتونه بهتون کمک کنه و چرا اینقدر توصیه میشه. یکیاز روشهای انجام اینکار رو در کلاس ServiceGenerator مشاهده کردین. با این اوصاف، احتمالاً نیاز خواهید داشت که اونرو براساس اهدافتون تنظیم کنید. اگه درمورد این آموزش سؤالی داشتین، میتونین تاپیک جداگانه ایجاد کنید.
منبع این آموزش، مقالهای در سایت
Future Studio بوده.