نمای کلی کتابخانه Paging 2   بخشی از Android Jetpack.

«کتابخانه صفحه‌بندی» به شما کمک می‌کند تا بخش‌های کوچکی از داده‌ها را به‌طور هم‌زمان بار کنید و نمایش دهید. بار کردن داده‌های جزئی درصورت درخواست، استفاده از پهنای باند شبکه و منابع سیستم را کاهش می‌دهد.

این راهنما چندین نمونه مفهومی از کتابخانه را به‌همراه نمای کلی از نحوه عملکرد آن ارائه می‌دهد. برای مشاهده نمونه‌های کامل از نحوه عملکرد این کتابخانه، codelab و نمونه‌های موجود در بخش منابع اضافی را امتحان کنید.

راه‌اندازی

برای وارد کردن مؤلفه‌های «صفحه‌بندی» به برنامه Android، وابستگی‌های زیر را به فایل build.gradle برنامه‌تان اضافه کنید:

شیک

dependencies {
  def paging_version = "2.1.2"

  implementation "androidx.paging:paging-runtime:$paging_version" // For Kotlin use paging-runtime-ktx

  // alternatively - without Android dependencies for testing
  testImplementation "androidx.paging:paging-common:$paging_version" // For Kotlin use paging-common-ktx

  // optional - RxJava support
  implementation "androidx.paging:paging-rxjava2:$paging_version" // For Kotlin use paging-rxjava2-ktx
}

کاتلین

dependencies {
  val paging_version = "2.1.2"

  implementation("androidx.paging:paging-runtime:$paging_version") // For Kotlin use paging-runtime-ktx

  // alternatively - without Android dependencies for testing
  testImplementation("androidx.paging:paging-common:$paging_version") // For Kotlin use paging-common-ktx

  // optional - RxJava support
  implementation("androidx.paging:paging-rxjava2:$paging_version") // For Kotlin use paging-rxjava2-ktx
}

معماری کتابخانه

این بخش مؤلفه‌های اصلی کتابخانه صفحه‌بندی را شرح می‌دهد و نشان می‌دهد.

PagedList

جزء کلیدی «کتابخانه صفحه‌بندی» کلاس PagedList است که بخش‌هایی از داده‌های برنامه شما یا صفحه‌ها را بار می‌کند. وقتی به داده‌های بیشتری نیاز باشد، این داده‌ها به شیء PagedList موجود صفحه‌بندی می‌شوند. اگر هرکدام از داده‌های بارشده تغییر کند، نمونه جدیدی از PagedList از LiveData یا شیء مبتنی بر RxJava2 به نگهدارنده داده‌های دیده‌شدنی ارسال می‌شود. با تولید اشیاء PagedList، میانای کاربر برنامه شما محتوای آن‌ها را ارائه می‌دهد، درحالی‌که به چرخه‌های حیات کنترل‌کننده‌های میانای کاربر شما احترام می‌گذارد.

تکه‌کد زیر نشان می‌دهد که چگونه می‌توانید مدل نمای برنامه خود را برای بار کردن و ارائه داده‌ها بااستفاده از نگهدارنده LiveData از اشیاء PagedList پیکربندی کنید:

کاتلین

class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {
    val concertList: LiveData<PagedList<Concert>> =
            concertDao.concertsByDate().toLiveData(pageSize = 50)
}

جاوا

public class ConcertViewModel extends ViewModel {
    private ConcertDao concertDao;
    public final LiveData<PagedList<Concert>> concertList;

    // Creates a PagedList object with 50 items per page.
    public ConcertViewModel(ConcertDao concertDao) {
        this.concertDao = concertDao;
        concertList = new LivePagedListBuilder<>(
                concertDao.concertsByDate(), 50).build();
    }
}

داده

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

مثال زیر از کتابخانه پایداری Room برای سازمان‌دهی داده‌های برنامه‌تان استفاده می‌کند، اما اگر می‌خواهید داده‌هایتان را بااستفاده از روش دیگری ذخیره کنید، می‌توانید کارخانه منبع داده خودتان را نیز ارائه دهید.

کاتلین

