آموزش کتابخانه Paging در اندروید

بسیاری از برنامه ها مجموعه بزرگی از داده ها را به کاربران نمایش می دهند . برای مثال برنامه آمازون را در نظر بگیرید این برنامه لیستی از محصولات را نشان می دهد. محصولات زیادی هم دارد اما تمام محصولات را یکباره بارگیری نمی کند ,برخی از محصولات را  نشان می دهد و به محض رسیدن به آخرین مورد از لیست , محصولات بیشتری را بارگیری می کند و به ما نشان می دهد .اینکار صفحه بندی یا پیمایش (Paging)  نامیده می شود. در این مقاله از آموزشگاه های برنامه نویسی در مشهد به معرفی این ویژگی می پردازیم .

چرا از Paging استفاده کنیم؟

  • فرض کنید بیش از ۱۰۰۰ آیتم برای لیست تان دارید که از یک سرور پشتیبان ,می گیرید. کار اشتباهی است که همه آیتم ها را یکباره خوانده و نمایش دهیم .

معایب عدم استفاده از صفحه بندی (Paging) :

  • کاربر همه آیتمها را همزمان نمی بیند اما شما همه آیتم ها را یکدفعه بارگذاری می کنید که در نتیجه پهنای باند بیشتری را بیهوده مصرف خواهید کرد.
  •  همچنین ایجاد یک لیست بزرگ از منابع سیستم بیشتری استفاده می کند که نتیجه آن یک برنامه کند خواهد بود.

مزایای استفاده از پیمایش:

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

کتابخانه paging

کتابخانه paging اندروید یک جز  از کامپوننت jetpack اندروید هست .به یاد داشته باشید که بطور پیش فرض در دسترس نیست و باید این کتابخانه را به برنامه اضافه کنیم. اینکار به ما کمک می کند که داده هارا به تدریج و به زیبایی در RecyclerView  بارگیری کنیم.

Android Jetpack مجموعه ای از کتابخانه ها، ابزار ها و راهنمایی های معماری است که به شما کمک می کند تا سریع و آسان به ساخت برنامه های کاربردی برای اندروید بپردازید. Android Jetpack زیر ساخت ها ی کد را فراهم می کند، بنابراین شما می توانید بر روی آنچه که در برنامه شما مورد نیاز است ، تمرکز کنید.

پیش نیازها

  • استفاده از Retrofit در اندروید:از کتابخانه Retrofit برای آوردن داده ها از API backend استفاده خواهیم کرد.
  • RecyclerView:بعد از آوردن آیتم ها از سرور آنها را در یک RecyclerView بارگذاری خواهیم کرد.
  • Android ViewModel: یک جزء دیگر از Android Jetpack میباشد و به ما کمک می کند اطلاعات مربوط  به رابط کاربر(UI) را به روش کارآمدتر نمایش دهیم .

Backend API

مهمترین قسمت Backend API هست . اگر چه شما میتوانید داده ها را از پایگاه داده SQLITE با استفاده از کتابخانه Paging بارگذاری کنید اما اغلب , برنامه نیاز به استخراج داده از Bakend API دارد . شما میتوانید با یادگیری یکسری آموزشها یک API را با استفاده از PHP و MYSQL بسازید .

در این آموزش API را خودمان نخواهیم ساخت و از یک API واقعی از Stackoverflow استفاده کنیم.در زیر لینک API هست  :

https://api.stackexchange.com/2.2/answers?page=1&pagesize=50&site=stackoverflow

در نشانی اینترنتی API بالا پارامترهای زیر را داریم:

  • Page: شماره صفحه ای که میخواهیم بیاریم.
  • Pagesize:تعداد کل آیتم هایی که در صفحه میخواهیم.
  • Site:سایتی که میخواهیم داده ها را از آن  بیاوریم.

نشانی اینترنتی بالا پاسخ زیر را خواهد داد:

آموزش کتابخانه Paging در اندروید
آموزش کتابخانه Paging در اندروید

داده ها از Stackoverflow می آیند و این سایت یک مجموعه داده بسیار بزرگی دارد بنابراین ممکنه تعداد نامحدود صفحات داشته باشیم. حال وظیفه ما این است که صفحه ۱ را برداریم و به محض رسیدن کاربر به انتهای فهرست ,صفحه بعدی را بارگذاری کنیم و برای اینکار از کتابخانه paging اندروید استفاده می کنیم.

حالا بیایید وارد کد واقعی شویم.

ایجاد یک پروژه جدید:

  • یک پروژه جدید با عنوان Paging Library Tutorial ایجاد میکنیم.

