কন্টেন্ট প্রোভাইডার সম্পর্কিত প্রাথমিক তথ্য

কন্টেন্ট প্রোভাইডার ডেটার সেন্ট্রাল রিপোজিটরিতে অ্যাক্সেস ম্যানেজ করে। প্রোভাইডার হল Android অ্যাপ্লিকেশনের অংশ, যা প্রায়শই ডেটা নিয়ে কাজ করার জন্য নিজস্ব UI প্রদান করে। তবে, কন্টেন্ট প্রোভাইডার মূলত অন্যান্য অ্যাপ্লিকেশন ব্যবহার করে, যা প্রোভাইডার ক্লায়েন্ট অবজেক্ট ব্যবহার করে প্রোভাইডার অ্যাক্সেস করে। প্রোভাইডার ও প্রোভাইডার ক্লায়েন্ট একসাথে ডেটার জন্য একটি সামঞ্জস্যপূর্ণ, স্ট্যান্ডার্ড ইন্টারফেস অফার করে যা ইন্টারপ্রসেস কমিউনিকেশন ও সুরক্ষিত ডেটা অ্যাক্সেসও ম্যানেজ করে।

সাধারণত আপনি দুটি পরিস্থিতির মধ্যে একটিতে কন্টেন্ট প্রোভাইডারের সাথে কাজ করেন: অন্য অ্যাপ্লিকেশনে আগে থেকে থাকা কন্টেন্ট প্রোভাইডার অ্যাক্সেস করার জন্য কোড প্রয়োগ করা বা অন্যান্য অ্যাপ্লিকেশনের সাথে ডেটা শেয়ার করার জন্য আপনার অ্যাপ্লিকেশনে একটি নতুন কন্টেন্ট প্রোভাইডার তৈরি করা।

এই পৃষ্ঠায় আগে থেকে থাকা কন্টেন্ট প্রোভাইডারের সাথে কাজ করার প্রাথমিক তথ্য দেওয়া আছে। আপনার নিজের অ্যাপ্লিকেশনে কন্টেন্ট প্রোভাইডার প্রয়োগ করার ব্যাপারে জানতে, কন্টেন্ট প্রোভাইডার তৈরি করুন দেখুন।

এই বিষয়ে নিম্নলিখিত বিষয়গুলি বর্ণনা করা হয়েছে:

  • কন্টেন্ট প্রোভাইডার কীভাবে কাজ করে।
  • কন্টেন্ট প্রোভাইডারের থেকে ডেটা ফিরিয়ে আনার জন্য আপনি যে API ব্যবহার করেন।
  • কন্টেন্ট প্রোভাইডারে ডেটা যোগ, আপডেট বা মুছে ফেলার জন্য আপনি যে API ব্যবহার করেন।
  • অন্যান্য API ফিচার যা পরিষেবা প্রদানকারীর সাথে কাজ করার সুবিধা প্রদান করে।

এক নজরে

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

কন্টেন্ট প্রোভাইডার আপনার অ্যাপ্লিকেশনের ডেটা স্টোরেজ লেয়ারে অ্যাক্সেস কোঅর্ডিনেট করে, যাতে বিভিন্ন API এবং কম্পোনেন্ট কাজ করতে পারে। ছবি ১-এ যেমন দেখানো হয়েছে, এর মধ্যে নিম্নলিখিত বিষয়গুলি অন্তর্ভুক্ত:

  • অন্যান্য অ্যাপ্লিকেশনের সাথে আপনার অ্যাপ্লিকেশন ডেটার অ্যাক্সেস শেয়ার করা
  • উইজেটে ডেটা পাঠানো হচ্ছে
  • SearchRecentSuggestionsProvider ব্যবহার করে সার্চ ফ্রেমওয়ার্কের মাধ্যমে আপনার অ্যাপ্লিকেশনের জন্য কাস্টম সার্চ সাজেশন ফেরত পাঠানো হচ্ছে
  • AbstractThreadedSyncAdapter-এর ইমপ্লিমেন্টেশন ব্যবহার করে আপনার সার্ভারের সাথে অ্যাপ্লিকেশন ডেটা সিঙ্ক করা হচ্ছে
  • CursorLoader ব্যবহার করে আপনার UI-তে ডেটা লোড করা হচ্ছে
কন্টেন্ট প্রোভাইডার ও অন্যান্য কম্পোনেন্টের মধ্যে সম্পর্ক।

ছবি ১. কন্টেন্ট প্রোভাইডার ও অন্যান্য কম্পোনেন্টের মধ্যে সম্পর্ক।

কোনও পরিষেবা প্রদানকারীকে অ্যাক্সেস করা

আপনি কন্টেন্ট প্রোভাইডারের ডেটা অ্যাক্সেস করতে চাইলে, ক্লায়েন্ট হিসেবে প্রোভাইডারের সাথে যোগাযোগ করার জন্য আপনার অ্যাপ্লিকেশনের Context-এ ContentResolver অবজেক্ট ব্যবহার করেন। ContentResolver অবজেক্টটি প্রোভাইডার অবজেক্টের সাথে যোগাযোগ করে, যা ContentProvider ইমপ্লিমেন্ট করে এমন একটি ক্লাসের ইনস্ট্যান্স।

প্রোভাইডার অবজেক্ট ক্লায়েন্টের থেকে ডেটার অনুরোধ পায়, অনুরোধ করা অ্যাকশন পারফর্ম করে এবং ফলাফল ফেরত পাঠায়। এই অবজেক্টে এমন পদ্ধতি আছে যা প্রোভাইডার অবজেক্টে একই নামের পদ্ধতিকে কল করে, ContentProvider-এর কংক্রিট সাবক্লাসের একটির ইনস্ট্যান্স। ContentResolver পদ্ধতি পারসিস্টেন্ট স্টোরেজের প্রাথমিক "CRUD" (তৈরি, পুনরুদ্ধার, আপডেট ও মুছে ফেলা) ফাংশন প্রদান করে।

আপনার UI থেকে ContentProvider অ্যাক্সেস করার সাধারণ প্যাটার্ন ব্যাকগ্রাউন্ডে অ্যাসিঙ্ক্রোনাস কোয়েরি চালানোর জন্য CursorLoader ব্যবহার করে। আপনার UI-তে Activity বা Fragment কোয়েরির জন্য CursorLoader কল করে, যা ContentResolver ব্যবহার করে ContentProvider পায়।

কোয়েরি চলাকালীন UI যাতে ব্যবহারকারীর কাছে উপলভ্য থাকে তা এটি নিশ্চিত করে। এই প্যাটার্নে বিভিন্ন অবজেক্টের ইন্টার‍্যাকশন এবং অন্তর্নিহিত স্টোরেজ মেকানিজম অন্তর্ভুক্ত থাকে, যেমনটি চিত্র ২-এ দেখানো হয়েছে।

ContentProvider, অন্যান্য ক্লাস ও স্টোরেজের মধ্যে ইন্টার‍্যাকশন।

ছবি ২. ContentProvider, অন্যান্য ক্লাস ও স্টোরেজের মধ্যে ইন্টার‍্যাকশন।

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

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

সারণী ১: ব্যবহারকারীর অভিধানের সারণীর নমুনা।

শব্দ অ্যাপ আইডি ফ্রিকোয়েন্সি লোকেল _ID
mapreduce user1 100 bn_IN 1
precompiler user14 200 fr_FR 2
applet ব্যবহারকারী২ ২২৫ fr_CA 3
const user1 255 pt_BR 4
int ব্যবহারকারী৫ 100 en_UK 5

সারণী ১-এর প্রতিটি সারি এমন একটি শব্দকে দেখায় যা স্ট্যান্ডার্ড অভিধানে পাওয়া যায়নি। প্রতিটি কলাম সেই শব্দের জন্য ডেটার একটি অংশকে উপস্থাপন করে, যেমন লোকেল যেখানে এটি প্রথম দেখা গেছে। কলাম হেডার হল কলামের নাম যা প্রদানকারীর কাছে স্টোর করা থাকে। তাই, কোনও সারির লোকেল উল্লেখ করতে, উদাহরণস্বরূপ, আপনি এটির locale কলাম উল্লেখ করেন। এই প্রদানকারীর জন্য, _ID কলামটি প্রাথমিক কী কলাম হিসেবে কাজ করে যা প্রদানকারী অটোমেটিক বজায় রাখে।

ব্যবহারকারীর ডিকশনারি প্রদানকারীর থেকে শব্দ ও তাদের লোকালের তালিকা পেতে, আপনি ContentResolver.query()-এ কল করুন। query() মেথড, ব্যবহারকারীর অভিধান প্রদানকারীর দ্বারা সংজ্ঞায়িত ContentProvider.query() মেথডকে কল করে। কোডের নিম্নলিখিত লাইনগুলি একটি ContentResolver.query() কল দেখায়:

Kotlin

