خلاصه کتاب آموزش اندروید - Retrofit  ارتباط با سرور 1

این سری نوشته ها خلاصه کتاب اموزشی به نام Android Programming: The Big Nerd Ranch Guide که سعی خواهم کرد در حال مطالعه کتاب نکات خلاصه و مفید رو بنویسمدر این مطلب نحوه استفاده از Retrofit برای ایجاد ارتباط با سرور را توضیح میدهیم و خواهیم گفت چطور با استفاده از کتابخانه Gson پاسخ سرور را از JSON به کلاس های کاتلین تبدیل کنید . Retrofit با استفاده از متد های اینترفیسی که تعریف میکنیم کلاس های مورد نیاز برای ارتباط را ساخته و بصورت یک درخواست HTTP آن را ارسال میکند و پاسخی از نوع HTTP را به فرمت OkHttp.ResponseBody برمی گرداند . اما بهتر است که اطلاعات دریافتی به شکل نوع داده اپلیکیشن شما برگردانده شود که برای این منظور Retrofit با استفاده از تبدیل کننده ها ( converter ) داده های شما را به درخواست تبدیل کرده و پاسخ ها را نیز به شکلی که میخواهید بر میگرداند . اولین قدم مثل همیشه افزودن dependency است :implementation 'com.squareup.retrofit2:retrofit:last_version'رابط API که باید تعریف شود همان interface کاتلین می باشد که با annotations تزئین شده :interface FlickrApi { @GET(&quot/&quot) fun fetchContents(): Call<String> }هر متد باید با یکی از انواع درخواست های HTTP مشخص شود . انواع متداول GET, POST, PUT, DELETE می باشد . هر متد تعریف شده یک آدرس نیز دارد که ادامه دهنده آدرس URL اصلی است که از قبل تعریف خواهد شد ، اما " / " در این متد نشان میدهد که همان آدرس اصلی به عنوان مقصد درخواست خواهد بود . نوع Call نیز جهت برگرداندن پاسخ از سرور می باشد که با تعیین <Call<String به Retrofit خواهد گفت پاسخ سرور را به String برگرداند . حال تنها باید retrofit را بسازیم :val retrofit: Retrofit = Retrofit.Builder() .baseUrl(&quothttps://www.flickr.com/&quot) .build() val flickrApi: FlickrApi = retrofit.create(FlickrApi::class.java) } با تعریف Retrofit.Builder و اعلام یک آدرس URL اولیه کار تمام است . فقط حتما باید حواستان باشد که در آخر آدرس URL یک "/" نیز اضافه کنید تا مطمئن شوید که آدرس تعریف شده در API به ادامه این آدرس ترکیب شود . همانطور که مشخص است Retrofit هیچ کلاسی را در زمان کامپایل نمی سازد و تنها وقتی در زمان اجرا کد retrofit.create اجرا میشود کلاس های مورد نظر با استفاده از رابط API که تعریف کرده اید ساخته میشود . برا اینکه بتوان پاسخ سرور را به نوع String تبدیل کرد باید از تبدیل کننده استففاده کرد که اینکار با تبدیل کننده ای که توسط تیم Retrofit ساخته شده به سادگی کد زیر قابل انجام است . فقط باید از قبل dependency مورد نظر را اضافه کنید :implementation 'com.squareup.retrofit2:converter-scalars:last_version'حال با کد زیر به سادگی پاسخ دریافتی از سرور قبل از برگرداندن نتیجه به نوع String تبدیل خواهد شد:val retrofit: Retrofit = Retrofit.Builder() .baseUrl(&quothttps://www.flickr.com/&quot) .addConverterFactory(ScalarsConverterFactory.create()) .build() val flickrApi: FlickrApi = retrofit.create(FlickrApi::class.java) }جهت اجرای درخواست و دریافت نتیجه باید به شکل زیر درخواست رابط API را با استفاده از متد enqueue در onCreate اجرا کنیم :val flickrHomePageRequest: Call<String> = flickrApi.fetchContents()flickrHomePageRequest.enqueue(object : Callback<String> { override fun onFailure(call: Call<String>, t: Throwable) { Log.e(TAG, &quotFailed to fetch photos&quot, t) } override fun onResponse(call: Call<String>, response: Response<String> ) { Log.d(TAG, &quotResponse received: ${response.body()}&quot) } })متد Call.enqueue دو کار بسیار عالی را برای ما انجام میدهد ، اول اینکه ارسال درخواست را طبق قوانین ترد اندروید در ترد پس زمینه اجرا میکند و دوم اینکه پاسخ دریافتی از سرور را در ترد اصلی برای به روز رسانی رابط کاربری بر میگرداند ،در ضمن متد enqueue جهت اجرا بصورت صف می باشد ، یعنی میتوانید چندین درخواست را با هم به آن اعلام کرده و Retrofit بصورت یکی یکی درخواست را ارسال و پاسخ را دریافت خواهد کرد . البته شما میتوانید بصورت مستقیم با استفاده از Call.execute هم درخواست را ارسال کنید فقط باید اجرا در ترد پس زمینه را بصورت دستی اعمال کنید .آخرین کار نیز افزودن کد دسترسی استفاده از اینترنت به فایل AndroidManifest می باشد : <uses-permission android:name=&quotandroid.permission.INTERNET&quot />قبلا در خصوص مزیت ها استفاده از LiveData در مطالب قبلی صحبت کردیم ، اینجا نیز با استفاده از قدرت فوق العاده این کتابخانه می توان کد تمیزی را ایجاد کرد . برای اینکار باید تنظیمات Retrofit را به یک کلاس جدا انتقال داده و نوع برگشتی را نیز از نوع LiveData تعریف کنیم :fun fetchContents(): LiveData<String> { val responseLiveData: MutableLiveData<String> = MutableLiveData() val flickrRequest: Call<String> = flickrApi.fetchContents() flickrRequest.enqueue(object : Callback<String> { override fun onFailure(call: Call<String>, t: Throwable) { Log.e(TAG, &quotFailed to fetch photos&quot, t) } override fun onResponse(call: Call<String>,response: Response<String>) { Log.d(TAG, &quotResponse received&quot) responseLiveData.value = response.body() } }) return responseLiveData } }حالا به راحتی میتوان در برنامه نتیجه درخواست از سرور را observe کرده و هر وقت که نتیجه برگردانده شد رابط کاربری را به روز رسانی کنیم : val flickrLiveData: LiveData<String> = FlickrFetchr().fetchContents() flickrLiveData.observe(this,Observer { responseString ->Log.d(TAG, &quotResponse received: $responseString&quot) })بطور معمول اطلاعات دریافتی از سرور از نوع JSON می باشد که ما باید بتوانیم آن را به مدل برنامه خود تبدیل کنیم تا به راحتی قابل استفاده باشد . مثلا لیستی از تصاویر و اطلاعات آن ، برای اینکار ابتدا متد جدیدی در رابط API تعریف میکنیم :interface FlickrApi { ....... @GET( &quotservices/rest/?method=flickr.interestingness.getList&quot + &quot&api_key=yourApiKeyHere&quot + &quot&format=json&quot + &quot&nojsoncallback=1&quot + &quot&extras=url_s&quot ) fun fetchPhotos(): Call<String> }همانطور که میبینید در آدرس درخواست یکسری اطلاعات مثل api_key نیز ارسال میشود که بسته به سرور شما اطلاعات مختلفی نیاز است .سپس طبق ساختار JSON دریافتی از سرور یک data class می سازیم :data class GalleryItem( var title: String = &quot&quot, var id: String = &quot&quot, var url: String = &quot&quot )برای تبدیل باید از کتابخانه Gson استفاده کنیم که dependency زیر را اضافه میکنیم :implementation 'com.google.code.gson:gson:last_version' implementation 'com.squareup.retrofit2:converter-gson:last_version'سپس کلاس های مدل براساس اطلاعات دریافتی JSON را می سازیم و با استفاده از SerializedName ارتباط بین فیلد های مدل و پاسخ را بصورت صریح مشخص میکنیم :data class GalleryItem( var title: String = &quot&quot, var id: String = &quot&quot, @SerializedName(&quoturl_s&quot) var url: String = &quot&quot ) class PhotoResponse { @SerializedName(&quotphoto&quot) lateinit var galleryItems: List<GalleryItem> }سپس کلاسی برای نوع دریافتی پاسخ ایجاد میکنیم :class FlickrResponse { lateinit var photos: PhotoResponse }حال کافی است در کلاس های ساخته شده برای ارتباط با سرور بجای دریافت String از مدل های بالا استفاده کنیم . البته با معرفی Gson به عنوان تبدیل کننده :interface FlickrApi { @GET(...) fun fetchPhotos(): Call<FlickrResponse> }val retrofit: Retrofit = Retrofit.Builder() .baseUrl(&quothttps://api.flickr.com/&quot) .addConverterFactory(GsonConverterFactory.create()) .build() flickrApi = retrofit.create(FlickrApi::class.java) } fun fetchPhotos(): LiveData<List<GalleryItem>> { val responseLiveData: MutableLiveData<List<GalleryItem>> = MutableLiveData() val flickrRequest: Call<FlickrResponse> = flickrApi.fetchPhotos() flickrRequest.enqueue(object : Callback<FlickrResponse> { override fun onFailure(call: Call<FlickrResponse>, t: Throwable) { Log.e(TAG, &quotFailed to fetch photos&quot, t) } override fun onResponse(call: Call<FlickrResponse>,response: Response<FlickrResponse>) { Log.d(TAG, &quotResponse received&quot) responseLiveData.value = response.body() val flickrResponse: FlickrResponse? = response.body() val photoResponse: PhotoResponse? = flickrResponse?.photos var galleryItems: List<GalleryItem> = photoResponse?.galleryItems?: mutableListOf() galleryItems = galleryItems.filterNot { it.url.isBlank() } responseLiveData.value = galleryItems } }) return responseLiveData } }ادامه در قسمت دوم ....