اضافه کردن وابستگی ها (Dependencies)

  • به فایل build.gradle بروید و وابستگی های زیر را اضافه کنید:
dependencies {
    def paging_version = "1.0.0"
    def view_model_version = "1.1.0"
    def support_version = "27.1.0"
    def glide_version = "4.3.1"
 
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation "com.android.support:appcompat-v7:$support_version"
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    
    //adding retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.4.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
    
    //adding view model
    implementation "android.arch.lifecycle:extensions:$view_model_version"
    implementation "android.arch.lifecycle:viewmodel:$view_model_version"
    
    //adding paging
    implementation "android.arch.paging:runtime:$paging_version"
    
    //adding recyclerview and cardview
    implementation "com.android.support:cardview-v7:$support_version"
    implementation "com.android.support:recyclerview-v7:$support_version"
    
    //adding glide 
    implementation "com.github.bumptech.glide:glide:$glide_version"
    annotationProcessor "com.github.bumptech.glide:compiler:$glide_version"
}

  • بعد از اضافه کردن وابستگی های مورد نیاز پروژه خود را sync (همگام) کنید.

ما وابستگیهای زیر را اضافه کردیم:

  • Retrofit and Gson: برای تجزیه json از URL ( نشانی اینترنتی)
  • ViewModel : کامپوننت اندروید برای ذخیره داده ها
  • Paging:کتابخانه صفحه بندی (پیمایش)
  • RecyclerView and CardView:برای ساختن فهرست
  • Glide:برای بارگذاری تصویر از URL

ایجاد کلاس مدل

ما به این کلاس نیاز داریم تا پاسخ Json را بطور خودکار تجزیه کنیم. در کل به کلاسهای زیادی  هم نیاز داریم تا پاسخ  را بطور خودکار به کلاس جاوا مربوطه متصل کنیم

  • یک فایل به نام StackApiResponse.java ایجاد کنید و کد زیر را بنویسید.
package net.simplifiedcoding.androidpagingexample;

import java.util.List;

class Owner {
    public int reputation;
    public long user_id;
    public String user_type;
    public String profile_image;
    public String display_name;
    public String link;
}

class Item {
    public Owner owner;
    public boolean is_accepted;
    public int score;
    public long last_activity_date;
    public long creation_date;
    public long answer_id;
    public long question_id;
}

public class StackApiResponse {
    public List<Item> items;
    public boolean has_more;
    public int quota_max;
    public int quota_remaining;
}
  • کد بالا بسیار ساده است . و در حد امکان کوتاه نوشته شده است . نام متغیرها با کلیدهای Json مطابقت دارد بنابراین Gson داده ها را بر اساس آن نمایش خواهد داد .
  • فایل بالا فقط یک کلاس عمومی (public) دارد .(در واقع ما میتوانیم فقط یک کلاس عمومی در یک فایل داشته باشیم)

کلاس به نام StackApiResponse.java شامل Json زیر است:

  • در کلاس فوق ما در حال تطبیق دادن داده های  Json بالا هستیم به همین دلیل است که فقط ۴ ویژگی در StackApiResponse.java داریم.
  • اولین مورد در داخل StackApiResponse.java آیتم (items) هست که شامل یک آرایه از آیتم ها می باشد.به همین دلیل است که یک < List<Item داریم.

سپس has_more از نوع boolean  هست و quota_max و quota_remaining  از نوع int هستند.

  • در حال حاضر داخل  Item , ما Json زیر را داریم:

  • درون items ما یک شی ء دیگر به نام Owner  داریم.به همین دلیل است که یک کلاس دیگر به نام owner ایجاد کرده ایم و یک شیء از  نوع owner را داخل کلاس Item تعریف کرده ایم.امیدوارم که فهمیده باشید که چطور کلاس مدل را برای Json مشخص تعریف کنید.

ایجاد کلاس Retrofit

هر بار که میخواهیم داده ها را از یک صفحه جدید دریافت کنیم به شیء Retrofit نیاز داریم. پس ایجاد یک نمونه از Retrofit ایده خوبی هست .

یک کلاس جدید به نام RetrofitClient ایجاد می کنیم.

package net.simplifiedcoding.androidpagingexample;

import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class RetrofitClient {

    private static final String BASE_URL = "https://api.stackexchange.com/2.2/";
    private static RetrofitClient mInstance;
    private Retrofit retrofit;


    private RetrofitClient() {
        retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }

    public static synchronized RetrofitClient getInstance() {
        if (mInstance == null) {
            mInstance = new RetrofitClient();
        }
        return mInstance;
    }

    public Api getApi() {
        return retrofit.create(Api.class);
    }
}

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