// Queries the UserDictionary and returns results
cursor = contentResolver.query(
        UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
        projection,                        // The columns to return for each row
        selectionClause,                   // Selection criteria
        selectionArgs.toTypedArray(),      // Selection criteria
        sortOrder                          // The sort order for the returned rows
)

জাভা

// Queries the UserDictionary and returns results
cursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
    projection,                        // The columns to return for each row
    selectionClause,                   // Selection criteria
    selectionArgs,                     // Selection criteria
    sortOrder);                        // The sort order for the returned rows

সারণী ২-এ দেখানো হয়েছে যে কীভাবে query(Uri,projection,selection,selectionArgs,sortOrder)-এর আর্গুমেন্ট SQL SELECT স্টেটমেন্টের সাথে ম্যাচ করে:

সারণী ২: SQL কোয়েরির সাথে query()-এর তুলনা।

query()টি আর্গুমেন্ট কীওয়ার্ড/প্যারামিটার বেছে নিন নোট
Uri FROM table_name Uri, table_name নামের পরিষেবা প্রদানকারীর টেবিলে ম্যাপ করে।
projection col,col,col,... projection হল কলামের একটি অ্যারে যা প্রতিটি সারির জন্য অন্তর্ভুক্ত করা হয়েছে পুনরুদ্ধার করা হয়েছে।
selection WHERE col = value selection সারি বেছে নেওয়ার শর্ত উল্লেখ করে।
selectionArgs একদম একই রকম কিছু নেই। বাছাই সংক্রান্ত আর্গুমেন্ট বাছাই সংক্রান্ত ক্লজের ? প্লেসহোল্ডারকে প্রতিস্থাপন করে।
sortOrder ORDER BY col,col,... sortOrder, Cursor-এ সারিগুলি যে ক্রমে দেখানো হবে তা নির্দিষ্ট করে।

কন্টেন্ট URI

কন্টেন্ট URI হল এমন একটি URI যা কোনও প্রোভাইডারের ডেটা শনাক্ত করে। কন্টেন্ট URI-তে পুরো প্রোভাইডারের প্রতীকী নাম—তার কর্তৃত্ব—এবং একটি নাম যা একটি টেবিলকে নির্দেশ করে—একটি পাথ অন্তর্ভুক্ত থাকে। প্রোভাইডারে কোনও টেবিল অ্যাক্সেস করার জন্য আপনি ক্লায়েন্ট মেথড কল করলে, টেবিলের কন্টেন্ট URI হল আর্গুমেন্টগুলির মধ্যে একটি।

কোডের পূর্ববর্তী লাইনে, কনস্ট্যান্ট CONTENT_URI-এ ব্যবহারকারীর অভিধান প্রদানকারীর Words টেবিলের কন্টেন্ট URI থাকে। ContentResolver অবজেক্ট URI-এর অথরিটি পার্স করে এবং পরিচিত পরিষেবা প্রদানকারীর সিস্টেম টেবিলের সাথে অথরিটি তুলনা করে পরিষেবা প্রদানকারীকে সমাধান করতে এটি ব্যবহার করে। এরপর ContentResolver সঠিক পরিষেবা প্রদানকারীর কাছে কোয়েরি আর্গুমেন্ট পাঠাতে পারে।

ContentProvider অ্যাক্সেস করার জন্য টেবিল বেছে নিতে কন্টেন্ট URI-এর পাথ অংশ ব্যবহার করে। সাধারণত, কোনও প্রদানকারীর কাছে প্রতিটি টেবিলের জন্য একটি করে পাথ থাকে।

কোডের আগের লাইনে, Words টেবিলের সম্পূর্ণ URI হল:

content://user_dictionary/words
  • content:// স্ট্রিং হল স্কিম, যা সবসময় থাকে এবং এটিকে কন্টেন্ট URI হিসেবে শনাক্ত করে।
  • user_dictionary স্ট্রিং হল পরিষেবা প্রদানকারীর কর্তৃত্ব।
  • words স্ট্রিং হল টেবিলের পাথ।

অনেক পরিষেবা প্রদানকারী URI-এর শেষে আইডি ভ্যালু যোগ করে আপনাকে টেবিলের একটি সারি অ্যাক্সেস করতে দেয়। যেমন, ব্যবহারকারীর অভিধান প্রদানকারী থেকে এমন একটি সারি ফিরিয়ে আনতে যার _ID হল 4, আপনি এই কন্টেন্ট URI ব্যবহার করতে পারেন:

Kotlin

val singleUri: Uri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI, 4)

জাভা

Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);

আপনি যখন সারিগুলির একটি সেট পুনরুদ্ধার করেন এবং তারপরে সেগুলির মধ্যে একটি আপডেট বা মুছতে চান তখন আপনি প্রায়শই ID মান ব্যবহার করেন।

মনে রাখবেন: Uri এবং Uri.Builder ক্লাসে স্ট্রিং থেকে সুগঠিত URI অবজেক্ট তৈরি করার জন্য সুবিধাজনক পদ্ধতি রয়েছে। ContentUris ক্লাসে URI-তে আইডি ভ্যালু যোগ করার জন্য সুবিধাজনক পদ্ধতি রয়েছে। আগের স্নিপেট withAppendedId() ব্যবহার করে ব্যবহারকারীর অভিধান প্রদানকারীর কন্টেন্ট URI-তে একটি আইডি যোগ করে।

পরিষেবা প্রদানকারীর থেকে ডেটা ফিরিয়ে আনা

এই বিভাগে, ব্যবহারকারীর অভিধান প্রদানকারীকে উদাহরণ হিসেবে ব্যবহার করে, কীভাবে কোনও প্রদানকারীর থেকে ডেটা ফিরিয়ে আনা যায় তা বর্ণনা করা হয়েছে।

স্পষ্টভাবে বোঝানোর জন্য, এই বিভাগের কোড স্নিপেট UI থ্রেডে ContentResolver.query() কল করে। তবে, আসল কোডে, আলাদা থ্রেডে অ্যাসিঙ্ক্রোনাসভাবে কোয়েরি করুন। আপনি CursorLoader ক্লাস ব্যবহার করতে পারেন, যা Loaders গাইডে আরও বিস্তারিতভাবে বর্ণনা করা হয়েছে। এছাড়াও, কোডের লাইনগুলি শুধুমাত্র স্নিপেট। এগুলি সম্পূর্ণ অ্যাপ্লিকেশন দেখায় না।

কোনও পরিষেবা প্রদানকারীর থেকে ডেটা ফিরিয়ে আনতে, এইসব প্রাথমিক ধাপ অনুসরণ করুন:

  1. প্রোভাইডারের জন্য রিড অ্যাক্সেসের অনুমতি অনুরোধ করুন।
  2. প্রোভাইডারকে কোয়েরি পাঠায় এমন কোড নির্ধারণ করুন।

পড়ার অ্যাক্সেস সংক্রান্ত অনুমতির অনুরোধ করুন

কোনও পরিষেবা প্রদানকারীর থেকে ডেটা ফিরিয়ে আনতে, আপনার অ্যাপ্লিকেশনকে পরিষেবা প্রদানকারীর জন্য রিড অ্যাক্সেস অনুমতি দিতে হবে। রানটাইমে আপনি এই অনুমতির জন্য অনুরোধ করতে পারবেন না। পরিবর্তে, আপনাকে উল্লেখ করতে হবে যে আপনার ম্যানিফেস্টে এই অনুমতি প্রয়োজন, <uses-permission> এলিমেন্ট এবং প্রদানকারীর দ্বারা সংজ্ঞায়িত সঠিক অনুমতির নাম ব্যবহার করে।

আপনার ম্যানিফেস্টে এই এলিমেন্ট নির্দিষ্ট করার অর্থ হল, আপনি নিজের অ্যাপ্লিকেশনের জন্য এই অনুমতি চাইছেন। ব্যবহারকারীরা আপনার অ্যাপ্লিকেশন ইনস্টল করলে, তারা এই অনুরোধে সম্মতি দেন।

আপনি যে পরিষেবা প্রদানকারী ব্যবহার করছেন তার জন্য রিড অ্যাক্সেস অনুমতির সঠিক নাম এবং পরিষেবা প্রদানকারীর দ্বারা ব্যবহৃত অন্যান্য অ্যাক্সেস অনুমতির নাম খুঁজে পেতে, পরিষেবা প্রদানকারীর ডকুমেন্টেশন দেখুন।

প্রোভাইডার অ্যাক্সেস করার ক্ষেত্রে অনুমতির ভূমিকা সম্পর্কে আরও বিস্তারিতভাবে কন্টেন্ট প্রোভাইডার সংক্রান্ত অনুমতি বিভাগে বর্ণনা করা হয়েছে।

ব্যবহারকারীর অভিধান প্রদানকারী তার ম্যানিফেস্ট ফাইলে android.permission.READ_USER_DICTIONARY অনুমতি সংজ্ঞায়িত করে, তাই যে অ্যাপ্লিকেশন প্রদানকারীর থেকে পড়তে চায় তাকে অবশ্যই এই অনুমতির জন্য অনুরোধ করতে হবে।

কোয়েরি তৈরি করুন

কোনও পরিষেবা প্রদানকারীর থেকে ডেটা ফিরিয়ে আনার পরবর্তী ধাপ হল কোয়েরি তৈরি করা। নিম্নলিখিত স্নিপেট ব্যবহারকারীর অভিধান প্রদানকারী অ্যাক্সেস করার জন্য কিছু ভেরিয়েবল সংজ্ঞায়িত করে:

Kotlin

// A "projection" defines the columns that are returned for each row
private val mProjection: Array<String> = arrayOf(
        UserDictionary.Words._ID,    // Contract class constant for the _ID column name
        UserDictionary.Words.WORD,   // Contract class constant for the word column name
        UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
)

// Defines a string to contain the selection clause
private var selectionClause: String? = null

// Declares an array to contain selection arguments
private lateinit var selectionArgs: Array<String>

জাভা

// A "projection" defines the columns that are returned for each row
String[] mProjection =
{
    UserDictionary.Words._ID,    // Contract class constant for the _ID column name
    UserDictionary.Words.WORD,   // Contract class constant for the word column name
    UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
};

// Defines a string to contain the selection clause
String selectionClause = null;

// Initializes an array to contain selection arguments
String[] selectionArgs = {""};

পরবর্তী স্নিপেট দেখায় কীভাবে ContentResolver.query() ব্যবহার করতে হয়, উদাহরণ হিসেবে ব্যবহারকারীর অভিধান প্রদানকারী ব্যবহার করে। প্রদানকারী ক্লায়েন্ট কোয়েরি SQL কোয়েরির মতোই এবং এতে ফেরত দেওয়ার জন্য কলামের সেট, বেছে নেওয়ার মাপকাঠির সেট এবং ক্রমানুসারে সাজানোর সেটিং থাকে।

কোয়েরি যে কলামের সেট দেখায় তাকে প্রোজেকশন বলা হয় এবং ভেরিয়েবেল হল mProjection।

যে এক্সপ্রেশনটি সারিগুলি পুনরুদ্ধার করার জন্য নির্দিষ্ট করে তা একটি নির্বাচন ধারা এবং নির্বাচন আর্গুমেন্টে বিভক্ত। বাছাই করার ক্লজ হল লজিক্যাল ও বুলিয়ান এক্সপ্রেশন, কলামের নাম ও ভ্যালুর কম্বিনেশন। ভেরিয়েবেল হল mSelectionClause। আপনি যদি কোনও ভ্যালুর পরিবর্তে পরিবর্তনযোগ্য প্যারামিটার ? উল্লেখ করেন, তাহলে কোয়েরি পদ্ধতিটি বেছে নেওয়া আর্গুমেন্ট অ্যারে থেকে ভ্যালু ফিরিয়ে আনে, যা হল ভেরিয়েবল mSelectionArgs।

পরবর্তী স্নিপেটে, ব্যবহারকারী কোনও শব্দ না লিখলে, বেছে নেওয়ার ক্লজ null-এ সেট করা হয় এবং কোয়েরি প্রোভাইডারে থাকা সব শব্দ ফিরিয়ে দেয়। ব্যবহারকারী কোনও শব্দ লিখলে, বেছে নেওয়ার ক্লজ UserDictionary.Words.WORD + " = ?"-এ সেট করা হয় এবং বেছে নেওয়ার আর্গুমেন্ট অ্যারের প্রথম এলিমেন্ট ব্যবহারকারীর লেখা শব্দে সেট করা হয়।

Kotlin

/*
 * This declares a String array to contain the selection arguments.
 */
private lateinit var selectionArgs: Array<String>

// Gets a word from the UI
searchString = searchWord.text.toString()

// Insert code here to check for invalid or malicious input

// If the word is the empty string, gets everything
selectionArgs = searchString?.takeIf { it.isNotEmpty() }?.let {
    selectionClause = "${UserDictionary.Words.WORD} = ?"
    arrayOf(it)
} ?: run {
    selectionClause = null
    emptyArray<String>()
}

// Does a query against the table and returns a Cursor object
mCursor = contentResolver.query(
        UserDictionary.Words.CONTENT_URI, // The content URI of the words table
        projection,                       // The columns to return for each row
        selectionClause,                  // Either null or the word the user entered
        selectionArgs,                    // Either empty or the string the user entered
        sortOrder                         // The sort order for the returned rows
)

// Some providers return null if an error occurs, others throw an exception
when (mCursor?.count) {
    null -> {
        /*
         * Insert code here to handle the error. Be sure not to use the cursor!
         * You might want to call android.util.Log.e() to log this error.
         */
    }
    0 -> {
        /*
         * Insert code here to notify the user that the search is unsuccessful. This isn't
         * necessarily an error. You might want to offer the user the option to insert a new
         * row, or re-type the search term.
         */
    }
    else -> {
        // Insert code here to do something with the results
    }
}

জাভা

/*
 * This defines a one-element String array to contain the selection argument.
 */
String[] selectionArgs = {""};

// Gets a word from the UI
searchString = searchWord.getText().toString();

// Remember to insert code here to check for invalid or malicious input

// If the word is the empty string, gets everything
if (TextUtils.isEmpty(searchString)) {
    // Setting the selection clause to null returns all words
    selectionClause = null;
    selectionArgs[0] = "";

} else {
    // Constructs a selection clause that matches the word that the user entered
    selectionClause = UserDictionary.Words.WORD + " = ?";

    // Moves the user's input string to the selection arguments
    selectionArgs[0] = searchString;

}

// Does a query against the table and returns a Cursor object
mCursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI, // The content URI of the words table
    projection,                       // The columns to return for each row
    selectionClause,                  // Either null or the word the user entered
    selectionArgs,                    // Either empty or the string the user entered
    sortOrder);                       // The sort order for the returned rows

// Some providers return null if an error occurs, others throw an exception
if (null == mCursor) {
    /*
     * Insert code here to handle the error. Be sure not to use the cursor! You can
     * call android.util.Log.e() to log this error.
     *
     */
// If the Cursor is empty, the provider found no matches
} else if (mCursor.getCount() < 1) {

    /*
     * Insert code here to notify the user that the search is unsuccessful. This isn't necessarily
     * an error. You can offer the user the option to insert a new row, or re-type the
     * search term.
     */

} else {
    // Insert code here to do something with the results

}

এই কোয়েরিটি নিম্নলিখিত SQL স্টেটমেন্টের অনুরূপ:

SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;

এই SQL স্টেটমেন্টে, কনট্র্যাক্ট ক্লাস কনস্ট্যান্টের পরিবর্তে আসল কলামের নাম ব্যবহার করা হয়েছে।

ক্ষতিকর ইনপুট থেকে সুরক্ষিত রাখুন

কন্টেন্ট প্রোভাইডারের ম্যানেজ করা ডেটা SQL ডেটাবেসে থাকলে, র SQL স্টেটমেন্টে এক্সটার্নাল, বিশ্বস্ত নয় এমন ডেটা অন্তর্ভুক্ত করলে SQL ইনজেকশন হতে পারে।

নিম্নলিখিত বেছে নেওয়ার শর্ত বিবেচনা করুন:

Kotlin

// Constructs a selection clause by concatenating the user's input to the column name
var selectionClause = "var = $mUserInput"

জাভা

// Constructs a selection clause by concatenating the user's input to the column name
String selectionClause = "var = " + userInput;

আপনি এটি করলে, ব্যবহারকারীকে আপনার SQL স্টেটমেন্টে ক্ষতিকর SQL কনক্যাটিনেট করার অনুমতি দেন। যেমন, ব্যবহারকারী mUserInput-এর জন্য "nothing; DROP TABLE *;" লিখতে পারেন, যার ফলে var = nothing; DROP TABLE *; সিলেকশন ক্লজ তৈরি হয়।

যেহেতু সিলেকশন ক্লজকে SQL স্টেটমেন্ট হিসেবে বিবেচনা করা হয়, তাই এটি প্রোভাইডারকে অন্তর্নিহিত SQLite ডেটাবেসের সব টেবিল মুছে ফেলতে বাধ্য করতে পারে, যদি না প্রোভাইডারকে SQL ইনজেকশন-এর প্রচেষ্টা ধরার জন্য সেট-আপ করা হয়।

এই সমস্যা এড়াতে, এমন একটি সিলেকশন ক্লজ ব্যবহার করুন যা পরিবর্তনযোগ্য প্যারামিটার হিসেবে ? এবং সিলেকশন আর্গুমেন্টের একটি আলাদা অ্যারে ব্যবহার করে। এইভাবে, ব্যবহারকারীর ইনপুট SQL স্টেটমেন্টের অংশ হিসেবে ব্যাখ্যা করার পরিবর্তে সরাসরি কোয়েরির সাথে যুক্ত হয়। এটি SQL হিসেবে বিবেচিত হয় না বলে ব্যবহারকারীর ইনপুট ক্ষতিকর SQL ইঞ্জেক্ট করতে পারে না। ব্যবহারকারীর ইনপুট অন্তর্ভুক্ত করতে কনক্যাটেনেশন ব্যবহার করার পরিবর্তে, এই সিলেকশন ক্লজ ব্যবহার করুন:

Kotlin

// Constructs a selection clause with a replaceable parameter
var selectionClause = "var = ?"

জাভা

// Constructs a selection clause with a replaceable parameter
String selectionClause =  "var = ?";

এইভাবে বেছে নেওয়া আর্গুমেন্টের অ্যারে সেট-আপ করুন:

Kotlin

// Defines a mutable list to contain the selection arguments
var selectionArgs: MutableList<String> = mutableListOf()

জাভা

// Defines an array to contain the selection arguments
String[] selectionArgs = {""};

এইভাবে বেছে নেওয়া আর্গুমেন্ট অ্যারেতে একটি ভ্যালু লিখুন:

Kotlin

// Adds the user's input to the selection argument
selectionArgs += userInput

জাভা

// Sets the selection argument to the user's input
selectionArgs[0] = userInput;

এমন একটি সিলেকশন ক্লজ যা পরিবর্তনযোগ্য প্যারামিটার হিসেবে ? এবং সিলেকশন আর্গুমেন্টের অ্যারে ব্যবহার করে, সেটি সিলেকশন নির্দিষ্ট করার জন্য সবচেয়ে ভালো উপায়, এমনকি প্রোভাইডার SQL ডেটাবেসের উপর ভিত্তি করে তৈরি না হলেও।

কোয়েরি ফলাফল দেখান

ContentResolver.query() ক্লায়েন্ট পদ্ধতি সবসময় এমন একটি Cursor রিটার্ন করে যাতে কোয়েরির সিলেকশন ক্রাইটেরিয়ার সাথে ম্যাচ করা সারির জন্য কোয়েরির প্রোজেকশন দ্বারা নির্দিষ্ট করা কলাম থাকে। একটি Cursor অবজেক্ট, এতে থাকা সারি ও কলামে র‍্যান্ডম রিড অ্যাক্সেস প্রদান করে।

Cursor পদ্ধতি ব্যবহার করে, আপনি ফলাফলের সারিগুলি বারবার দেখতে, প্রতিটি কলামের ডেটার ধরন নির্ধারণ করতে, কলাম থেকে ডেটা বের করতে এবং ফলাফলের অন্যান্য বৈশিষ্ট্য পরীক্ষা করতে পারেন।

কিছু Cursor প্রয়োগ, প্রোভাইডারের ডেটা পরিবর্তন হলে অটোমেটিক অবজেক্ট আপডেট করে, Cursor পরিবর্তন হলে অবজার্ভার অবজেক্টে ট্রিগার মেথড বা দুটিই করে।

মনে রাখবেন: কোয়েরি করা অবজেক্টের প্রকৃতির উপর ভিত্তি করে কোনও পরিষেবা প্রদানকারী কলামে অ্যাক্সেস সীমিত করতে পারে। যেমন, পরিচিতি সরবরাহকারী কিছু কলামের অ্যাক্সেস সিঙ্ক অ্যাডাপ্টারে সীমাবদ্ধ করে, তাই এটি কোনও অ্যাক্টিভিটি বা পরিষেবায় সেগুলি ফিরিয়ে দেয় না।

বেছে নেওয়ার মাপকাঠির সাথে কোনও সারি ম্যাচ না করলে, প্রোভাইডার Cursor অবজেক্ট রিটার্ন করে যার জন্য Cursor.getCount() হল 0—অর্থাৎ, একটি খালি কার্সর।

কোনও ইন্টার্নাল সমস্যা হলে, কোয়েরির ফলাফল নির্দিষ্ট পরিষেবা প্রদানকারীর উপর নির্ভর করে। এটি null রিটার্ন করতে পারে অথবা Exception থ্রো করতে পারে।

যেহেতু Cursor হল সারির তালিকা, তাই Cursor-এর কন্টেন্ট দেখানোর একটি ভালো উপায় হল SimpleCursorAdapter ব্যবহার করে ListView-এর সাথে এটি লিঙ্ক করা।

নিচের স্নিপেটটি আগের স্নিপেটের কোডটি চালিয়ে যায়। এটি একটি SimpleCursorAdapter অবজেক্ট তৈরি করে যার মধ্যে কোয়েরির মাধ্যমে পাওয়া Cursor থাকে এবং এই অবজেক্টকে ListView-এর অ্যাডাপ্টার হিসেবে সেট করে।

Kotlin

// Defines a list of columns to retrieve from the Cursor and load into an output row
val wordListColumns : Array<String> = arrayOf(
        UserDictionary.Words.WORD,      // Contract class constant containing the word column name
        UserDictionary.Words.LOCALE     // Contract class constant containing the locale column name
)

// Defines a list of View IDs that receive the Cursor columns for each row
val wordListItems = intArrayOf(R.id.dictWord, R.id.locale)

// Creates a new SimpleCursorAdapter
cursorAdapter = SimpleCursorAdapter(
        applicationContext,             // The application's Context object
        R.layout.wordlistrow,           // A layout in XML for one row in the ListView
        mCursor,                        // The result from the query
        wordListColumns,                // A string array of column names in the cursor
        wordListItems,                  // An integer array of view IDs in the row layout
        0                               // Flags (usually none are needed)
)

// Sets the adapter for the ListView
wordList.setAdapter(cursorAdapter)

জাভা

// Defines a list of columns to retrieve from the Cursor and load into an output row
String[] wordListColumns =
{
    UserDictionary.Words.WORD,   // Contract class constant containing the word column name
    UserDictionary.Words.LOCALE  // Contract class constant containing the locale column name
};

// Defines a list of View IDs that receive the Cursor columns for each row
int[] wordListItems = { R.id.dictWord, R.id.locale};

// Creates a new SimpleCursorAdapter
cursorAdapter = new SimpleCursorAdapter(
    getApplicationContext(),               // The application's Context object
    R.layout.wordlistrow,                  // A layout in XML for one row in the ListView
    mCursor,                               // The result from the query
    wordListColumns,                       // A string array of column names in the cursor
    wordListItems,                         // An integer array of view IDs in the row layout
    0);                                    // Flags (usually none are needed)

// Sets the adapter for the ListView
wordList.setAdapter(cursorAdapter);

মনে রাখবেন: Cursor-এর মাধ্যমে ListView ব্যাক-আপ করতে, কার্সারে অবশ্যই _ID নামের কলাম থাকতে হবে। এই কারণে, আগে দেখানো কোয়েরি Words টেবিলের জন্য _ID কলাম ফিরিয়ে আনে, যদিও ListView এটি দেখায় না। এই বিধিনিষেধ থেকে এটিও বোঝা যায় যে কেন বেশিরভাগ পরিষেবা প্রদানকারীর প্রতিটি টেবিলের জন্য _IDটি কলাম থাকে।

কোয়েরি ফলাফল থেকে ডেটা পান

কোয়েরির ফলাফল দেখানোর পাশাপাশি, আপনি অন্যান্য কাজের জন্য সেগুলি ব্যবহার করতে পারবেন। যেমন, আপনি ব্যবহারকারীর অভিধান প্রদানকারী থেকে বানান উদ্ধার করতে পারেন এবং তারপরে অন্যান্য প্রদানকারীর মধ্যে সেগুলি দেখতে পারেন। এটি করতে, আপনি Cursor-এর সারিগুলি বারবার ব্যবহার করেন, যেমনটি নিম্নলিখিত উদাহরণে দেখানো হয়েছে:

Kotlin

/*
* Only executes if the cursor is valid. The User Dictionary Provider returns null if
* an internal error occurs. Other providers might throw an Exception instead of returning null.
*/
mCursor?.apply {
    // Determine the column index of the column named "word"
    val index: Int = getColumnIndex(UserDictionary.Words.WORD)

    /*
     * Moves to the next row in the cursor. Before the first movement in the cursor, the
     * "row pointer" is -1, and if you try to retrieve data at that position you get an
     * exception.
     */
    while (moveToNext()) {
        // Gets the value from the column
        newWord = getString(index)

        // Insert code here to process the retrieved word
        ...
        // End of while loop
    }
}

জাভা

// Determine the column index of the column named "word"
int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);

/*
 * Only executes if the cursor is valid. The User Dictionary Provider returns null if
 * an internal error occurs. Other providers might throw an Exception instead of returning null.
 */

if (mCursor != null) {
    /*
     * Moves to the next row in the cursor. Before the first movement in the cursor, the
     * "row pointer" is -1, and if you try to retrieve data at that position you get an
     * exception.
     */
    while (mCursor.moveToNext()) {

        // Gets the value from the column
        newWord = mCursor.getString(index);

        // Insert code here to process the retrieved word
        ...
        // End of while loop
    }
} else {

    // Insert code here to report an error if the cursor is null or the provider threw an exception
}

Cursor প্রয়োগে অবজেক্ট থেকে বিভিন্ন ধরনের ডেটা ফিরিয়ে আনার জন্য একাধিক "get" পদ্ধতি থাকে। যেমন, আগের স্নিপেট getString() ব্যবহার করে। এছাড়াও, এতে একটি getType() পদ্ধতি আছে যা কলামের ডেটার ধরন নির্দেশ করে এমন একটি মান দেখায়।

কোয়েরি ফলাফলের রিসোর্স রিলিজ করুন

Cursor অবজেক্ট আর প্রয়োজন না হলে বন্ধ করে দিতে হবে, যাতে সেগুলির সাথে যুক্ত রিসোর্স শীঘ্রই রিলিজ করা যায়। এটি হয় close() কল করে অথবা Java প্রোগ্রামিং ল্যাঙ্গুয়েজে try-with-resources স্টেটমেন্ট বা Kotlin প্রোগ্রামিং ল্যাঙ্গুয়েজে use() ফাংশন ব্যবহার করে করা যেতে পারে।

কন্টেন্ট প্রোভাইডার সংক্রান্ত অনুমতি

একটি প্রদানকারীর অ্যাপ্লিকেশন অনুমতিগুলি নির্দিষ্ট করতে পারে যা অন্যান্য অ্যাপ্লিকেশনগুলির কাছে প্রদানকারীর ডেটা অ্যাক্সেস করার জন্য অবশ্যই থাকতে হবে৷ এইসব অনুমতি ব্যবহারকারীকে জানতে দেয় যে কোনও অ্যাপ্লিকেশন কোন ডেটা অ্যাক্সেস করার চেষ্টা করছে। প্রোভাইডারের প্রয়োজনীয়তার উপর ভিত্তি করে, অন্যান্য অ্যাপ্লিকেশন প্রোভাইডারকে অ্যাক্সেস করার জন্য প্রয়োজনীয় অনুমতি চায়। ব্যবহারকারীরা অ্যাপ্লিকেশন ইনস্টল করার সময় অনুরোধ করা অনুমতি দেখতে পান।

কোনও পরিষেবা প্রদানকারীর অ্যাপ্লিকেশন কোনও অনুমতি নির্দিষ্ট না করলে, অন্যান্য অ্যাপ্লিকেশনের কাছে পরিষেবা প্রদানকারীর ডেটার অ্যাক্সেস থাকে না, যদি না পরিষেবা প্রদানকারীকে এক্সপোর্ট করা হয়। এছাড়াও, নির্দিষ্ট অনুমতি নির্বিশেষে, প্রদানকারীর অ্যাপ্লিকেশনের উপাদানগুলির কাছে সর্বদা সম্পূর্ণ রিড এবং রাইট অ্যাক্সেস থাকে।

ব্যবহারকারীর অভিধান প্রদানকারীর থেকে ডেটা পেতে android.permission.READ_USER_DICTIONARY অনুমতি প্রয়োজন। ডেটা যোগ, আপডেট বা মুছে ফেলার জন্য পরিষেবা প্রদানকারীর কাছে আলাদা android.permission.WRITE_USER_DICTIONARY অনুমতি আছে।

কোনও প্রোভাইডার অ্যাক্সেস করার জন্য প্রয়োজনীয় অনুমতি পেতে, কোনও অ্যাপ্লিকেশন তার ম্যানিফেস্ট ফাইলে <uses-permission> এলিমেন্ট সহ সেগুলির জন্য অনুরোধ করে। Android প্যাকেজ ম্যানেজার অ্যাপ্লিকেশন ইনস্টল করার সময়, ব্যবহারকারীকে অ্যাপ্লিকেশনের অনুরোধ করা সব অনুমতি অনুমোদন করতে হবে। ব্যবহারকারী অনুমতি দিলে, প্যাকেজ ম্যানেজার ইনস্টলেশন চালিয়ে যায়। ব্যবহারকারী অনুমতি না দিলে, প্যাকেজ ম্যানেজার ইনস্টলেশন বন্ধ করে দেয়।

নিম্নলিখিত নমুনা <uses-permission> এলিমেন্ট ব্যবহারকারীর অভিধান প্রদানকারীর কাছে পড়ার অ্যাক্সেসের অনুরোধ করে:

<uses-permission android:name="android.permission.READ_USER_DICTIONARY">

প্রদানকারীর অ্যাক্সেসের উপর অনুমতির প্রভাব সম্পর্কে আরও বিস্তারিতভাবে নিরাপত্তা সংক্রান্ত পরামর্শ-এ ব্যাখ্যা করা হয়েছে।

ডেটা যোগ, আপডেট ও মুছে ফেলা

আপনি যেভাবে কোনও পরিষেবা প্রদানকারীর থেকে ডেটা পান, ঠিক একইভাবে ডেটা পরিবর্তন করার জন্য পরিষেবা প্রদানকারীর ক্লায়েন্ট এবং পরিষেবা প্রদানকারীর ContentProvider-এর মধ্যে ইন্টার‍্যাকশন ব্যবহার করেন। আপনি ContentResolver-এর একটি পদ্ধতিকে এমন আর্গুমেন্ট সহ কল করেন যা ContentProvider-এর সংশ্লিষ্ট পদ্ধতিতে পাস করা হয়। প্রোভাইডার এবং প্রোভাইডার ক্লায়েন্ট অটোমেটিক নিরাপত্তা এবং ইন্টারপ্রসেস কমিউনিকেশন ম্যানেজ করে।

ডেটা যোগ করুন

কোনও পরিষেবা প্রদানকারীর কাছে ডেটা পাঠাতে, আপনি ContentResolver.insert() মেথড কল করেন। এই পদ্ধতিটি প্রদানকারীর মধ্যে একটি নতুন সারি যোগ করে এবং সেই সারির জন্য একটি কন্টেন্ট URI প্রদান করে। নিম্নলিখিত স্নিপেট দেখায় যে কীভাবে ব্যবহারকারীর অভিধান প্রদানকারীতে একটি নতুন শব্দ সন্নিবেশ করতে হয়:

Kotlin

// Defines a new Uri object that receives the result of the insertion
lateinit var newUri: Uri
...
// Defines an object to contain the new values to insert
val newValues = ContentValues().apply {
    /*
     * Sets the values of each column and inserts the word. The arguments to the "put"
     * method are "column name" and "value".
     */
    put(UserDictionary.Words.APP_ID, "example.user")
    put(UserDictionary.Words.LOCALE, "en_US")
    put(UserDictionary.Words.WORD, "insert")
    put(UserDictionary.Words.FREQUENCY, "100")

}

newUri = contentResolver.insert(
        UserDictionary.Words.CONTENT_URI,   // The UserDictionary content URI
        newValues                           // The values to insert
)

জাভা

// Defines a new Uri object that receives the result of the insertion
Uri newUri;
...
// Defines an object to contain the new values to insert
ContentValues newValues = new ContentValues();

/*
 * Sets the values of each column and inserts the word. The arguments to the "put"
 * method are "column name" and "value".
 */
newValues.put(UserDictionary.Words.APP_ID, "example.user");
newValues.put(UserDictionary.Words.LOCALE, "en_US");
newValues.put(UserDictionary.Words.WORD, "insert");
newValues.put(UserDictionary.Words.FREQUENCY, "100");

newUri = getContentResolver().insert(
    UserDictionary.Words.CONTENT_URI,   // The UserDictionary content URI
    newValues                           // The values to insert
);

নতুন সারির ডেটা একটি ContentValues অবজেক্টে যায়, যা এক-সারি কার্সারের মতো ফর্ম্যাটে থাকে। এই অবজেক্টের কলামে একই ধরনের ডেটা থাকার প্রয়োজন নেই এবং আপনি যদি কোনও ভ্যালু নির্দিষ্ট করতে না চান, তাহলে ContentValues.putNull() ব্যবহার করে কলামকে null-এ সেট করতে পারেন।

আগের স্নিপেট _ID কলাম যোগ করে না, কারণ এই কলাম অটোমেটিক মেইনটেইন করা হয়। পরিষেবা প্রদানকারী যোগ করা প্রতিটি সারিতে _ID-এর একটি অনন্য ভ্যালু অ্যাসাইন করে। প্রদানকারীরা সাধারণত এই ভ্যালুকে টেবিলের প্রাথমিক কী হিসেবে ব্যবহার করেন।

newUri-এ ফেরত আসা কন্টেন্ট URI, নিম্নলিখিত ফর্ম্যাট সহ নতুন যোগ করা সারি শনাক্ত করে:

content://user_dictionary/words/<id_value>

<id_value> হল নতুন সারির জন্য _ID-এর কন্টেন্ট। বেশিরভাগ পরিষেবা প্রদানকারী এই ধরনের কন্টেন্ট URI অটোমেটিক শনাক্ত করতে পারে এবং তারপরে নির্দিষ্ট সারিতে অনুরোধ করা অপারেশনটি সম্পাদন করে।

ফেরত আসা Uri থেকে _ID-এর ভ্যালু পেতে, ContentUris.parseId()-এ কল করুন।

ডেটা আপডেট করুন

কোনও রো আপডেট করতে, আপনি আপডেট করা ভ্যালু সহ ContentValues অবজেক্ট ব্যবহার করেন, ঠিক যেমন আপনি ইনসার্ট করার সময় করেন এবং কোয়েরি করার সময় যেমন সিলেকশন ক্রাইটেরিয়া ব্যবহার করেন। আপনি যে ক্লায়েন্ট পদ্ধতি ব্যবহার করেন সেটি হল ContentResolver.update()। আপনি যে কলাম আপডেট করছেন, সেটির জন্য আপনাকে শুধু ContentValues অবজেক্টে ভ্যালু যোগ করতে হবে। আপনি কোনও কলামের কন্টেন্ট মুছে দিতে চাইলে, ভ্যালু null হিসেবে সেট করুন।

নিম্নলিখিত স্নিপেটটি সেইসব সারি পরিবর্তন করে যেগুলির লোকেলে "en" ভাষা আছে, যাতে null লোকেল থাকে। রিটার্ন ভ্যালু হল আপডেট করা সারির সংখ্যা।

Kotlin

// Defines an object to contain the updated values
val updateValues = ContentValues().apply {
    /*
     * Sets the updated value and updates the selected words.
     */
    putNull(UserDictionary.Words.LOCALE)
}

// Defines selection criteria for the rows you want to update
val selectionClause: String = UserDictionary.Words.LOCALE + "LIKE ?"
val selectionArgs: Array<String> = arrayOf("en_%")

// Defines a variable to contain the number of updated rows
var rowsUpdated: Int = 0
...
rowsUpdated = contentResolver.update(
        UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
        updateValues,                      // The columns to update
        selectionClause,                   // The column to select on
        selectionArgs                      // The value to compare to
)

জাভা

// Defines an object to contain the updated values
ContentValues updateValues = new ContentValues();

// Defines selection criteria for the rows you want to update
String selectionClause = UserDictionary.Words.LOCALE +  " LIKE ?";
String[] selectionArgs = {"en_%"};

// Defines a variable to contain the number of updated rows
int rowsUpdated = 0;
...
/*
 * Sets the updated value and updates the selected words.
 */
updateValues.putNull(UserDictionary.Words.LOCALE);

rowsUpdated = getContentResolver().update(
    UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
    updateValues,                      // The columns to update
    selectionClause,                   // The column to select on
    selectionArgs                      // The value to compare to
);

ContentResolver.update() কল করার সময় ব্যবহারকারীর ইনপুট স্যানিটাইজ করুন। এই বিষয়ে আরও জানতে, ক্ষতিকর ইনপুট থেকে সুরক্ষিত থাকুন বিভাগটি পড়ুন।

ডেটা মুছুন

সারি মুছে ফেলা, সারি ডেটা ফিরিয়ে আনার মতোই। আপনি যে সারিগুলি মুছতে চান তার জন্য নির্বাচনের মানদণ্ড নির্দিষ্ট করেন এবং ক্লায়েন্ট পদ্ধতি মুছে ফেলা সারির সংখ্যা ফেরত দেয়। নিম্নলিখিত স্নিপেট সেইসব সারি মুছে দেয় যেগুলির অ্যাপ আইডি "user"-এর সাথে মেলে। এই পদ্ধতি মুছে ফেলা সারির সংখ্যা দেখায়।

Kotlin

// Defines selection criteria for the rows you want to delete
val selectionClause = "${UserDictionary.Words.APP_ID} LIKE ?"
val selectionArgs: Array<String> = arrayOf("user")

// Defines a variable to contain the number of rows deleted
var rowsDeleted: Int = 0
...
// Deletes the words that match the selection criteria
rowsDeleted = contentResolver.delete(
        UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
        selectionClause,                   // The column to select on
        selectionArgs                      // The value to compare to
)

জাভা

// Defines selection criteria for the rows you want to delete
String selectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
String[] selectionArgs = {"user"};

// Defines a variable to contain the number of rows deleted
int rowsDeleted = 0;
...
// Deletes the words that match the selection criteria
rowsDeleted = getContentResolver().delete(
    UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
    selectionClause,                   // The column to select on
    selectionArgs                      // The value to compare to
);

ContentResolver.delete() কল করার সময় ব্যবহারকারীর ইনপুট স্যানিটাইজ করুন। এই বিষয়ে আরও জানতে, ক্ষতিকর ইনপুট থেকে সুরক্ষিত থাকুন বিভাগটি পড়ুন।

প্রোভাইডার ডেটার ধরন

কন্টেন্ট প্রোভাইডার বিভিন্ন ধরনের ডেটা অফার করতে পারে। ব্যবহারকারীর অভিধান প্রদানকারী শুধুমাত্র টেক্সট অফার করে, তবে প্রদানকারীরা নিম্নলিখিত ফর্ম্যাটগুলিও অফার করতে পারে:

  • পূর্ণসংখ্যা
  • লং ইন্টিজার (লং)
  • ফ্লোটিং পয়েন্ট
  • দীর্ঘ ফ্লোটিং পয়েন্ট (ডবল)

অন্য যে ডেটার ধরন পরিষেবা প্রদানকারীরা প্রায়ই ব্যবহার করেন তা হল বাইনারি লার্জ অবজেক্ট (BLOB) যা ৬৪ কেবি বাইট অ্যারে হিসেবে প্রয়োগ করা হয়। আপনি Cursor ক্লাস "get" পদ্ধতি দেখে উপলভ্য ডেটার ধরন দেখতে পারবেন।

প্রোভাইডারের প্রতিটি কলামের ডেটার ধরন সাধারণত তার ডকুমেন্টেশনে তালিকাভুক্ত করা হয়। ব্যবহারকারীর অভিধান প্রদানকারীর ডেটার ধরন, এর কন্ট্র্যাক্ট ক্লাস, UserDictionary.Words-এর জন্য রেফারেন্স ডকুমেন্টেশনে তালিকাভুক্ত করা আছে। কনট্র্যাক্ট ক্লাস কনট্র্যাক্ট ক্লাস বিভাগে বর্ণনা করা হয়েছে। এছাড়াও, আপনি Cursor.getType() কল করে ডেটার ধরন নির্ধারণ করতে পারবেন।

এছাড়াও, প্রদানকারীরা তাদের সংজ্ঞায়িত প্রতিটি কন্টেন্ট URI-এর জন্য MIME ডেটা টাইপ তথ্য বজায় রাখে। প্রোভাইডারের অফার করা ডেটা আপনার অ্যাপ্লিকেশন ম্যানেজ করতে পারবে কিনা তা জানতে অথবা MIME-এর প্রকারের উপর ভিত্তি করে ম্যানেজ করার ধরন বেছে নিতে, আপনি MIME-এর প্রকার সংক্রান্ত তথ্য ব্যবহার করতে পারবেন। সাধারণত, জটিল ডেটা স্ট্রাকচার বা ফাইল আছে এমন কোনও পরিষেবা প্রদানকারীর সাথে কাজ করার সময় MIME-এর প্রকার প্রয়োজন হয়।

যেমন, ContactsContract.Data পরিচিতি সরবরাহকারীর টেবিল MIME-এর প্রকার ব্যবহার করে প্রতিটি সারিতে স্টোর করা পরিচিতি ডেটার প্রকার লেবেল করে। কন্টেন্ট URI-এর সাথে সম্পর্কিত MIME-এর ধরন পেতে, ContentResolver.getType()-এ কল করুন।

MIME-এর প্রকার রেফারেন্স বিভাগে স্ট্যান্ডার্ড ও কাস্টম MIME-এর প্রকারের সিনট্যাক্স বর্ণনা করা হয়েছে।

প্রদানকারীর অ্যাক্সেসের বিকল্প ফর্ম

অ্যাপ্লিকেশন ডেভেলপমেন্টের ক্ষেত্রে পরিষেবা প্রদানকারীর অ্যাক্সেসের তিনটি বিকল্প ফর্ম গুরুত্বপূর্ণ:

  • ব্যাচ অ্যাক্সেস: আপনি ContentProviderOperation ক্লাসের পদ্ধতি ব্যবহার করে অ্যাক্সেস কলের ব্যাচ তৈরি করতে পারেন এবং তারপরে ContentResolver.applyBatch()-এর মাধ্যমে সেগুলি প্রয়োগ করতে পারেন।
  • অ্যাসিঙ্ক্রোনাস কোয়েরি: আলাদা থ্রেডে কোয়েরি করুন। আপনি CursorLoader অবজেক্ট ব্যবহার করতে পারবেন। লোডার গাইডে দেওয়া উদাহরণ থেকে এটি কীভাবে করতে হয় তা জানতে পারবেন।
  • ইনটেন্ট ব্যবহার করে ডেটা অ্যাক্সেস করা: আপনি সরাসরি কোনও প্রোভাইডারকে ইনটেন্ট পাঠাতে না পারলেও, প্রোভাইডারের অ্যাপ্লিকেশনে ইনটেন্ট পাঠাতে পারবেন, যা সাধারণত প্রোভাইডারের ডেটা পরিবর্তন করার জন্য সবচেয়ে উপযুক্ত।

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

ব্যাচ অ্যাক্সেস

প্রোভাইডারের ব্যাচ অ্যাক্সেস অনেক সারি যোগ করার জন্য, একই মেথড কলে একাধিক টেবিলে সারি যোগ করার জন্য এবং সাধারণভাবে একটি ট্রানজ্যাকশন হিসেবে প্রসেস বাউন্ডারি জুড়ে এক সেট অপারেশন করার জন্য উপযোগী, যাকে অ্যাটমিক অপারেশন বলা হয়।

ব্যাচ মোডে কোনও প্রোভাইডার অ্যাক্সেস করতে, ContentProviderOperation অবজেক্টের একটি অ্যারে তৈরি করুন এবং তারপরে ContentResolver.applyBatch()-এর মাধ্যমে কোনও কন্টেন্ট প্রোভাইডারে সেগুলি পাঠান। আপনি নির্দিষ্ট কন্টেন্ট URI-এর পরিবর্তে এই পদ্ধতিতে কন্টেন্ট প্রোভাইডারের কর্তৃত্ব পাস করেন।

এটি অ্যারের প্রতিটি ContentProviderOperation অবজেক্টকে আলাদা টেবিলের বিরুদ্ধে কাজ করতে দেয়। ContentResolver.applyBatch()-এ কল করলে ফলাফলের একটি অ্যারে পাওয়া যায়।

ContactsContract.RawContacts কনট্র্যাক্ট ক্লাসের বিবরণে একটি কোড স্নিপেট অন্তর্ভুক্ত আছে যা ব্যাচ ইনসার্শন দেখায়।

ইনটেন্ট ব্যবহার করে ডেটা অ্যাক্সেস

ইনটেন্ট কোনও কন্টেন্ট প্রোভাইডারকে পরোক্ষ অ্যাক্সেস প্রদান করতে পারে। আপনার অ্যাপ্লিকেশনের অ্যাক্সেস সংক্রান্ত অনুমতি না থাকলেও, আপনি ব্যবহারকারীকে প্রদানকারীর ডেটা অ্যাক্সেস করতে দিতে পারেন। এর জন্য, অনুমতি আছে এমন কোনও অ্যাপ্লিকেশন থেকে ফলাফল ইনটেন্ট ফেরত পেতে হবে অথবা অনুমতি আছে এমন কোনও অ্যাপ্লিকেশন অ্যাক্টিভেট করে ব্যবহারকারীকে সেখানে কাজ করতে দিতে হবে।

টেম্পোরারি পারমিশনের মাধ্যমে অ্যাক্সেস পাওয়া

আপনার কাছে সঠিক অ্যাক্সেস অনুমতি না থাকলেও, আপনি কন্টেন্ট প্রোভাইডারে ডেটা অ্যাক্সেস করতে পারবেন। এর জন্য, অনুমতি আছে এমন কোনও অ্যাপ্লিকেশনে ইনটেন্ট পাঠাতে হবে এবং URI অনুমতি সহ ফলাফল ইনটেন্ট ফেরত পেতে হবে। এগুলি হল নির্দিষ্ট কন্টেন্ট URI-এর জন্য অনুমতি যা অ্যাক্টিভিটি শেষ না হওয়া পর্যন্ত থাকে। যে অ্যাপ্লিকেশনের স্থায়ী অনুমতি আছে সেটি ফলাফল ইনটেন্টে ফ্ল্যাগ সেট করার মাধ্যমে টেম্পোরারি অনুমতি দেয়:

মনে রাখবেন: এইসব ফ্ল্যাগ, কন্টেন্ট URI-তে যার অথরিটি আছে সেই পরিষেবা প্রদানকারীকে সাধারণ রিড বা রাইট অ্যাক্সেস দেয় না। শুধুমাত্র URI-এর জন্য অ্যাক্সেস দেওয়া হয়।

অন্য অ্যাপে কন্টেন্ট URI পাঠানোর সময়, এর মধ্যে অন্তত একটি ফ্ল্যাগ অন্তর্ভুক্ত করুন। ফ্ল্যাগগুলি এমন যেকোনও অ্যাপকে নিম্নলিখিত ক্ষমতা প্রদান করে যা একটি ইনটেন্ট গ্রহণ করে এবং Android 11 (API লেভেল ৩০) বা তার পরবর্তী যেকোনও ভার্সনকে টার্গেট করে:

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

প্রদানকারী তার ম্যানিফেস্টে কন্টেন্ট URI-এর জন্য URI অনুমতি সংজ্ঞায়িত করে, android:grantUriPermissions অ্যাট্রিবিউট <provider> এলিমেন্ট এবং <grant-uri-permission> চাইল্ড এলিমেন্ট <provider> এলিমেন্ট ব্যবহার করে। URI অনুমতির মেকানিজম সম্পর্কে আরও বিস্তারিতভাবে Android-এ অনুমতি সংক্রান্ত গাইডে ব্যাখ্যা করা হয়েছে।

যেমন, আপনার কাছে READ_CONTACTS অনুমতি না থাকলেও, আপনি পরিচিতি সরবরাহকারী থেকে কোনও পরিচিতির ডেটা উদ্ধার করতে পারবেন। আপনি হয়ত এমন কোনও অ্যাপ্লিকেশনে এটি করতে চাইবেন যা কোনও পরিচিতির জন্মদিনে তাকে ই-গ্রিটিংস পাঠায়। READ_CONTACTS-এর জন্য অনুরোধ করার পরিবর্তে, যা আপনাকে ব্যবহারকারীর সব পরিচিতি এবং তাদের সব তথ্য অ্যাক্সেস করার অনুমতি দেয়, ব্যবহারকারীকে তার কোন কোন পরিচিতি আপনার অ্যাপ্লিকেশন ব্যবহার করবে তা নিয়ন্ত্রণ করতে দিন। এটি করতে, নিম্নলিখিত প্রসেস ব্যবহার করুন:

  1. আপনার অ্যাপ্লিকেশনে, startActivityForResult() পদ্ধতি ব্যবহার করে ACTION_PICK অ্যাকশন এবং "contacts" MIME-এর প্রকার CONTENT_ITEM_TYPE সহ একটি ইনটেন্ট পাঠান।
  2. এই ইনটেন্ট, People অ্যাপের "বাছাই" অ্যাক্টিভিটির জন্য ইনটেন্ট ফিল্টারের সাথে মিলে যায় বলে, অ্যাক্টিভিটিটি ফোরগ্রাউন্ডে চলে আসে।
  3. বেছে নেওয়ার অ্যাক্টিভিটিতে, ব্যবহারকারী আপডেট করার জন্য একটি পরিচিতি বেছে নেন। এটি হলে, আপনার অ্যাপ্লিকেশনে ফেরত দেওয়ার জন্য একটি ইনটেন্ট সেট-আপ করতে, সিলেকশন অ্যাক্টিভিটি setResult(resultcode, intent) কল করে। ইনটেন্টে ব্যবহারকারীর বেছে নেওয়া পরিচিতির কন্টেন্ট URI এবং "এক্সট্রা" ফ্ল্যাগ FLAG_GRANT_READ_URI_PERMISSION থাকে। এইসব ফ্ল্যাগ আপনার অ্যাপকে URI অনুমতি দেয় যাতে কন্টেন্ট URI-এর মাধ্যমে নির্দেশ করা পরিচিতির ডেটা পড়া যায়। বেছে নেওয়ার অ্যাক্টিভিটি তারপর finish()-কে কল করে আপনার অ্যাপ্লিকেশনে কন্ট্রোল ফিরিয়ে দেয়।
  4. আপনার অ্যাক্টিভিটি ফোরগ্রাউন্ডে ফিরে আসে এবং সিস্টেম আপনার অ্যাক্টিভিটির onActivityResult() মেথড কল করে। এই পদ্ধতিটি People অ্যাপে বেছে নেওয়ার অ্যাক্টিভিটির মাধ্যমে তৈরি করা ফলাফল ইনটেন্ট রিসিভ করে।
  5. ফলাফল ইনটেন্ট থেকে কন্টেন্ট URI-এর মাধ্যমে, আপনি পরিচিতি সরবরাহকারী থেকে পরিচিতির ডেটা পড়তে পারবেন, যদিও আপনি ম্যানিফেস্টে সরবরাহকারীর কাছে স্থায়ীভাবে পড়ার অ্যাক্সেসের অনুমতির জন্য অনুরোধ করেননি। তারপর আপনি পরিচিতির জন্মদিনের তথ্য বা ইমেল আইডি পেতে এবং ই-গ্রিটিং পাঠাতে পারবেন।

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

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

যেমন, Calendar অ্যাপ্লিকেশন একটি ACTION_INSERT ইনটেন্ট গ্রহণ করে যা আপনাকে অ্যাপ্লিকেশনের ইনসার্ট UI অ্যাক্টিভেট করতে দেয়। আপনি এই ইনটেন্টে "অতিরিক্ত" ডেটা পাস করতে পারেন, যা অ্যাপ্লিকেশন UI অটো-ফিল করতে ব্যবহার করে। পৌনঃপুনিক ইভেন্টের সিনট্যাক্স জটিল হওয়ার কারণে, Calendar Provider-এ ইভেন্ট যোগ করার পছন্দের উপায় হল ACTION_INSERT-এর মাধ্যমে Calendar অ্যাপ অ্যাক্টিভেট করা এবং তারপর ব্যবহারকারীকে সেখানে ইভেন্ট যোগ করতে দেওয়া।

সহায়ক অ্যাপ ব্যবহার করে ডেটা দেখানো

আপনার অ্যাপ্লিকেশনের অ্যাক্সেস করার অনুমতি থাকলেও, আপনি এখনও অন্য অ্যাপ্লিকেশনে ডেটা দেখানোর জন্য ইনটেন্ট ব্যবহার করতে পারেন। যেমন, Calendar অ্যাপ্লিকেশন একটি ACTION_VIEW ইনটেন্ট গ্রহণ করে যা একটি নির্দিষ্ট তারিখ বা ইভেন্ট দেখায়। এর ফলে, আপনার নিজস্ব UI তৈরি না করেই ক্যালেন্ডারের তথ্য দেখাতে পারবেন। এই ফিচার সম্পর্কে আরও জানতে, Calendar প্রদানকারীর ওভারভিউ দেখুন।

আপনি যে অ্যাপ্লিকেশনে ইনটেন্ট পাঠান সেটি পরিষেবা প্রদানকারীর সাথে যুক্ত অ্যাপ্লিকেশন হতে হবে এমন কোনও বাধ্যবাধকতা নেই। যেমন, আপনি পরিচিতি সরবরাহকারী থেকে কোনও পরিচিতি রিট্রিভ করতে পারেন, তারপর পরিচিতির ছবির জন্য কন্টেন্ট URI সহ একটি ACTION_VIEW ইনটেন্ট ইমেজ ভিউয়ারে পাঠাতে পারেন।

কনট্র্যাক্ট ক্লাস

একটি কনট্র্যাক্ট ক্লাস ধ্রুবকগুলিকে সংজ্ঞায়িত করে যা অ্যাপ্লিকেশনগুলিকে কন্টেন্ট URI, কলামের নাম, ইনটেন্ট অ্যাকশন এবং কন্টেন্ট প্রোভাইডারের অন্যান্য ফিচারের সাথে কাজ করতে সাহায্য করে। প্রোভাইডারের সাথে কনট্র্যাক্ট ক্লাস অটোমেটিক অন্তর্ভুক্ত করা হয় না। প্রদানকারীর ডেভেলপারকে সেগুলি সংজ্ঞায়িত করতে হবে এবং তারপরে অন্যান্য ডেভেলপারদের জন্য সেগুলি উপলভ্য করতে হবে। Android প্ল্যাটফর্মের সাথে অন্তর্ভুক্ত অনেক প্রোভাইডারের প্যাকেজ android.provider-এ সংশ্লিষ্ট কনট্র্যাক্ট ক্লাস আছে।

যেমন, ব্যবহারকারীর অভিধান প্রদানকারীর একটি কনট্র্যাক্ট ক্লাস UserDictionary আছে যাতে কন্টেন্ট URI এবং কলামের নামের কনস্ট্যান্ট থাকে। Words টেবিলের কন্টেন্ট URI, UserDictionary.Words.CONTENT_URI ধ্রুবকে সংজ্ঞায়িত করা আছে। UserDictionary.Words ক্লাসে কলামের নামের কনস্ট্যান্টও থাকে, যা এই গাইডের উদাহরণ স্নিপেটে ব্যবহার করা হয়। যেমন, কোয়েরি প্রোজেকশনকে নিম্নলিখিতভাবে সংজ্ঞায়িত করা যেতে পারে:

Kotlin

val projection : Array<String> = arrayOf(
        UserDictionary.Words._ID,
        UserDictionary.Words.WORD,
        UserDictionary.Words.LOCALE
)

জাভা

String[] projection =
{
    UserDictionary.Words._ID,
    UserDictionary.Words.WORD,
    UserDictionary.Words.LOCALE
};

পরিচিতি সরবরাহকারীর জন্য আরেকটি কনট্র্যাক্ট ক্লাস হল ContactsContract। এই ক্লাসের রেফারেন্স ডকুমেন্টেশনে উদাহরণ কোড স্নিপেট অন্তর্ভুক্ত আছে। এর একটি সাবক্লাস, ContactsContract.Intents.Insert, হল একটি কনট্র্যাক্ট ক্লাস যাতে ইনটেন্ট ও ইনটেন্ট ডেটার জন্য কনস্ট্যান্ট থাকে।

MIME-এর প্রকার সংক্রান্ত রেফারেন্স

কন্টেন্ট প্রোভাইডার স্ট্যান্ডার্ড MIME মিডিয়া টাইপ, কাস্টম MIME টাইপ স্ট্রিং বা দুটিই ফিরিয়ে দিতে পারে।

MIME-এর প্রকারের ফর্ম্যাট নিচে দেওয়া হল:

type/subtype

যেমন, সুপরিচিত MIME-এর প্রকার text/html-এর text প্রকার এবং html সাবটাইপ আছে। প্রদানকারী কোনও URI-এর জন্য এই ধরনের ডেটা পাঠালে, তার অর্থ হল সেই URI ব্যবহার করে কোয়েরি করলে HTML ট্যাগ সহ টেক্সট পাওয়া যাবে।

কাস্টম MIME-এর ধরন স্ট্রিং, যাকে ভেন্ডর-নির্দিষ্ট MIME-এর ধরনও বলা হয়, সেটিতে আরও জটিল type এবং subtype ভ্যালু থাকে। একাধিক সারির ক্ষেত্রে, টাইপ ভ্যালু সবসময় নিম্নলিখিত হয়:

vnd.android.cursor.dir

একটি সারির জন্য, টাইপ ভ্যালু সবসময় নিম্নলিখিত হয়:

vnd.android.cursor.item

subtype হল পরিষেবা প্রদানকারী-নির্দিষ্ট। Android-এর বিল্ট-ইন প্রদানকারীর সাধারণত একটি সহজ সাবটাইপ থাকে। যেমন, Contacts অ্যাপ্লিকেশন যখন কোনও টেলিফোন নম্বরের জন্য একটি সারি তৈরি করে, তখন এটি সারিতে নিম্নলিখিত MIME-এর প্রকার সেট করে:

vnd.android.cursor.item/phone_v2

সাবটাইপের ভ্যালু হল phone_v2।

অন্যান্য পরিষেবা প্রদানকারী ডেভেলপাররা পরিষেবা প্রদানকারীর কর্তৃত্ব এবং টেবিলের নামের উপর ভিত্তি করে সাবটাইপের নিজস্ব প্যাটার্ন তৈরি করতে পারেন। যেমন, ট্রেনের সময়সূচি আছে এমন কোনও পরিষেবা প্রদানকারীর কথা ভাবুন। প্রদানকারীর অথরিটি হল com.example.trains এবং এতে Line1, Line2 ও Line3 টেবিল আছে। Line1 টেবিলের জন্য নিম্নলিখিত কন্টেন্ট URI-এর উত্তরে:

content://com.example.trains/Line1

প্রোভাইডার নিম্নলিখিত MIME-এর প্রকার রিটার্ন করে:

vnd.android.cursor.dir/vnd.example.line1

Line2 টেবিলের ৫ নম্বর সারির জন্য নিম্নলিখিত কন্টেন্ট URI-এর উত্তরে:

content://com.example.trains/Line2/5

প্রোভাইডার নিম্নলিখিত MIME-এর প্রকার রিটার্ন করে:

vnd.android.cursor.item/vnd.example.line2

বেশিরভাগ কন্টেন্ট প্রোভাইডার তাদের ব্যবহার করা MIME-এর প্রকারের জন্য কনট্র্যাক্ট ক্লাস কনস্ট্যান্ট নির্ধারণ করে। যেমন, Contacts Provider কনট্র্যাক্ট ক্লাস ContactsContract.RawContacts, একটি র কন্ট্যাক্ট সারির MIME-এর প্রকারের জন্য CONTENT_ITEM_TYPE কনস্ট্যান্টকে সংজ্ঞায়িত করে।

একক সারির জন্য কন্টেন্ট URI কন্টেন্ট URI বিভাগে বর্ণনা করা হয়েছে।