استارت‌آپ و کارآفرینی

ارائه مثال عملی با کتابخانه Retrofit2 – قسمت دوم

در قسمت قبل ویژگی‌ها و تغییرات Retrofit2 رو بیان کردیم. در این بخش به ارائه مثال عملی استفاده از این کتابخانه می‌پردازیم.

در این برنامه دو درخواست به دو سرور مختلف ارسال می‌شه. اولی به یک فایل json خواهد‌ بود که داخل اون لیستی از اسامی فیلم و امتیاز به همراه لینک عکس پوستر آنها قرار داره. اطلاعات دریافت‌شده توسط ListView نمایش داده می‌شه. همچنین عکس پوستر فیلم هم توسط کتابخانه Picasso بارگذاری خواهد شد. دومین درخواست به بخش API سایت stackoverflow خواهد بود. در مثال دوم با استفاده از یک فرم یک سری پارامتر به API مورد نظر ارسال‌شده تا اطلاعات مربوط به سوال‌های پرسیده‌شده در این وب‌سایت دریافت شود. تعداد رکوردهای دریافت‌شده و عنوان اولین سوال نیز نمایش داده می‌شود.

سورس این برنامه در github قرار گرفته است، هرگونه pull request در جهت بهبود برنامه پذیرفته خواهد‌ شد.

برای injection کردن المان‌های صفحه از کتابخانه butterknife استفاده‌ شده‌است. برای توضیحات بیشتر در این مورد می‌تونید به مقاله‌ای از صمصام بابادی مراجعه کنید. همچنین برای توضیحات بیشتر نمایش عکس در ListView‌ با استفاده از Picasso به مقاله‌ای از طاها قاسمی مراجعه کنید.

ابتدا خطوط زیر رو به build.gradle‌ اضافه کنید:

compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'
compile 'com.jakewharton:butterknife:7.0.1'
compile 'com.squareup.picasso:picasso:2.5.2'

منتظر باشید تا کار sync‌ کردن کتابخانه‌ها تموم بشه.
طراحی رابط‌کاربری بسیار ساده بوده و بیشتر به دریافت و نمایش اطلاعات توجه شده است. به دلیل‌اینکه کل سورس برنامه در دسترس هست از ذکر جزئیات خودداری می‌کنم. برای صفحه اصلی یا MainActivity دو المان Button قرار داده‌شده که با زدن هر کدام Activity‌ مربوطه نمایش داده می‌شه.

retrofitDemo_1

فایل json مربوط به اطلاعات فیلم

بخشی از اطلاعات ذخیره‌شده در این فایل به‌صورت زیر است:

[{
        "title": "Dawn of the Planet of the Apes",
        "image": "http://api.androidhive.info/json/movies/1.jpg",
        "rating": 8.3,
        "releaseYear": 2014,
        "genre": ["Action", "Drama", "Sci-Fi"]
    },
    {
        "title": "District 9",
        "image": "http://api.androidhive.info/json/movies/2.jpg",
        "rating": 8,
        "releaseYear": 2009,
        "genre": ["Action", "Sci-Fi", "Thriller"]
    }
....

یکی از توانایی‌های خوب Retrofit مپ‌کردن دیتای دریافتی بر روی کلاس‌های جاوا یا اصطلاحا POJO هست. این کار رو می‌شه با یکی از تبدیل‌کننده‌های رسمی همین کتابخانه به نام GsonConverter انجام داد. کتابخانه مورد نظر رو به gradle اضافه کردیم ولی ابتدا باید بتونیم کلاس POJO رو ایجاد‌ کنیم. این کار به راحتی توسط این وب‌سایت قابل انجام هست. متن فایل json مورد نظر رو کپی کنید و در این وب سایت قرار بدید، تنظیمات رو مطابق عکس زیر انجام بدید:
pojo

با زدن دکمه Preview کلاس POJO مربوط به این ساختار json ایجاد می‌شه. کلاس مورد نظر رو در پروژه با استفاده از همین نام تعریف شده در اینجا یعنی Movie ایجاد می‌کنیم.

آدرس API وب سایت stackoverflow بخش سوالات

بخشی از اطلاعات نمایش داده‌شده در این لینک به صورت زیر است:

{"items":[{"tags":["java","android","jni","native-code"],"owner":{"reputation":3764,"user_id":682869,"user_type":"registered","accept_rate":69,"profile_image":"https://i.stack.imgur.com/vJH1M.jpg?s=128&g=1","display_name":"Jake","link":"http://stackoverflow.com/users/682869/jake"},"is_answered":false,"view_count":18,"answer_count":0,"score":2,"last_activity_date":1456091762,"creation_date":1456091762,"question_id":35542630,"link":"http://stackoverflow.com/questions/35542630/android-java-stack-vs-native-stack","title":"Android - Java Stack vs Native Stack"},{"tags":["java","android","appium","inspector"],"owner":{"reputation":1,"user_id":5711488,"user_type":"registered",
....

برای به دست آوردن کلاس POJO این دیتا نیز به روش اشاره‌شده عمل می‌کنیم. تفاوتی که در این مثال با مثال قبل وجود داره اینه که فایل خروجی این دیتا خود شامل چندین کلاس است. یعنی برای مثال، کلاس اصلی یا Parent با نام Question نام‌گذاری شده است که در داخل اون لیستی از کلاس Item وجود داره. این لیست، شامل سوالاتی هست که پرسیده شده‌اند. در داخل کلاس Item کلاس دیگری تعریف شده به نام Owner که اطلاعات مربوط به کسی که سوال رو پرسیده در اون نگهداری می‌شه.
باید برای تمامی این کلاس‌ها به صورت جداگانه فایل جاوا ایجاد کرد و مقادیر ویژگی‌های اون‌ها رو وارد کرد. در ادامه خواهید دید که با استفاده از این تبدیل‌کننده، به راحتی و بدون درگیر شدن با ساختار پیچیده این داده‌ها می‌توان به تمامی اطلاعات دسترسی پیدا کرد.

بعد از ایجاد کلاس‌های POJO به سراغ کلاس تعریف توابع سرویس Retrofit خواهیم رفت. کلاس مورد نظر با نام APIService به صورت زیر ایجاد شده:

package ir.bkhezry.retrofit2.Service;


import java.util.List;

import ir.bkhezry.retrofit2.Model.Question;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
import ir.bkhezry.retrofit2.Model.Movie;

/**
 * Created by bkhezry on 2/20/2016.
 */
public interface APIService {
    @GET("questions")
    Call<Question> getQuestionsService(@Query("page") int page,
                                       @Query("pagesize") int pagesize,
                                       @Query("order") String order,
                                       @Query("sort") String sort,
                                       @Query("tagged") String tagged,
                                       @Query("site") String site);

    @GET("movies.json")
    Call<List<Movie>> getMoviesService();
}

در این کلاس دو تابع به نام های getQuestionsService و getMovieService ایجاد شده‌اند. هر دو کلاس با استفاده از متد GET اطلاعات رو از سرور دریافت می‌کنن. در تابع مربوط به دریافت اطلاعات سوالات پرسیده‌شده در وب سایت stackoverflow تعدادی پارامتر ارسال می‌شوند. هر کدام از این پارامترها در داخل آدرس API ذکر شده وجود دارند و باید به همراه هر درخواست، این مقادیر ارسال شوند.

مقدار برگشتی تابع getMovieService لیستی از کلاس Movie خواهد بود. همچنین برای تابع دیگر، کلاس Question مقدار برگشتی خواهد بود که شامل لیستی از کلاس Item است. در این کلاس ویژگی‌های سوالات پرسیده‌شده قرار دارد.

با اضافه‌کردن این کلاس ساختار فایل‌های برنامه به صورت زیر خواهد بود:
ProjectExplorer

اکنون نوبت به پیاده‌سازی تابع دریافت‌کننده اطلاعات به صورت ناهمگام است. در فایل HiveActivity این تعریف به این صورت است:

private void getData() {
        Retrofit retrofit = new Retrofit.Builder().baseUrl("http://api.androidhive.info/json/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        APIService apiService = retrofit.create(APIService.class);
        Call<List<Movie>> call = apiService.getMoviesService();
        call.enqueue(new Callback<List<Movie>>() {
            @Override
            public void onResponse(Call<List<Movie>> call, Response<List<Movie>> response) {
                hidePDialog();
                try {
                    if (response.isSuccess()) {
                        Toast.makeText(HiveActivity.this, "Success", Toast.LENGTH_LONG).show();
                        movieList.clear();
                        movieList.addAll(response.body());
                        adapter.notifyDataSetChanged();

                    } else {
                        Toast.makeText(HiveActivity.this, "Failed! : " + response.errorBody().string(), Toast.LENGTH_LONG).show();

                    }
                } catch (IOException e) {
                    Log.e("bkhezry", "IOException:"+e.getMessage().toString());
                }
            }

            @Override
            public void onFailure(Call<List<Movie>> call, Throwable t) {
                hidePDialog();
                if (t != null) {
                   Toast.makeText(HiveActivity.this, "Failed! OnFailure: " + t.getMessage(), Toast.LENGTH_LONG).show();
                }

            }
        });

در این تابع، GsonConverter به عنوان تبدیل‌کننده معرفی شده. همچنین baseUrl نیز با مقدار http://api.androidhive.info/json/ سرور دریافت اطلاعات را مشخص می‌کند. چنانچه دریافت اطلاعات با موفقیت انجام نشود در تابع ()response.errorBody خطای روی داده نمایش داده می‌شود. حتما برای کنترل مقادیر بازگشتی از این روش استفاده کنید.

لیست Movie‌ دریافت‌شده به بخش نمایش اطلاعات که CustomListAdapter نام دارد ارسال‌شده و سپس نمایش داده می‌شود. خروجی این بخش به صورت زیر خواهد بود:

retrofitDemo_2

تعریف تابع ناهمگام دریافت اطلاعات در فایل StackoverflowActivity به صورت زیر است:

void getDataFunction() {
        getData.setText("Waiting...");
        progressBar.setVisibility(View.VISIBLE);
        Retrofit retrofit = new Retrofit.Builder().baseUrl("https://api.stackexchange.com/2.2/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        APIService apiService = retrofit.create(APIService.class);
        int pageVar = Integer.parseInt(page.getText().toString());
        int pagesizeVar = Integer.parseInt(pagesize.getText().toString());
        String orderVar = order.getText().toString();
        String sortVar = sort.getText().toString();
        String taggedVar = tagged.getText().toString();
        String siteVar = site.getText().toString();
        Call<Question> call = apiService.getQuestionsService(pageVar, pagesizeVar, orderVar, sortVar, taggedVar, siteVar);
        call.enqueue(new Callback<Question>() {
            @Override
            public void onResponse(Call<Question> call, Response<Question> response) {
                getData.setText("Get Data");
                progressBar.setVisibility(View.INVISIBLE);
                String responseString = "Response: ";
                try {
                    if (response.isSuccess()) {
                        Toast.makeText(StackoverflowActivity.this, "Success", Toast.LENGTH_LONG).show();

                        Question question = response.body();
                        responseString += "\nSize Of Questions: " + question.getItems().size();
                        if (question.getItems().size() > 0)
                            responseString += "\nFirst item title: " + question.getItems().get(0).getTitle();

                        responseTextView.setText(responseString);
                    } else {
                        responseString += "\nFailed! : " + response.errorBody().string();
                        responseTextView.setText(responseString);
                    }
                } catch (IOException e) {
                    Log.e("bkhezry", "IOException:"+e.getMessage().toString());
                }

            }

            @Override
            public void onFailure(Call<Question> call, Throwable t) {
                getData.setText("Get Data");
                progressBar.setVisibility(View.INVISIBLE);
                if (t != null) {
                    responseTextView.setText("Response:\nOnFailure: " + t.getMessage());
                }
            }
        });

تمامی روند کار همانند دریافت اطلاعات ذکر شده قبلی هست بجز فراخوانی تابع getQuestionsService که در آن مقادیر مورد نظر نیز به تابع ارسال می‌شوند. پس از دریافت اطلاعات تعداد آن‌ها نمایش داده شده و در صورتی که تعداد آن‌ها یک و بیشتر باشد، عنوان عنصر اول لیست Item یا سوال نیز نمایش داده می‌شود.
فرم دریافت اطلاعات به صورت زیر است که با مقادیر پیش فرض نمایش داده می‌شود:
retrofitDemo_3
پس از دریافت اطلاعات به صورت زیر نمایش داده می‌شود:
retrofitDemo_4

منابع: + +

13 نظرات
  1. مصطفی نصیری می گوید

    با تشکر از مقاله خوبت
    میشه لطف کنی یه مقایسه کوچیک بین retrofit و volley انجام بدی و تفاوت هاشون رو بگی؟

    1. بهروز خضری می گوید

      من آشنایی زیادی با volley ندارم، تنها توو یکی از همین منابع دیدمش.
      اما مقایسه‌ای تقریبا یک ماه قبل روی این دوتا انجام شده که می‌تونید به این لینک مراجعه کنید:
      http://goo.gl/rgjifL
      توو stackoverflow‌ هم مباحثی گفته شده که می‌تونید این لینک رو ببینید:
      http://goo.gl/U8HP9

  2. علی می گوید

    سلام، می شه پروژه عملی این آموزش رو برای دانلود قرار بدین ؟
    ممنون

    1. بهروز خضری می گوید

      سلام،
      یک بار دیگه متن مقاله رو مطالعه کنید، سورس این پروژه در گیت هاب قرار داده شده که لینکش ذکر شده.

      1. میثم می گوید

        سلام
        من لینک گیت هاب رو نمبینم 🙂 اگه امکن داره قرار بدید

        1. بهروز خضری می گوید

          بر روی github‌ کلیک کنید

  3. ستار می گوید

    خیلی عالی بود جناب خضری – لطفا مقالات بیشتری از retrofti2 بزارید . خوشم اومده و اگه خدا بخواد تو پروژه هام از retrtofit استفاده میکنم . الانم که دیگه نسخه ۲ از بتا اومده بیرون.

    بازم ممنون – اجرکم من الله

  4. ستار می گوید

    سلام جناب خضری اگر امکان داره یه نمونه از ذخیره اطلاعات توسط retrofit 2 بزارید
    بنده دریافت اطلاعات رو تمرین کردم و خیلی عالی و خوب بود!
    ولی تو ارسال اطلاعات نتونستم منبع و نمونه خوبی پیدا کنم !
    اگر امکان داره یه نمونه ساده هم شده بزارین یا راهنمایی کنین !
    باز هم ممنون – من از مطالب سایتتون نهایت استفاده را دارم میبرم ! واقعا مدیونم بهتون!

  5. محمد می گوید

    سلام لطفا کار با retrofit رو با وب سرویس های جاوا و localhost آموزش بدین متشکرم

    1. بهروز خضری می گوید

      رتروفیت با rest api ارتباط برقرار می‌کنه. یعنی ربطی به برنامه‌نویسی سمت سرور نداره
      باید پیاده‌سازی درست api سمت سرور انجام شده باشه، پس کار رتروفیت ثابت خواهد بود.
      برای آموزش بیشتر در مورد رتروفیت ویدیو آموزشی احمد طحانی رو ببینید
      https://faranesh.com/programming/14946-retrofit-in-android

  6. علی زنده دل می گوید

    سلام استاد.
    خسته نباشید
    برای تبدیل یه جیسون ساده مثل زیر باید چه کلاس هایی داشته باشیم و برای دستور GET استفاده کرد.
    و سوال دوم اینکه چجوری میشه اطلاعات دریافتی از Object رو تو کلاس User ذخیره کنیم؟

    نمونه جیسون
    {
    “user”: {
    “id”: ۱۵۰,
    “name”: “test name”,
    “email”: “example@gmail.com”,
    “mobile”: “۰۹۳۶۰۳۹۸۰۰۶″,
    “type”: ۱
    }
    }

    1. بهروز خضری می گوید

      چنانچه از لینکی که بالا گفتم برید و متن json رو بهش بدید این خروجی رو میده بهتون.
      https://gist.github.com/bkhezry/2ef7e6f338e792bbb9e804e3c492c38b

  7. محمود می گوید

    با سلام
    من از یه api یکسری دیتا دریافت میکنم و داخل یکی از آیتم های جیسون یک آدرس api دیگه هست که آدرس عکس ذخیره شده .باید اونو چطوری پیاده سازی کنم که آدرس عکسو دربیارم مخم دیگه ج نداد.
    واقعیتش با volley دو تا request فرستادم و آدرسو درآوردم ولی تو کلاس مدل ست نمیشه و nullه.

ارسال یک پاسخ

آدرس ایمیل شما منتشر نخواهد شد.