ایجاد API

  • حالا ما رابط فراخوان API را ایجاد میکنیم.
  • یک رابط به نام Api ایجاد کرده و کد زیر را می نویسیم.
package net.simplifiedcoding.androidpagingexample;

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;

public interface Api {

    @GET("answers")
    Call<StackApiResponse> getAnswers(@Query("page") int page, @Query("pagesize") int pagesize, @Query("site") String site);
}

ایجاد RecyclerView

  • همانطور که قبلا گفته بودیم این لیست را در RecyclerView نمایش خواهیم داد . به  activity_main.xml بروید و RecyclerView را در اینجا تعریف کنید.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</android.support.constraint.ConstraintLayout>
  • حالا برای RecyclerView یک فایل لایوت به نام recyclerview_users ایجاد کنید و کد  xml زیر را بنویسید.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">


    <ImageView
        android:id="@+id/imageView"
        android:layout_width="70dp"
        android:layout_height="70dp" />


    <TextView
        android:id="@+id/textViewName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginLeft="15dp"
        android:layout_toRightOf="@id/imageView"
        android:text="Belal Khan"
        android:textAppearance="@style/Base.TextAppearance.AppCompat.Large"
        android:textColor="@color/colorDefault" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_below="@id/imageView"
        android:background="@color/colorDefault" />

</RelativeLayout>

ایجاد  PagedListAdapter 

برای خواندن صفحه ی داده ها از RecyclerView.Adapter استفاده نخواهیم کرد  و به جای آن از  pageListAdapter استفاده خواهیم کرد. این کلاسی هست که کارهایی مثل شمارش آیتم , صفحه فراخوانی (callback) و غیره را انجام میدهد . و نکته این است که به عنوان رابط برای RecyclerView ما خواهد بود.

  • یک کلاس به نام ItemAdapter ایجاد کنید و کد زیر را بنویسید.
package net.simplifiedcoding.androidpagingexample;

import android.arch.paging.PagedListAdapter;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.util.DiffUtil;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.bumptech.glide.Glide;

public class ItemAdapter extends PagedListAdapter<Item, ItemAdapter.ItemViewHolder> {

    private Context mCtx;

    ItemAdapter(Context mCtx) {
        super(DIFF_CALLBACK);
        this.mCtx = mCtx;
    }

    @NonNull
    @Override
    public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(mCtx).inflate(R.layout.recyclerview_users, parent, false);
        return new ItemViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) {
        Item item = getItem(position);

        if (item != null) {
            holder.textView.setText(item.owner.display_name);
            Glide.with(mCtx)
                    .load(item.owner.profile_image)
                    .into(holder.imageView);
        }else{
            Toast.makeText(mCtx, "Item is null", Toast.LENGTH_LONG).show();
        }
    }

    private static DiffUtil.ItemCallback<Item> DIFF_CALLBACK =
            new DiffUtil.ItemCallback<Item>() {
                @Override
                public boolean areItemsTheSame(Item oldItem, Item newItem) {
                    return oldItem.question_id == newItem.question_id;
                }

                @Override
                public boolean areContentsTheSame(Item oldItem, Item newItem) {
                    return oldItem.equals(newItem);
                }
            };

    class ItemViewHolder extends RecyclerView.ViewHolder {

        TextView textView;
        ImageView imageView;

        public ItemViewHolder(View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.textViewName);
            imageView = itemView.findViewById(R.id.imageView);
        }
    }
}

آداپتر تقریبا شبیه<> RecyclerView.Adapter است.تنها تغییر اینجا این است که ما  پیاده سازی Diff_CallBack  را داریم که برایsuper()  استفاده میکنیم هستیم . این فراخوانی برای متمایز کردن دو مورد در یک لیست استفاده میشود.

  • برای <> pagedListAdapter ما Item   و  viewholder  را تعریف می کنیم . Item موردی هست که شما میخواهید نمایش داده شود و ما یک کلاس به این نام داریم که حاوی اطلاعاتی است که برای نمایش نیاز داریم.

ایجاد منبع داه Item

منبع داده item هایمان از جایی است که داده های واقعی را قرار دارد خوانده می شود و همانطور که میدانید که ما از StackOverFlow  Api استفاده میکنیم.

برای ایجاد منبع داده گزینه های زیادی مانند :ItemKeyedDataSource, PageKeyedDataSource, PositionalDataSource    داریم.