@Dao
interface ConcertDao {
    // The Int type parameter tells Room to use a PositionalDataSource object.
    @Query("SELECT * FROM concerts ORDER BY date DESC")
    fun concertsByDate(): DataSource.Factory<Int, Concert>
}

جاوا

@Dao
public interface ConcertDao {
    // The Integer type parameter tells Room to use a
    // PositionalDataSource object.
    @Query("SELECT * FROM concerts ORDER BY date DESC")
    DataSource.Factory<Integer, Concert> concertsByDate();
}

برای کسب اطلاعات بیشتر درباره نحوه بار کردن داده‌ها در اشیای PagedList، راهنمای نحوه بار کردن داده‌های صفحه‌بندی‌شده را ببینید.

رابط کاربری

کلاس PagedList با PagedListAdapter کار می‌کند تا موارد را در RecyclerView بار کند. این کلاس‌ها باهم کار می‌کنند تا محتوا را هنگام بار شدن واکشی و نمایش دهند، محتوای خارج از دید را پیش‌واکشی کنند، و تغییرات محتوا را پویانمایی کنند.

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

پشتیبانی از معماری‌های داده مختلف

«کتابخانه صفحه‌بندی» از معماری‌های داده زیر پشتیبانی می‌کند:

  • فقط از سرور زیرینه ارائه می‌شود.
  • فقط در پایگاه داده درون‌دستگاهی ذخیره می‌شود.
  • ترکیبی از منابع دیگر، بااستفاده از پایگاه داده درون‌دستگاهی به‌عنوان حافظه نهان.

شکل ۱ نشان می‌دهد که داده‌ها در هریک از این سناریوهای معماری چگونه جریان می‌یابند. در مورد راه‌حل فقط شبکه یا فقط پایگاه داده، داده‌ها مستقیماً به مدل UI برنامه شما جریان می‌یابند. اگر از رویکرد ترکیبی استفاده می‌کنید، داده‌ها از سرور زیرینه شما به پایگاه داده درون‌دستگاهی و سپس به مدل واسط کاربر برنامه‌تان منتقل می‌شود. گاهی اوقات، نقطه پایانی هر جریان داده برای بار کردن داده‌ها تمام می‌شود، در این مرحله، داده‌های بیشتری را از مؤلفه‌ای که داده‌ها را ارائه کرده است درخواست می‌کند. برای مثال، وقتی داده‌های پایگاه داده درون‌دستگاهی تمام می‌شود، داده‌های بیشتری از سرور درخواست می‌کند.

نمودارهای جریان داده‌ها
شکل ۱. نحوه جریان داده‌ها ازطریق هریک از معماری‌هایی که «کتابخانه صفحه‌بندی» پشتیبانی می‌کند

بقیه این بخش توصیه‌هایی برای پیکربندی هر مورد استفاده از جریان داده ارائه می‌دهد.

فقط شبکه

برای نمایش داده‌های سرور زیرینه، از نسخه همگام Retrofit API برای بار کردن اطلاعات در شیء DataSource سفارشی خودتان استفاده کنید.

فقط پایگاه داده

RecyclerView خود را برای مشاهده فضای ذخیره‌سازی محلی، ترجیحاً بااستفاده از کتابخانه پایداری Room، راه‌اندازی کنید. به این ترتیب، هرگاه داده‌ای در پایگاه داده برنامه‌تان درج یا اصلاح شود، این تغییرات به‌طور خودکار در RecyclerView که این داده‌ها را نمایش می‌دهد منعکس می‌شود.

شبکه و پایگاه داده

پس‌از شروع مشاهده پایگاه داده، می‌توانید بااستفاده از PagedList.BoundaryCallback به زمان اتمام داده‌های پایگاه داده گوش دهید. سپس می‌توانید موارد بیشتری را از شبکه‌تان واکشی کنید و آن‌ها را در پایگاه داده درج کنید. اگر واسط کاربر شما پایگاه داده را مشاهده می‌کند، این تنها کاری است که باید انجام دهید.

رسیدگی به خطاهای شبکه

When using a network to fetch or page the data that you're displaying using the Paging Library, it's important to not treat the network as being either "available" or "unavailable" all the time, as many connections are intermittent or flaky:

  • ممکن است سرور خاصی به درخواست شبکه پاسخ ندهد.
  • ممکن است دستگاه به شبکه‌ای متصل باشد که کند یا ضعیف است.

درعوض، برنامه‌تان باید هر درخواست را ازنظر وجود خطا بررسی کند و در مواردی که شبکه دردسترس نیست، به بهترین شکل ممکن بازیابی شود. برای مثال، می‌توانید دکمه «امتحان مجدد» را برای کاربران ارائه دهید تا درصورت کار نکردن مرحله بازآوری داده، آن را انتخاب کنند. اگر درطول مرحله صفحه‌بندی داده‌ها خطایی رخ دهد، بهتر است درخواست‌های صفحه‌بندی را به‌طور خودکار دوباره امتحان کنید.

به‌روزرسانی برنامه موجود

اگر برنامه‌تان ازقبل داده‌ها را از پایگاه داده یا منبع پس‌بخش مصرف می‌کند، می‌توانید مستقیماً به عملکردی که «کتابخانه صفحه‌بندی» ارائه می‌دهد ارتقا دهید. این بخش نحوه ارتقا دادن برنامه‌ای را نشان می‌دهد که طراحی رایج موجود دارد.

راه‌حل‌های صفحه‌بندی سفارشی

اگر از عملکرد سفارشی برای بار کردن زیرمجموعه‌های کوچک داده از منبع داده برنامه‌تان استفاده می‌کنید، می‌توانید این منطق را با منطق کلاس PagedList جایگزین کنید. نمونه‌های PagedList اتصالات داخلی به منابع داده رایج را ارائه می‌دهند. این نمونه‌ها همچنین آداپتورهایی برای اشیاء RecyclerView ارائه می‌دهند که ممکن است در میانای کاربری برنامه‌تان اضافه کنید.

داده‌ها بااستفاده از فهرست‌ها به‌جای صفحه‌ها بار می‌شود

اگر از فهرست درون‌حافظه‌ای به‌عنوان ساختار داده پشتیبان برای آداپتور میانای کاربری‌تان استفاده می‌کنید، درنظر داشته باشید که اگر تعداد موارد موجود در فهرست زیاد است، به‌روزرسانی‌های داده را بااستفاده از کلاس PagedList مشاهده کنید. نمونه‌های PagedList می‌توانند از LiveData<PagedList> یا Observable<List> برای انتقال به‌روزرسانی‌های داده به میانای کاربر برنامه‌تان استفاده کنند و زمان بار کردن و استفاده از حافظه را به‌حداقل برسانند. بهتر از آن، جایگزین کردن شیء List با شیء PagedList در برنامه‌تان نیازی به هیچ تغییری در ساختار واسط کاربر برنامه یا منطق به‌روزرسانی داده ندارد.

مرتبط کردن مکان‌نمای داده با نمای فهرستی بااستفاده از CursorAdapter

برنامه شما ممکن است از CursorAdapter برای مرتبط کردن داده‌های Cursor با ListView استفاده کند. در این حالت، معمولاً باید از ListView به RecyclerView به‌عنوان محفظه واسط کاربر فهرست برنامه‌تان مهاجرت کنید، سپس مؤلفه Cursor را با Room یا PositionalDataSource جایگزین کنید، بسته به اینکه نمونه‌های Cursor به پایگاه داده SQLite دسترسی دارند یا نه.

در برخی شرایط، مثلاً هنگام کار با نمونه‌های Spinner، فقط خود آداپتور را ارائه می‌دهید. سپس کتابخانه‌ای داده‌های بارشده در آن آداپتور را می‌گیرد و داده‌ها را برای شما نمایش می‌دهد. در این شرایط، نوع داده‌های آداپتور را به LiveData<PagedList> تغییر دهید، سپس این فهرست را در شیء ArrayAdapter بپیچید و بعداز آن تلاش کنید تا کلاس کتابخانه این موارد را در واسط کاربر گسترش دهد.

بار کردن محتوا به‌صورت ناهمگام بااستفاده از AsyncListUtil

اگر از اشیای AsyncListUtil برای بار کردن و نمایش گروه‌های اطلاعات به‌صورت ناهمگام استفاده می‌کنید، «کتابخانه صفحه‌بندی» به شما امکان می‌دهد داده‌ها را راحت‌تر بار کنید:

  • داده‌های شما لازم نیست موقعیتی باشد. «کتابخانه صفحه‌بندی» به شما امکان می‌دهد داده‌ها را مستقیماً از باطن خود بااستفاده از کلیدهایی که شبکه ارائه می‌دهد بار کنید.
  • داده‌های شما می‌تواند بی‌شمار باشد. بااستفاده از «کتابخانه صفحه‌بندی»، می‌توانید داده‌ها را در صفحه‌ها بار کنید تا زمانی که هیچ داده‌ای باقی نماند.
  • می‌توانید داده‌هایتان را راحت‌تر مشاهده کنید. کتابخانه Paging می‌تواند داده‌های شما را که ViewModel برنامه در ساختار داده‌های قابل‌مشاهده نگه می‌دارد ارائه دهد.

نمونه‌های پایگاه داده

تکه‌کدهای زیر چند روش ممکن برای کنار هم قرار دادن همه قطعات را نشان می‌دهد.

مشاهده داده‌های صفحه‌بندی‌شده بااستفاده از LiveData

تکه‌کد زیر نشان می‌دهد که همه بخش‌ها چگونه باهم کار می‌کنند. با اضافه شدن، برداشته شدن، یا تغییر کردن رویدادهای کنسرت در پایگاه داده، محتوای RecyclerView به‌طور خودکار و کارآمد به‌روز می‌شود:

کاتلین

@Dao
interface ConcertDao {
    // The Int type parameter tells Room to use a PositionalDataSource
    // object, with position-based loading under the hood.
    @Query("SELECT * FROM concerts ORDER BY date DESC")
    fun concertsByDate(): DataSource.Factory<Int, Concert>
}

class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {
    val concertList: LiveData<PagedList<Concert>> =
            concertDao.concertsByDate().toLiveData(pageSize = 50)
}

class ConcertActivity : AppCompatActivity() {
    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val viewModel: ConcertViewModel by viewModels()
        val recyclerView = findViewById(R.id.concert_list)
        val adapter = ConcertAdapter()
        viewModel.concertList.observe(this, PagedList(adapter::submitList))
        recyclerView.setAdapter(adapter)
    }
}

class ConcertAdapter() :
        PagedListAdapter<Concert, ConcertViewHolder>(DIFF_CALLBACK) {
    fun onBindViewHolder(holder: ConcertViewHolder, position: Int) {
        val concert: Concert? = getItem(position)

        // Note that "concert" is a placeholder if it's null.
        holder.bindTo(concert)
    }

    companion object {
        private val DIFF_CALLBACK = object :
                DiffUtil.ItemCallback<Concert>() {
            // Concert details may have changed if reloaded from the database,
            // but ID is fixed.
            override fun areItemsTheSame(oldConcert: Concert,
                    newConcert: Concert) = oldConcert.id == newConcert.id

            override fun areContentsTheSame(oldConcert: Concert,
                    newConcert: Concert) = oldConcert == newConcert
        }
    }
}

جاوا

@Dao
public interface ConcertDao {
    // The Integer type parameter tells Room to use a PositionalDataSource
    // object, with position-based loading under the hood.
    @Query("SELECT * FROM concerts ORDER BY date DESC")
    DataSource.Factory<Integer, Concert> concertsByDate();
}

public class ConcertViewModel extends ViewModel {
    private ConcertDao concertDao;
    public final LiveData<PagedList<Concert>> concertList;

    public ConcertViewModel(ConcertDao concertDao) {
        this.concertDao = concertDao;
        concertList = new LivePagedListBuilder<>(
            concertDao.concertsByDate(), /* page size */ 50).build();
    }
}

public class ConcertActivity extends AppCompatActivity {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ConcertViewModel viewModel =
                new ViewModelProvider(this).get(ConcertViewModel.class);
        RecyclerView recyclerView = findViewById(R.id.concert_list);
        ConcertAdapter adapter = new ConcertAdapter();
        viewModel.concertList.observe(this, adapter::submitList);
        recyclerView.setAdapter(adapter);
    }
}

