লোডার

Android 9 (API লেভেল ২৮) থেকে লোডার আর ব্যবহার করা হয় না। Activity এবং Fragment লাইফসাইকেল ম্যানেজ করার সময় ডেটা লোড করার সমস্যা সমাধান করার জন্য ViewModel অবজেক্ট এবং LiveData-এর কম্বিনেশন ব্যবহার করার সাজেশন দেওয়া হয়। ভিউ মডেল কনফিগারেশন পরিবর্তন থেকে বেঁচে যায়, যেমন লোডার, কিন্তু কম বয়লারপ্লেট কোড সহ। LiveData ডেটা লোড করার জন্য একটি লাইফসাইকেল-অ্যাওয়ার পদ্ধতি প্রদান করে যা আপনি একাধিক ভিউ মডেলে পুনরায় ব্যবহার করতে পারবেন। এছাড়াও, আপনি MediatorLiveData ব্যবহার করে LiveData একত্রিত করতে পারবেন। রুম ডেটাবেস থেকে পাওয়া কোয়েরির মতো যেকোনও পর্যবেক্ষণযোগ্য কোয়েরি, ডেটাতে হওয়া পরিবর্তন পর্যবেক্ষণ করার জন্য ব্যবহার করা যেতে পারে।

ViewModel এবং LiveData এমন পরিস্থিতিতেও উপলভ্য থাকে যেখানে আপনার কাছে LoaderManager-এর অ্যাক্সেস থাকে না, যেমন Service-এ। দুটি একসাথে ব্যবহার করলে, UI লাইফসাইকেল নিয়ে মাথা না ঘামিয়েই আপনার অ্যাপের প্রয়োজনীয় ডেটা অ্যাক্সেস করার সহজ উপায় পাওয়া যায়। LiveData সম্পর্কে আরও জানতে, LiveData ওভারভিউ দেখুন। ViewModel সম্পর্কে আরও জানতে, ViewModel ওভারভিউ দেখুন।

Loader API আপনাকে কন্টেন্ট প্রোভাইডার বা অন্য ডেটা সোর্স থেকে ডেটা লোড করতে দেয় যাতে তা FragmentActivity বা Fragment-এ দেখানো যায়।

লোডার না থাকলে, আপনি যেসব সমস্যার সম্মুখীন হতে পারেন তার মধ্যে নিম্নলিখিতগুলি অন্তর্ভুক্ত:

  • আপনি যদি সরাসরি অ্যাক্টিভিটি বা ফ্র্যাগমেন্টে ডেটা ফেচ করেন, তাহলে UI থ্রেড থেকে সম্ভাব্য ধীরগতির কোয়েরি পারফর্ম করার কারণে আপনার ব্যবহারকারীরা রেসপন্সিভনেসের অভাবের কারণে সমস্যায় পড়েন।
  • আপনি যদি অন্য কোনও থ্রেড থেকে ডেটা ফেচ করেন, সম্ভবত AsyncTask-এর মাধ্যমে, তাহলে বিভিন্ন অ্যাক্টিভিটি বা ফ্র্যাগমেন্ট লাইফসাইকেল ইভেন্টের মাধ্যমে সেই থ্রেড এবং UI থ্রেড, উভয়ই ম্যানেজ করার দায়িত্ব আপনার, যেমন onDestroy() এবং কনফিগারেশন পরিবর্তন।

লোডার এইসব সমস্যার সমাধান করে এবং অন্যান্য সুবিধা প্রদান করে:

  • UI যাতে স্লো বা রেসপন্সিভ না হয়ে যায়, তার জন্য লোডার আলাদা থ্রেডে রান করে।
  • ইভেন্ট ঘটলে লোডার কলব্যাক পদ্ধতি প্রদান করে থ্রেড ম্যানেজমেন্টকে সহজ করে তোলে।
  • ডুপ্লিকেট কোয়েরি আটকানোর জন্য কনফিগারেশন পরিবর্তন জুড়ে লোডার ফলাফল ধরে রাখে এবং ক্যাশে করে।
  • অন্তর্নিহিত ডেটা সোর্সে পরিবর্তন মনিটর করার জন্য লোডাররা একটি অবজারভার প্রয়োগ করতে পারেন। যেমন, ডেটা পরিবর্তন হলে রিলোড ট্রিগার করার জন্য CursorLoader অটোমেটিক ContentObserver রেজিস্টার করে।

Loader API সারসংক্ষেপ

অ্যাপে লোডার ব্যবহার করার সময় একাধিক ক্লাস ও ইন্টারফেসের প্রয়োজন হতে পারে। নিম্নলিখিত সারণীতে সেগুলির সারসংক্ষেপ দেওয়া হল:

ক্লাস/ইন্টারফেস বর্ণনা
LoaderManager এক বা একাধিক Loader ইনস্ট্যান্স ম্যানেজ করার জন্য FragmentActivity বা Fragment-এর সাথে সম্পর্কিত একটি অ্যাবস্ট্রাক্ট ক্লাস। প্রতিটি অ্যাক্টিভিটি বা ফ্র্যাগমেন্টের জন্য শুধুমাত্র একটি LoaderManager থাকে, তবে একটি LoaderManager একাধিক লোডার ম্যানেজ করতে পারে।

LoaderManager পেতে, অ্যাক্টিভিটি বা ফ্র্যাগমেন্ট থেকে getSupportLoaderManager() কল করুন।

লোডার থেকে ডেটা লোড করা শুরু করতে, initLoader() অথবা restartLoader()-এ কল করুন। সিস্টেম অটোমেটিক নির্ধারণ করে যে একই পূর্ণসংখ্যা আইডি সহ কোনও লোডার আগে থেকেই আছে কিনা এবং নতুন লোডার তৈরি করে বা আগে থেকে থাকা লোডার আবার ব্যবহার করে।

LoaderManager.LoaderCallbacks এই ইন্টারফেসে কলব্যাক পদ্ধতি আছে যা লোডার ইভেন্ট ঘটলে কল করা হয়। ইন্টারফেস তিনটি কলব্যাক পদ্ধতি নির্ধারণ করে:
  • onCreateLoader(int, Bundle): সিস্টেমের নতুন লোডার তৈরি করার প্রয়োজন হলে কল করা হয়। আপনার কোডে, একটি Loader অবজেক্ট তৈরি করুন এবং এটি সিস্টেমে ফেরত পাঠান।
  • onLoadFinished(Loader<D>, D): লোডার ডেটা লোড করা শেষ করলে কল করা হয়। সাধারণত আপনি আপনার কোডে ব্যবহারকারীকে ডেটা দেখান।
  • onLoaderReset(Loader<D>): আগে তৈরি করা লোডার রিসেট করার সময় কল করা হয়, যখন আপনি destroyLoader(int) কল করেন অথবা যখন অ্যাক্টিভিটি বা ফ্র্যাগমেন্ট নষ্ট হয়ে যায়, যার ফলে এর ডেটা উপলভ্য থাকে না। আপনার কোড থেকে, লোডার ডেটার যেকোনও রেফারেন্স সরিয়ে দিন।
আপনার অ্যাক্টিভিটি বা ফ্র্যাগমেন্ট সাধারণত এই ইন্টারফেস প্রয়োগ করে এবং আপনি initLoader() বা restartLoader()-এ কল করলে এটি রেজিস্টার করা হয়।
Loader লোডার ডেটা লোড করে। এই ক্লাসটি অ্যাবস্ট্র্যাক্ট এবং সব লোডারের জন্য মূল ক্লাস হিসেবে কাজ করে। আপনি সরাসরি Loader সাবক্লাস করতে পারেন অথবা প্রয়োগ করার পদ্ধতি সহজ করতে নিম্নলিখিত বিল্ট-ইন সাবক্লাসের মধ্যে একটি ব্যবহার করতে পারেন:
  • AsyncTaskLoader: একটি অ্যাবস্ট্রাক্ট লোডার যা আলাদা থ্রেডে লোড অপারেশন পারফর্ম করার জন্য AsyncTask প্রদান করে।
  • CursorLoader: ContentProvider থেকে অ্যাসিঙ্ক্রোনাসভাবে ডেটা লোড করার জন্য AsyncTaskLoader-এর একটি কংক্রিট সাবক্লাস। এটি একটি ContentResolver কোয়েরি করে এবং একটি Cursor রিটার্ন করে।

নিম্নলিখিত বিভাগগুলি আপনাকে দেখায় যে কীভাবে কোনও অ্যাপ্লিকেশনে এই ক্লাস এবং ইন্টারফেসগুলি ব্যবহার করতে হয়।

অ্যাপ্লিকেশনে লোডার ব্যবহার করা

এই বিভাগে Android অ্যাপ্লিকেশনে কীভাবে লোডার ব্যবহার করতে হয় তা বর্ণনা করা হয়েছে। যেসব অ্যাপ্লিকেশন লোডার ব্যবহার করে, সেগুলিতে সাধারণত নিম্নলিখিত বিষয়গুলি থাকে:

  • FragmentActivity বা Fragment।
  • LoaderManager-এর একটি ইনস্ট্যান্স।
  • ContentProvider-এর মাধ্যমে ব্যাক-আপ নেওয়া ডেটা লোড করার জন্য CursorLoader। অন্যথায়, অন্য কোনও সোর্স থেকে ডেটা লোড করতে, আপনি Loader বা AsyncTaskLoader-এর নিজস্ব সাবক্লাস প্রয়োগ করতে পারেন।
  • LoaderManager.LoaderCallbacks-এর জন্য একটি বাস্তবায়ন। এখানে আপনি নতুন লোডার তৈরি করতে এবং আগে থেকে থাকা লোডারের রেফারেন্স ম্যানেজ করতে পারবেন।
  • লোডার ডেটা দেখানোর একটি উপায়, যেমন SimpleCursorAdapter।
  • CursorLoader ব্যবহার করার সময়, ContentProvider-এর মতো ডেটা সোর্স।

লোডার শুরু করুন

LoaderManager, FragmentActivity বা Fragment-এর মধ্যে এক বা একাধিক Loader ইনস্ট্যান্স ম্যানেজ করে। প্রতিটি অ্যাক্টিভিটি বা ফ্র্যাগমেন্টে শুধুমাত্র একটি LoaderManager থাকে।

আপনি সাধারণত অ্যাক্টিভিটির onCreate() পদ্ধতি বা ফ্র্যাগমেন্টের onCreate() পদ্ধতির মধ্যে Loader শুরু করেন। আপনি নিম্নলিখিত উপায়ে এটি করতে পারেন:

Kotlin

supportLoaderManager.initLoader(0, null, this)

জাভা

// Prepare the loader.  Either re-connect with an existing one,
// or start a new one.
getSupportLoaderManager().initLoader(0, null, this);

initLoader() পদ্ধতি নিম্নলিখিত প্যারামিটার গ্রহণ করে:

  • একটি অনন্য আইডি যা লোডারকে শনাক্ত করে। এই উদাহরণে, আইডি হল 0।
  • কনস্ট্রাকশনে লোডারকে সরবরাহ করার জন্য ঐচ্ছিক আর্গুমেন্ট (এই উদাহরণে null)।
  • একটি LoaderManager.LoaderCallbacks প্রয়োগ, যা LoaderManager লোডার ইভেন্ট রিপোর্ট করার জন্য কল করে। এই উদাহরণে, লোকাল ক্লাস LoaderManager.LoaderCallbacks ইন্টারফেস প্রয়োগ করে, তাই এটি নিজের একটি রেফারেন্স পাস করে, this।

initLoader() কল নিশ্চিত করে যে লোডার ইনিশিয়ালাইজ করা হয়েছে এবং অ্যাক্টিভ আছে। এর দুটি সম্ভাব্য ফলাফল আছে:

  • আইডি দ্বারা নির্দিষ্ট করা লোডার আগে থেকেই থাকলে, শেষবার তৈরি করা লোডার আবার ব্যবহার করা হয়।
  • আইডি দ্বারা নির্দিষ্ট করা লোডার যদি না থাকে, initLoader() ট্রিগার করে LoaderManager.LoaderCallbacks মেথড onCreateLoader()। এখানে আপনি নতুন লোডার ইনস্ট্যান্টিয়েট ও রিটার্ন করার জন্য কোড প্রয়োগ করেন। আরও আলোচনার জন্য, onCreateLoader সম্পর্কে বিভাগটি দেখুন।

উভয় ক্ষেত্রেই, প্রদত্ত LoaderManager.LoaderCallbacks ইমপ্লিমেন্টেশন লোডারের সাথে যুক্ত থাকে এবং লোডারের স্ট্যাটাস পরিবর্তন হলে কল করা হয়। এই কল করার সময়, কলার যদি শুরু করার অবস্থায় থাকে এবং অনুরোধ করা লোডার আগে থেকেই থাকে এবং তার ডেটা জেনারেট করে থাকে, তাহলে সিস্টেম initLoader() চলাকালীন অবিলম্বে onLoadFinished()-কে কল করে। এটি হওয়ার জন্য আপনাকে প্রস্তুত থাকতে হবে। এই কলব্যাক সম্পর্কে আরও আলোচনার জন্য, onLoadFinished সম্পর্কে বিভাগটি দেখুন।

initLoader() পদ্ধতিটি তৈরি করা Loader রিটার্ন করে, তবে আপনাকে এটির রেফারেন্স ক্যাপচার করতে হবে না। LoaderManager অটোমেটিক লোডার ম্যানেজ করে। LoaderManager প্রয়োজন অনুযায়ী লোড করা শুরু ও বন্ধ করে এবং লোডার ও তার সাথে সম্পর্কিত কন্টেন্টের স্ট্যাটাস বজায় রাখে।

এর অর্থ হল, আপনি খুব কমই সরাসরি লোডারের সাথে ইন্টার‍্যাক্ট করেন। নির্দিষ্ট ইভেন্ট ঘটলে লোডিং প্রসেসে হস্তক্ষেপ করার জন্য আপনি সাধারণত LoaderManager.LoaderCallbacks পদ্ধতি ব্যবহার করেন। এই বিষয়ে আরও আলোচনার জন্য, LoaderManager কলব্যাক ব্যবহার করা বিভাগ দেখুন।

লোডার রিস্টার্ট করা

আপনি initLoader() ব্যবহার করলে, আগের বিভাগে যেমন দেখানো হয়েছে, এটি নির্দিষ্ট আইডি সহ আগে থেকে থাকা লোডার ব্যবহার করে, যদি সেটি থাকে। না থাকলে, এটি একটি তৈরি করে। কিন্তু কখনও কখনও আপনি পুরনো ডেটা মুছে দিয়ে নতুন করে শুরু করতে চান।

আপনার পুরনো ডেটা বাতিল করতে, restartLoader() ব্যবহার করুন। যেমন, ব্যবহারকারীর কোয়েরি পরিবর্তন হলে SearchView.OnQueryTextListener-এর নিম্নলিখিত প্রয়োগ লোডার রিস্টার্ট করে। লোডারকে রিস্টার্ট করতে হবে যাতে এটি নতুন কোয়েরি করার জন্য সংশোধিত সার্চ ফিল্টার ব্যবহার করতে পারে।

Kotlin

fun onQueryTextChanged(newText: String?): Boolean {
    // Called when the action bar search text has changed.  Update
    // the search filter and restart the loader to do a new query
    // with this filter.
    curFilter = if (newText?.isNotEmpty() == true) newText else null
    supportLoaderManager.restartLoader(0, null, this)
    return true
}

জাভা

public boolean onQueryTextChanged(String newText) {
    // Called when the action bar search text has changed.  Update
    // the search filter, and restart the loader to do a new query
    // with this filter.
    curFilter = !TextUtils.isEmpty(newText) ? newText : null;
    getSupportLoaderManager().restartLoader(0, null, this);
    return true;
}

LoaderManager কলব্যাক ব্যবহার করুন

LoaderManager.LoaderCallbacks হল একটি কলব্যাক ইন্টারফেস যা ক্লায়েন্টকে LoaderManager-এর সাথে ইন্টার‍্যাক্ট করতে দেয়।

লোডার, বিশেষ করে CursorLoader, বন্ধ হওয়ার পরে তাদের ডেটা ধরে রাখবে বলে আশা করা হচ্ছে। এটি অ্যাপ্লিকেশনকে অ্যাক্টিভিটি বা ফ্র্যাগমেন্টের onStop() এবং onStart() মেথড জুড়ে তার ডেটা রাখতে দেয়, যাতে ব্যবহারকারীরা যখন কোনও অ্যাপ্লিকেশনে ফিরে আসেন, তখন তাদের ডেটা রিলোড হওয়ার জন্য অপেক্ষা করতে না হয়।

কখন নতুন লোডার তৈরি করতে হবে এবং কখন লোডারের ডেটা ব্যবহার করা বন্ধ করতে হবে তা অ্যাপ্লিকেশনকে জানাতে আপনি LoaderManager.LoaderCallbacks পদ্ধতি ব্যবহার করেন।

LoaderManager.LoaderCallbacks-এ এইসব পদ্ধতি অন্তর্ভুক্ত:

  • onCreateLoader(): প্রদত্ত আইডির জন্য নতুন Loader ইনস্ট্যান্টিয়েট করে এবং ফিরিয়ে দেয়।
  • onLoadFinished(): আগে তৈরি করা লোডার তার লোড শেষ করলে কল করা হয়।
  • onLoaderReset(): আগে তৈরি করা লোডার রিসেট করার সময় কল করা হয়, যার ফলে এটির ডেটা উপলভ্য থাকে না।

এই পদ্ধতিগুলি নিম্নলিখিত বিভাগে আরও বিস্তারিতভাবে বর্ণনা করা হয়েছে।

onCreateLoader

আপনি যখন কোনও লোডার অ্যাক্সেস করার চেষ্টা করেন, যেমন initLoader()-এর মাধ্যমে, তখন এটি চেক করে দেখে যে আইডি দ্বারা নির্দিষ্ট করা লোডারটি আছে কিনা। এটি না হলে, এটি LoaderManager.LoaderCallbacks মেথড onCreateLoader() ট্রিগার করে। এখানে আপনি নতুন লোডার তৈরি করেন। সাধারণত এটি একটি CursorLoader, তবে আপনি নিজের Loader সাবক্লাস প্রয়োগ করতে পারেন।

নিচের উদাহরণে, onCreateLoader() কলব্যাক পদ্ধতিটি তার কনস্ট্রাক্টর পদ্ধতি ব্যবহার করে একটি CursorLoader তৈরি করে, যার জন্য ContentProvider-এ কোয়েরি করার জন্য প্রয়োজনীয় তথ্যের সম্পূর্ণ সেট প্রয়োজন। বিশেষ করে, নিম্নলিখিত বিষয়গুলি প্রয়োজন:

  • uri: কন্টেন্ট ফিরিয়ে আনার জন্য URI।
  • প্রোজেকশন: কোন কোন কলাম ফেরত দিতে হবে তার তালিকা। null পাস করলে সব কলাম রিটার্ন করে, যা অদক্ষ।
  • বাছাই: কোন সারিগুলি ফেরত দিতে হবে তা ঘোষণা করার একটি ফিল্টার, SQL WHERE ক্লজ হিসাবে ফর্ম্যাট করা হয়েছে (WHERE বাদ দিয়ে)। null পাস করলে, প্রদত্ত URI-এর জন্য সব সারি রিটার্ন করে।
  • selectionArgs: আপনি যদি বেছে নেওয়ার মধ্যে ?s অন্তর্ভুক্ত করেন, তাহলে সেগুলি বেছে নেওয়ার মধ্যে যে ক্রমে দেখা যায় সেই ক্রমে selectionArgs থেকে ভ্যালু দ্বারা প্রতিস্থাপিত হয়। ভ্যালুগুলি স্ট্রিং হিসেবে বাউন্ড করা আছে।
  • sortOrder: কীভাবে সারিগুলি অর্ডার করতে হয়, SQL ORDER BY ক্লজ হিসাবে ফর্ম্যাট করা হয় (ORDER BY বাদ দিয়ে)। null পাস করলে ডিফল্ট ক্রমানুসারে সাজানোর সেটিং ব্যবহার করা হয়, যা ক্রমানুসারে নাও হতে পারে।

Kotlin

// If non-null, this is the current filter the user has provided.
private var curFilter: String? = null
...
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
    // This is called when a new Loader needs to be created.  This
    // sample only has one Loader, so we don't care about the ID.
    // First, pick the base URI to use depending on whether we are
    // currently filtering.
    val baseUri: Uri = if (curFilter != null) {
        Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, Uri.encode(curFilter))
    } else {
        ContactsContract.Contacts.CONTENT_URI
    }

    // Now create and return a CursorLoader that will take care of
    // creating a Cursor for the data being displayed.
    val select: String = "((${Contacts.DISPLAY_NAME} NOTNULL) AND (" +
            "${Contacts.HAS_PHONE_NUMBER}=1) AND (" +
            "${Contacts.DISPLAY_NAME} != ''))"
    return (activity as? Context)?.let { context ->
        CursorLoader(
                context,
                baseUri,
                CONTACTS_SUMMARY_PROJECTION,
                select,
                null,
                "${Contacts.DISPLAY_NAME} COLLATE LOCALIZED ASC"
        )
    } ?: throw Exception("Activity cannot be null")
}

জাভা

// If non-null, this is the current filter the user has provided.
String curFilter;
...
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    // This is called when a new Loader needs to be created.  This
    // sample only has one Loader, so we don't care about the ID.
    // First, pick the base URI to use depending on whether we are
    // currently filtering.
    Uri baseUri;
    if (curFilter != null) {
        baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                  Uri.encode(curFilter));
    } else {
        baseUri = Contacts.CONTENT_URI;
    }

    // Now create and return a CursorLoader that will take care of
    // creating a Cursor for the data being displayed.
    String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
            + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
            + Contacts.DISPLAY_NAME + " != '' ))";
    return new CursorLoader(getActivity(), baseUri,
            CONTACTS_SUMMARY_PROJECTION, select, null,
            Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
}

onLoadFinished

আগে তৈরি করা লোডার তার লোড শেষ করলে এই পদ্ধতিটি কল করা হয়। এই লোডারের জন্য সরবরাহ করা শেষ ডেটা রিলিজ করার আগে এই পদ্ধতিটি কল করার গ্যারান্টি দেওয়া হয়। এই পর্যায়ে, পুরনো ডেটার সব ব্যবহার সরিয়ে দিন, কারণ এটি রিলিজ করা হবে। তবে, আপনি নিজে ডেটা রিলিজ করবেন না—লোডার এটির মালিক এবং এটি দেখভাল করে।

অ্যাপ্লিকেশনটি আর ডেটা ব্যবহার করছে না তা জানতে পারলেই লোডার ডেটা রিলিজ করে দেয়। যেমন, ডেটা যদি CursorLoader-এর কার্সর হয়, তাহলে নিজে থেকে close() কল করবেন না। কার্সর CursorAdapter-এ প্লেস করা হলে, swapCursor() পদ্ধতি ব্যবহার করুন যাতে পুরনো Cursor বন্ধ না হয়ে যায়, যেমনটি নিম্নলিখিত উদাহরণে দেখানো হয়েছে:

Kotlin

private lateinit var adapter: SimpleCursorAdapter
...
override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor?) {
    // Swap the new cursor in. (The framework will take care of closing the
    // old cursor once we return.)
    adapter.swapCursor(data)
}

জাভা

// This is the Adapter being used to display the list's data.
SimpleCursorAdapter adapter;
...
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    // Swap the new cursor in. (The framework will take care of closing the
    // old cursor once we return.)
    adapter.swapCursor(data);
}

onLoaderReset

আগে তৈরি করা লোডার রিসেট করার সময় এই পদ্ধতিকে কল করা হয়, যার ফলে এর ডেটা উপলভ্য থাকে না। এই কলব্যাক আপনাকে ডেটা কখন রিলিজ করা হবে তা জানতে সাহায্য করে যাতে আপনি এটির রেফারেন্স সরাতে পারেন।

এই প্রয়োগ null-এর মান সহ swapCursor() কল করে:

Kotlin

private lateinit var adapter: SimpleCursorAdapter
...
override fun onLoaderReset(loader: Loader<Cursor>) {
    // This is called when the last Cursor provided to onLoadFinished()
    // above is about to be closed.  We need to make sure we are no
    // longer using it.
    adapter.swapCursor(null)
}

জাভা

// This is the Adapter being used to display the list's data.
SimpleCursorAdapter adapter;
...
public void onLoaderReset(Loader<Cursor> loader) {
    // This is called when the last Cursor provided to onLoadFinished()
    // above is about to be closed.  We need to make sure we are no
    // longer using it.
    adapter.swapCursor(null);
}

উদাহরণ

উদাহরণ হিসেবে, এখানে Fragment-এর সম্পূর্ণ প্রয়োগ দেখানো হয়েছে যা ListView দেখায়, এতে পরিচিতি কন্টেন্ট প্রোভাইডারের বিরুদ্ধে কোয়েরির ফলাফল থাকে। এটি পরিষেবা প্রদানকারীর উপর কোয়েরি ম্যানেজ করতে CursorLoader ব্যবহার করে।

যেহেতু এই উদাহরণটি ব্যবহারকারীর পরিচিতি অ্যাক্সেস করার জন্য একটি অ্যাপ্লিকেশন থেকে নেওয়া হয়েছে, তাই এর ম্যানিফেস্টে অবশ্যই অনুমতি অন্তর্ভুক্ত থাকতে হবে READ_CONTACTS.

Kotlin

private val CONTACTS_SUMMARY_PROJECTION: Array<String> = arrayOf(
        Contacts._ID,
        Contacts.DISPLAY_NAME,
        Contacts.CONTACT_STATUS,
        Contacts.CONTACT_PRESENCE,
        Contacts.PHOTO_ID,
        Contacts.LOOKUP_KEY
)


class CursorLoaderListFragment :
        ListFragment(),
        SearchView.OnQueryTextListener,
        LoaderManager.LoaderCallbacks<Cursor> {

    // This is the Adapter being used to display the list's data.
    private lateinit var mAdapter: SimpleCursorAdapter

    // If non-null, this is the current filter the user has provided.
    private var curFilter: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Prepare the loader.  Either re-connect with an existing one,
        // or start a new one.
        loaderManager.initLoader(0, null, this)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // Give some text to display if there is no data.  In a real
        // application, this would come from a resource.
        setEmptyText("No phone numbers")

        // We have a menu item to show in action bar.
        setHasOptionsMenu(true)

        // Create an empty adapter we will use to display the loaded data.
        mAdapter = SimpleCursorAdapter(activity,
                android.R.layout.simple_list_item_2,
                null,
                arrayOf(Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS),
                intArrayOf(android.R.id.text1, android.R.id.text2),
                0
        )
        listAdapter = mAdapter
    }

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        // Place an action bar item for searching.
        menu.add("Search").apply {
            setIcon(android.R.drawable.ic_menu_search)
            setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
            actionView = SearchView(activity).apply {
                setOnQueryTextListener(this@CursorLoaderListFragment)
            }
        }
    }

    override fun onQueryTextChange(newText: String?): Boolean {
        // Called when the action bar search text has changed.  Update
        // the search filter, and restart the loader to do a new query
        // with this filter.
        curFilter = if (newText?.isNotEmpty() == true) newText else null
        loaderManager.restartLoader(0, null, this)
        return true
    }

    override fun onQueryTextSubmit(query: String): Boolean {
        // Don't care about this.
        return true
    }

    override fun onListItemClick(l: ListView, v: View, position: Int, id: Long) {
        // Insert desired behavior here.
        Log.i("FragmentComplexList", "Item clicked: $id")
    }

    override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
        // This is called when a new Loader needs to be created.  This
        // sample only has one Loader, so we don't care about the ID.
        // First, pick the base URI to use depending on whether we are
        // currently filtering.
        val baseUri: Uri = if (curFilter != null) {
            Uri.withAppendedPath(Contacts.CONTENT_URI, Uri.encode(curFilter))
        } else {
            Contacts.CONTENT_URI
        }

        // Now create and return a CursorLoader that will take care of
        // creating a Cursor for the data being displayed.
        val select: String = "((${Contacts.DISPLAY_NAME} NOTNULL) AND (" +
                "${Contacts.HAS_PHONE_NUMBER}=1) AND (" +
                "${Contacts.DISPLAY_NAME} != ''))"
        return (activity as? Context)?.let { context ->
            CursorLoader(
                    context,
                    baseUri,
                    CONTACTS_SUMMARY_PROJECTION,
                    select,
                    null,
                    "${Contacts.DISPLAY_NAME} COLLATE LOCALIZED ASC"
            )
        } ?: throw Exception("Activity cannot be null")
    }

    override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor) {
        // Swap the new cursor in.  (The framework will take care of closing the
        // old cursor once we return.)
        mAdapter.swapCursor(data)
    }

    override fun onLoaderReset(loader: Loader<Cursor>) {
        // This is called when the last Cursor provided to onLoadFinished()
        // above is about to be closed.  We need to make sure we are no
        // longer using it.
        mAdapter.swapCursor(null)
    }
}

জাভা

public static class CursorLoaderListFragment extends ListFragment
        implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {

    // This is the Adapter being used to display the list's data.
    SimpleCursorAdapter mAdapter;

    // If non-null, this is the current filter the user has provided.
    String curFilter;

    @Override public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Prepare the loader.  Either re-connect with an existing one,
        // or start a new one.
        getLoaderManager().initLoader(0, null, this);
    }

    @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        // Give some text to display if there is no data.  In a real
        // application, this would come from a resource.
        setEmptyText("No phone numbers");

        // We have a menu item to show in action bar.
        setHasOptionsMenu(true);

        // Create an empty adapter we will use to display the loaded data.
        mAdapter = new SimpleCursorAdapter(getActivity(),
                android.R.layout.simple_list_item_2, null,
                new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
                new int[] { android.R.id.text1, android.R.id.text2 }, 0);
        setListAdapter(mAdapter);
    }

    @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        // Place an action bar item for searching.
        MenuItem item = menu.add("Search");
        item.setIcon(android.R.drawable.ic_menu_search);
        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
        SearchView sv = new SearchView(getActivity());
        sv.setOnQueryTextListener(this);
        item.setActionView(sv);
    }

    public boolean onQueryTextChange(String newText) {
        // Called when the action bar search text has changed.  Update
        // the search filter, and restart the loader to do a new query
        // with this filter.
        curFilter = !TextUtils.isEmpty(newText) ? newText : null;
        getLoaderManager().restartLoader(0, null, this);
        return true;
    }

    @Override public boolean onQueryTextSubmit(String query) {
        // Don't care about this.
        return true;
    }

    @Override public void onListItemClick(ListView l, View v, int position, long id) {
        // Insert desired behavior here.
        Log.i("FragmentComplexList", "Item clicked: " + id);
    }

    // These are the Contacts rows that we will retrieve.
    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
        Contacts._ID,
        Contacts.DISPLAY_NAME,
        Contacts.CONTACT_STATUS,
        Contacts.CONTACT_PRESENCE,
        Contacts.PHOTO_ID,
        Contacts.LOOKUP_KEY,
    };
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // This is called when a new Loader needs to be created.  This
        // sample only has one Loader, so we don't care about the ID.
        // First, pick the base URI to use depending on whether we are
        // currently filtering.
        Uri baseUri;
        if (curFilter != null) {
            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                    Uri.encode(curFilter));
        } else {
            baseUri = Contacts.CONTENT_URI;
        }

        // Now create and return a CursorLoader that will take care of
        // creating a Cursor for the data being displayed.
        String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
                + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                + Contacts.DISPLAY_NAME + " != '' ))";
        return new CursorLoader(getActivity(), baseUri,
                CONTACTS_SUMMARY_PROJECTION, select, null,
                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
    }

    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        // Swap the new cursor in.  (The framework will take care of closing the
        // old cursor once we return.)
        mAdapter.swapCursor(data);
    }

    public void onLoaderReset(Loader<Cursor> loader) {
        // This is called when the last Cursor provided to onLoadFinished()
        // above is about to be closed.  We need to make sure we are no
        // longer using it.
        mAdapter.swapCursor(null);
    }
}

আরও উদাহরণ

লোডার কীভাবে ব্যবহার করতে হয় তা নিম্নলিখিত উদাহরণ থেকে বুঝতে পারবেন:

  • LoaderCursor: পূর্ববর্তী স্নিপেটের একটি সম্পূর্ণ ভার্সন।
  • পরিচিতির তালিকা ফিরিয়ে আনা: একটি ওয়াকথ্রু যা পরিচিতি প্রদানকারীর থেকে ডেটা ফিরিয়ে আনতে CursorLoader ব্যবহার করে।
  • LoaderThrottle: কন্টেন্ট প্রোভাইডারের ডেটা পরিবর্তন হলে, সেটি যাতে কম সংখ্যক কোয়েরি করে, তার জন্য থ্রটলিং কীভাবে ব্যবহার করতে হয়, তার উদাহরণ।