برای مثال ما قصد داریم ازPageKeyedDataSource استفاده کنیم . و در Api باید برای آوردن صفحه ای که میخواهیم  شماره صفحه را بفرستیم . پس در اینجا شماره صفحه می شود کلید صفحه ی ما .

  • یک کلاس با نام ItemDataSource ایجاد کنید و کد زیر را بنویسید.
package net.simplifiedcoding.androidpagingexample;

import android.arch.paging.PageKeyedDataSource;
import android.support.annotation.NonNull;
import android.util.Log;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class ItemDataSource extends PageKeyedDataSource<Integer, Item> {

    //the size of a page that we want
    public static final int PAGE_SIZE = 50;

    //we will start from the first page which is 1
    private static final int FIRST_PAGE = 1;

    //we need to fetch from stackoverflow
    private static final String SITE_NAME = "stackoverflow";


    //this will be called once to load the initial data
    @Override
    public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull final LoadInitialCallback<Integer, Item> callback) {
        RetrofitClient.getInstance()
                .getApi().getAnswers(FIRST_PAGE, PAGE_SIZE, SITE_NAME)
                .enqueue(new Callback<StackApiResponse>() {
                    @Override
                    public void onResponse(Call<StackApiResponse> call, Response<StackApiResponse> response) {
                        if (response.body() != null) {
                            callback.onResult(response.body().items, null, FIRST_PAGE + 1);
                        }
                    }

                    @Override
                    public void onFailure(Call<StackApiResponse> call, Throwable t) {

                    }
                });
    }

    //this will load the previous page
    @Override
    public void loadBefore(@NonNull final LoadParams<Integer> params, @NonNull final LoadCallback<Integer, Item> callback) {
        RetrofitClient.getInstance()
                .getApi().getAnswers(params.key, PAGE_SIZE, SITE_NAME)
                .enqueue(new Callback<StackApiResponse>() {
                    @Override
                    public void onResponse(Call<StackApiResponse> call, Response<StackApiResponse> response) {

                        //if the current page is greater than one
                        //we are decrementing the page number
                        //else there is no previous page
                        Integer adjacentKey = (params.key > 1) ? params.key - 1 : null;
                        if (response.body() != null) {

                            //passing the loaded data
                            //and the previous page key 
                            callback.onResult(response.body().items, adjacentKey);
                        }
                    }

                    @Override
                    public void onFailure(Call<StackApiResponse> call, Throwable t) {

                    }
                });
    }

    //this will load the next page
    @Override
    public void loadAfter(@NonNull final LoadParams<Integer> params, @NonNull final LoadCallback<Integer, Item> callback) {
        RetrofitClient.getInstance()
                .getApi()
                .getAnswers(params.key, PAGE_SIZE, SITE_NAME)
                .enqueue(new Callback<StackApiResponse>() {
                    @Override
                    public void onResponse(Call<StackApiResponse> call, Response<StackApiResponse> response) {
                        
                        if (response.body() != null) {
                            //if the response has next page
                            //incrementing the next page number
                            Integer key = response.body().has_more ? params.key + 1 : null; 
                            
                            //passing the loaded data and next page value 
                            callback.onResult(response.body().items, key);
                        }
                    }

                    @Override
                    public void onFailure(Call<StackApiResponse> call, Throwable t) {

                    }
                });
    }
}

ممکن است کد بالا مشکل به نظر برسد اما مهمترین بخش پروژه ما هست . پس با هم مرور میکنیم.

  • کلاس بالا   از کلاس <  PageKeyedDataSource<Integer, Item ارث بری دارد . Integer در اینجا کلید صفحه را تعریف می کند که در اینجا  ما عددی صحیح استفاده می کنیم . هر بار که ما یک صفحه جدید از API میخواهیم باید شماره صفحه ای که میخواهیم  که یک عدد صحیح است را بفرستیم . Item موردی است که ما از API بدست خواهیم آورد .از قبل یک کلاس به نام Item داریم.
  • سپس اندازه یک صفحه را تعریف کردیم که ۵۰ هست , شماره صفحه اول که ۱ هست و sitename جایی هست که میخواهیم اطلاعات را بدست آوریم اگر میخواهید این مقادیر را تغییر دهید.
  • ما سه متد را overridden کردیم :

() loadInitials: این متد داده های اولیه را بار خواهد کرد.

() loadBefore:  این متد صفحه قبلی را بار خواهد کرد.

() loadAfter:  این متد صفحه بعد  را بار خواهد کرد.

ایجاد Item Data Source Factory

ما قصد داریم از <> MutableLiveData برای ذخیره  PageKeyedDataSource مان استفاده کنیم و برای اینکار به  DataSource.Factory نیاز داریم.

package net.simplifiedcoding.androidpagingexample;