public class ConcertAdapter
        extends PagedListAdapter<Concert, ConcertViewHolder> {
    protected ConcertAdapter() {
        super(DIFF_CALLBACK);
    }

    @Override
    public void onBindViewHolder(@NonNull ConcertViewHolder holder,
            int position) {
        Concert concert = getItem(position);
        if (concert != null) {
            holder.bindTo(concert);
        } else {
            // Null defines a placeholder item - PagedListAdapter automatically
            // invalidates this row when the actual object is loaded from the
            // database.
            holder.clear();
        }
    }

    private static DiffUtil.ItemCallback<Concert> DIFF_CALLBACK =
            new DiffUtil.ItemCallback<Concert>() {
        // Concert details may have changed if reloaded from the database,
        // but ID is fixed.
        @Override
        public boolean areItemsTheSame(Concert oldConcert, Concert newConcert) {
            return oldConcert.getId() == newConcert.getId();
        }

        @Override
        public boolean areContentsTheSame(Concert oldConcert,
                Concert newConcert) {
            return oldConcert.equals(newConcert);
        }
    };
}

مشاهده داده‌های صفحه‌بندی‌شده بااستفاده از RxJava2

اگر ترجیح می‌دهید به‌جای LiveData از RxJava2 استفاده کنید، می‌توانید به‌جای آن شیء Observable یا Flowable ایجاد کنید:

کاتلین

class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {
    val concertList: Observable<PagedList<Concert>> =
            concertDao.concertsByDate().toObservable(pageSize = 50)
}

جاوا

public class ConcertViewModel extends ViewModel {
    private ConcertDao concertDao;
    public final Observable<PagedList<Concert>> concertList;

    public ConcertViewModel(ConcertDao concertDao) {
        this.concertDao = concertDao;

        concertList = new RxPagedListBuilder<>(
                concertDao.concertsByDate(), /* page size */ 50)
                        .buildObservable();
    }
}

سپس می‌توانید بااستفاده از کد موجود در قطعه کد زیر، مشاهده داده‌ها را شروع و متوقف کنید:

کاتلین

class ConcertActivity : AppCompatActivity() {
    private val adapter = ConcertAdapter()

    // Use the 'by viewModels()' Kotlin property delegate
    // from the activity-ktx artifact
    private val viewModel: ConcertViewModel by viewModels()

    private val disposable = CompositeDisposable()

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val recyclerView = findViewById(R.id.concert_list)
        recyclerView.setAdapter(adapter)
    }

    override fun onStart() {
        super.onStart()
        disposable.add(viewModel.concertList
                .subscribe(adapter::submitList)))
    }

    override fun onStop() {
        super.onStop()
        disposable.clear()
    }
}

جاوا

public class ConcertActivity extends AppCompatActivity {
    private ConcertAdapter adapter = new ConcertAdapter();
    private ConcertViewModel viewModel;

    private CompositeDisposable disposable = new CompositeDisposable();

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        RecyclerView recyclerView = findViewById(R.id.concert_list);

        viewModel = new ViewModelProvider(this).get(ConcertViewModel.class);
        recyclerView.setAdapter(adapter);
    }

    @Override
    protected void onStart() {
        super.onStart();
        disposable.add(viewModel.concertList
                .subscribe(adapter.submitList(flowableList)
        ));
    }

    @Override
    protected void onStop() {
        super.onStop();
        disposable.clear();
    }
}

کد ConcertDao و ConcertAdapter برای راه‌حل مبتنی بر RxJava2 و راه‌حل مبتنی بر LiveData یکسان است.

ارائه کردن بازخورد

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

ردیاب مشکل
مشکلات را گزارش کنید تا بتوانیم اشکالات را برطرف کنیم.

منابع بیشتر

برای کسب اطلاعات بیشتر درباره «کتابخانه صفحه‌بندی»، به منابع زیر مراجعه کنید.

نمونه‌ها

Codelabs

ویدیوها