import android.arch.lifecycle.MutableLiveData;
import android.arch.paging.DataSource;
import android.arch.paging.PageKeyedDataSource;

public class ItemDataSourceFactory extends DataSource.Factory {

    //creating the mutable live data
    private MutableLiveData<PageKeyedDataSource<Integer, Item>> itemLiveDataSource = new MutableLiveData<>();

    @Override
    public DataSource<Integer, Item> create() {
        //getting our data source object
        ItemDataSource itemDataSource = new ItemDataSource();

        //posting the datasource to get the values
        itemLiveDataSource.postValue(itemDataSource);
        
        //returning the datasource
        return itemDataSource;
    }

    
    //getter for itemlivedatasource
    public MutableLiveData<PageKeyedDataSource<Integer, Item>> getItemLiveDataSource() {
        return itemLiveDataSource;
    }
}

ایجاد  ViewModel

  • یک کلاس با نام  ItemViewModel ایجاد کنید و کد زیر را بنویسید:
package net.simplifiedcoding.androidpagingexample;

import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.ViewModel;
import android.arch.paging.LivePagedListBuilder;
import android.arch.paging.PageKeyedDataSource;
import android.arch.paging.PagedList;

public class ItemViewModel extends ViewModel {

    //creating livedata for PagedList  and PagedKeyedDataSource
    LiveData<PagedList<Item>> itemPagedList;
    LiveData<PageKeyedDataSource<Integer, Item>> liveDataSource;

    //constructor
    public ItemViewModel() {
        //getting our data source factory
        ItemDataSourceFactory itemDataSourceFactory = new ItemDataSourceFactory();

        //getting the live data source from data source factory
        liveDataSource = itemDataSourceFactory.getItemLiveDataSource();

        //Getting PagedList config
        PagedList.Config pagedListConfig =
                (new PagedList.Config.Builder())
                        .setEnablePlaceholders(false)
                        .setPageSize(ItemDataSource.PAGE_SIZE).build();

        //Building the paged list
        itemPagedList = (new LivePagedListBuilder(itemDataSourceFactory, pagedListConfig))
                .build();
    }
}

حالا پروژه خود را بازسازی (rebuild) کنید

نمایش Paged List

  • در نهایت به MainActivity.java بروید و کد زیر را بنویسید:
package net.simplifiedcoding.androidpagingexample;

import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProvider;
import android.arch.lifecycle.ViewModelProviders;
import android.arch.paging.PagedList;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.widget.Toast;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class MainActivity extends AppCompatActivity {

    //getting recyclerview
    private RecyclerView recyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        
        //setting up recyclerview
        recyclerView = findViewById(R.id.recyclerview);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setHasFixedSize(true);

        //getting our ItemViewModel
        ItemViewModel itemViewModel = ViewModelProviders.of(this).get(ItemViewModel.class);

        //creating the Adapter
        final ItemAdapter adapter = new ItemAdapter(this);

        
        //observing the itemPagedList from view model
        itemViewModel.itemPagedList.observe(this, new Observer<PagedList<Item>>() {
            @Override
            public void onChanged(@Nullable PagedList<Item> items) {
                
                //in case of any changes 
                //submitting the items to adapter
                adapter.submitList(items);
            }
        });

        //setting the adapter 
        recyclerView.setAdapter(adapter);
    }
}

حالا پروژه خود را اجرا کنید.

توجه: اگر هیچ آیتمی را نمی بینید مطمین شوید که  نشانی اینترنتی API به درستی کار می کند( با باز کردن نشانی اینترنتی در مرورگرتان ).گاهی اوقات  stackoverflow در خواستهای پیوسته به URLs را مسدود می کند.

دانلود پروژه کتابخانه Paging در اندروید

جهت شرکت در دوره های آموزش اندروید مشهد و آموزش طراحی سایت مشهد می توانید از طریق شماره تماس های پایین ما در ارتباط باشید . 

ورکشاپ رایگان دوره های تخصصی برنامه نویسی

شما این فرصت را دارید، با تکمیل فرم زیر، قبل از انتخاب دوره آموزشی مناسب خود، در ورکشاپ رایگان دوره های تخصصی برنامه نویسی شرکت کنید
  • این فیلد برای اعتبار سنجی است و باید بدون تغییر باقی بماند .

درباره‌ی دولت آبادی

همچنین ببینید

گزارش دوره اندروید

گزارش دوره آموزش اندروید – جلسه هشتم

جسله هشتم از دوره آموزش اندروید برگزار گردید . مهندس آذرنیوا مدرس دوره به معرفی …

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *