ফাইল শেয়ার করা

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

এই টিউটোরিয়ালে, কীভাবে আপনার অ্যাপে ফাইল বেছে নেওয়ার Activity সুবিধা তৈরি করতে হয় তা দেখানো হয়েছে, যা ফাইলের অনুরোধের উত্তর দেয়।

ফাইলের অনুরোধ পান

ক্লায়েন্ট অ্যাপ থেকে ফাইলের অনুরোধ পেতে এবং কন্টেন্ট URI দিয়ে উত্তর দিতে, আপনার অ্যাপকে এগুলি করতে হবে: ফাইল বেছে নেওয়ার Activity সুবিধা দিতে হবে। ক্লায়েন্ট অ্যাপ এই Activity শুরু করে startActivityForResult()-কে Intent কল করে, যার মধ্যে অ্যাকশন ACTION_PICK থাকে। ক্লায়েন্ট অ্যাপ startActivityForResult()কল করলে, আপনার অ্যাপ ব্যবহারকারীর বেছে নেওয়া ফাইলের জন্য কন্টেন্ট URI-এর আকারে ক্লায়েন্ট অ্যাপকে ফলাফল রিটার্ন করতে পারে।

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

ফাইল বেছে নেওয়ার অ্যাক্টিভিটি তৈরি করা

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

<manifest xmlns:android="http://schemas.android.com/apk/res/and>roid"
    ..<.
        a>pplication
        ...
  <          activity
                android:name=".FileSelectActivity"
           @     android:la>bel="File Se<lector" >
                inte<nt-filter
                    action
                        android:name>="android.intent<.action.PICK"/
                    category
                        android>:name="android.i<ntent.category.DEFAULT"/
                    category
                      >  android:name="<android.intent.category.OPEN>ABLE"/
                <    data android:mimeType=">text/plain"/<
             >       data a<ndroid:mi>meType="image/*"/
                /intent-filter
            /activity

কোডে ফাইল বেছে নেওয়ার অ্যাক্টিভিটি সংজ্ঞায়িত করা

এরপরে, ইন্টার্নাল স্টোরেজে আপনার অ্যাপের files/images/ ডিরেক্টরি থেকে উপলভ্য ফাইল দেখায় এমন একটি Activity সাবক্লাস সংজ্ঞায়িত করুন এবং ব্যবহারকারীকে পছন্দসই ফাইল বেছে নিতে দিন। নিচের স্নিপেট থেকে বোঝা যায় যে কীভাবে এটি Activity নির্ধারণ করতে হয় এবং ব্যবহারকারীর বেছে নেওয়া বিকল্পের উত্তর দিতে হয়:

Kotlin

class MainActivity : Activity() {

    // The path to the root of this app's internal storage
    private lateinit var privateRootDir: File
    // The path to the "images" subdirectory
    private lateinit var imagesDir: File
    // Array of files in the images subdirectory
    private lateinit var ima<geFi>les: ArrayFile
    // Array of filenames corresponding to imageFiles
    private lateinit var imageFi<lename>s: ArrayString

    // Initialize the Activity
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Set up an Intent to send back to apps that request a file
        resultIntent = Intent("com.example.myapp.ACTION_RETURN_FILE")
        // Get the files/ subdirectory of internal storage
        privateRootDir = filesDir
        // Get the files/images subdirectory;
        imagesDir = File(privateRootDir, "images")
        // Get the files in the images subdirectory
        imageFiles = imagesDir.listFiles()
        // Set the Activity's result to null to begin with
        setResult(Activity.RESULT_CANCELED, null)
        /*
         * Display the file names in the ListView fileListView.
         * Back the ListView with the array imageFilenames, which
         * you can create by iterating through imageFiles and
         * calling File.getAbsolutePath() for each File
         */
        ...
    }
    ...
}

জাভা

public class MainActivity extends Activity {
    // The path to the root of this app's internal storage
    private File privateRootDir;
    // The path to the "images" subdirectory
    private File imagesDir;
    // Array of files in the images subdirectory
    File[] imageFiles;
    // Array of filenames corresponding to imageFiles
    String[] imageFilenames;
    // Initialize the Activity
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Set up an Intent to send back to apps that request a file
        resultIntent =
                new Intent("com.example.myapp.ACTION_RETURN_FILE");
        // Get the files/ subdirectory of internal storage
        privateRootDir = getFilesDir();
        // Get the files/images subdirectory;
        imagesDir = new File(privateRootDir, "images");
        // Get the files in the images subdirectory
        imageFiles = imagesDir.listFiles();
        // Set the Activity's result to null to begin with
        setResult(Activity.RESULT_CANCELED, null);
        /*
         * Display the file names in the ListView fileListView.
         * Back the ListView with the array imageFilenames, which
         * you can create by iterating through imageFiles and
         * calling File.getAbsolutePath() for each File
         */
         ...
    }
    ...
}

ফাইল বেছে নেওয়ার ব্যাপারে উত্তর দেওয়া

ব্যবহারকারী শেয়ার করা ফাইল বেছে নিলে, আপনার অ্যাপ্লিকেশনকে অবশ্যই নির্ধারণ করতে হবে যে কোন ফাইলটি বেছে নেওয়া হয়েছে এবং তারপরে ফাইলের জন্য একটি কন্টেন্ট URI তৈরি করতে হবে। যেহেতু Activity, ListView-এ উপলভ্য ফাইলের তালিকা দেখায়, তাই ব্যবহারকারী কোনও ফাইলের নামে ক্লিক করলে সিস্টেম onItemClick() মেথড কল করে, যেখান থেকে আপনি বেছে নেওয়া ফাইলটি পেতে পারেন।

এক অ্যাপ থেকে অন্য অ্যাপে ফাইলের URI পাঠানোর জন্য ইনটেন্ট ব্যবহার করার সময়, আপনাকে অবশ্যই এমন URI পেতে হবে যা অন্য অ্যাপ পড়তে পারে। Android 6.0 (API লেভেল 23) ও তার পরের যেকোনও ভার্সনে এই কাজ করার জন্য বিশেষ যত্ন নিতে হবে, কারণ Android-এর সেই ভার্সনে অনুমতির মডেল পরিবর্তন করা হয়েছে, বিশেষ করে READ_EXTERNAL_STORAGE বিপজ্জনক অনুমতি হয়ে গেছে, যা গ্রহণকারী অ্যাপের নাও থাকতে পারে।

এইসব বিষয় বিবেচনা করে, আমরা সাজেস্ট করি যে আপনি Uri.fromFile() ব্যবহার করা এড়িয়ে চলুন, কারণ এতে অনেক অসুবিধা আছে। এই পদ্ধতি:

  • বিভিন্ন প্রোফাইল জুড়ে ফাইল শেয়ার করার অনুমতি দেয় না।
  • এর জন্য আপনার অ্যাপে WRITE_EXTERNAL_STORAGE Android 4.4 (API লেভেল 19) বা তার আগের যেকোনও ভার্সনে চলা ডিভাইসে অনুমতি থাকতে হবে।
  • প্রাপক অ্যাপের কাছে READ_EXTERNAL_STORAGE অনুমতি থাকতে হবে, যা Gmail-এর মতো গুরুত্বপূর্ণ শেয়ার টার্গেটে কাজ করবে না, কারণ সেগুলির কাছে সেই অনুমতি নেই।

Uri.fromFile()ব্যবহার করার পরিবর্তে, আপনি নির্দিষ্ট URI-তে অ্যাক্সেস দেওয়ার জন্য URI অনুমতি ব্যবহার করতে পারেন। Uri.fromFile()-এর তৈরি করা file:// URI-তে URI অনুমতি কাজ না করলেও, কন্টেন্ট প্রদানকারীর সাথে যুক্ত URI-তে কাজ করে। এই ধরনের URI তৈরি করতে FileProvider API আপনাকে সাহায্য করতে পারে। এই পদ্ধতিটি সেইসব ফাইলের ক্ষেত্রেও কাজ করে যেগুলি এক্সটার্নাল স্টোরেজে নেই, কিন্তু ইনটেন্ট পাঠানো অ্যাপের লোকাল স্টোরেজে আছে।

onItemClick()-এ, বেছে নেওয়া ফাইলের ফাইলের নামের জন্য একটি File অবজেক্ট পান এবং getUriForFile()-এ আর্গুমেন্ট হিসেবে এটি পাস করুন, এর সাথে FileProvider-এর জন্য <provider> এলিমেন্টে আপনার নির্দিষ্ট করা কর্তৃপক্ষকেও পাস করুন। ফলাফল হিসেবে প্রাপ্ত কন্টেন্ট URI-তে অথরিটি, ফাইলের ডিরেক্টরির সাথে সম্পর্কিত পাথ সেগমেন্ট (XML মেটা-ডেটাতে যেভাবে উল্লেখ করা হয়েছে) এবং ফাইলের নাম সহ এর এক্সটেনশন থাকে। XML মেটা-ডেটার উপর ভিত্তি করে কীভাবে FileProvider ডিরেক্টরিকে পাথ সেগমেন্টে ম্যাপ করে তা এই বিভাগে শেয়ার করা যায় এমন ডিরেক্টরি নির্দিষ্ট করুন বর্ণনা করা আছে।

বেছে নেওয়া ফাইল কীভাবে শনাক্ত করতে হয় এবং তার জন্য কীভাবে কন্টেন্ট URI পেতে হয় তা নিম্নলিখিত স্নিপেট থেকে জানতে পারবেন:

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
            /*
             * Get a File for the selected file name.
             * Assume that the file names are in the
             * imageFilename array.
             */
            val requestFile = File(imageFilenames[position])
            /*
             * Most file-related method calls need to be in
             * try-catch blocks.
             */
            // Use the FileProvider to get a content URI
            val fileUri: Uri? = try {
                FileProvider.getUriForFile(
                        this@MainActivity,
                        "com.example.myapp.fileprovider",
                        requestFile)
            } catch (e: IllegalArgumentException) {
                Log.e("File Selector",
                        "The selected file can't be shared: $requestFile")
                null
            }
            ...
        }
        ...
    }

জাভা

    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.setOnItemClickListener(
                new AdapterView.OnItemClickListener() {
            @Override
            /*
             * When a filename in the ListView is clicked, get its
             * content URI and send it to the requesting app
             */
            public void onItemClick(AdapterView<?> adapterView,
                    View view,
                    int position,
                    long rowId) {
                /*
                 * Get a File for the selected file name.
                 * Assume that the file names are in the
                 * imageFilename array.
                 */
                File requestFile = new File(imageFilename[position]);
                /*
                 * Most file-related method calls need to be in
                 * try-catch blocks.
                 */
                // Use the FileProvider to get a content URI
                try {
                    fileUri = FileProvider.getUriForFile(
                            MainActivity.this,
                            "com.example.myapp.fileprovider",
                            requestFile);
                } catch (IllegalArgumentException e) {
                    Log.e("File Selector",
                          "The selected file can't be shared: " + requestFile.toString());
                }
                ...
            }
        });
        ...
    }

মনে রাখবেন, আপনি শুধুমাত্র সেইসব ফাইলের জন্য কন্টেন্ট URI তৈরি করতে পারবেন যেগুলি এমন ডিরেক্টরিতে থাকে যা <paths> এলিমেন্ট থাকা মেটা-ডেটা ফাইলে উল্লেখ করেছেন, যেমন শেয়ার করা যায় এমন ডিরেক্টরি নির্দিষ্ট করুন বিভাগে বর্ণনা করা হয়েছে। আপনি যদি getUriForFile() কোনও File এমন পাথে কল করেন যা আপনি নির্দিষ্ট করেননি, তাহলে আপনি একটি IllegalArgumentException পাবেন।

ফাইলের জন্য অনুমতি দিন

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

নিচে দেওয়া কোড স্নিপেট থেকে আপনি জানতে পারবেন কীভাবে ফাইলের জন্য রিড পার্মিশন সেট করতে হয়:

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
            ...
            if (fileUri != null) {
                // Grant temporary read permission to the content URI
                resultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                ...
            }
            ...
        }
        ...
    }

জাভা

    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Define a listener that responds to clicks in the ListView
        fileListView.setOnItemClickListener(
                new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView,
                    View view,
                    int position,
                    long rowId) {
                ...
                if (fileUri != null) {
                    // Grant temporary read permission to the content URI
                    resultIntent.addFlags(
                        Intent.FLAG_GRANT_READ_URI_PERMISSION);
                }
                ...
             }
             ...
        });
    ...
    }

সতর্কতা: setFlags()-এর মাধ্যমে কল করা হল সাময়িক অ্যাক্সেস সংক্রান্ত অনুমতি ব্যবহার করে আপনার ফাইল নিরাপদে অ্যাক্সেস করার একমাত্র উপায়। ফাইলের কন্টেন্ট URI-এর জন্য Context.grantUriPermission() মেথড কল করা এড়িয়ে চলুন, কারণ এই মেথড অ্যাক্সেস দেয় যা আপনি শুধুমাত্র Context.revokeUriPermission() কল করার মাধ্যমে প্রত্যাহার করতে পারবেন।

Uri.fromFile() ব্যবহার করবেন না। এটি রিসিভ করা অ্যাপকে READ_EXTERNAL_STORAGE অনুমতি নিতে বাধ্য করে, আপনি যদি একাধিক ব্যবহারকারীর মধ্যে শেয়ার করার চেষ্টা করেন, তাহলে এটি একেবারেই কাজ করবে না এবং Android-এর 4.4 (API লেভেল 19)-এর নিচের ভার্সনে, আপনার অ্যাপকে WRITE_EXTERNAL_STORAGE থাকতে হবে। এছাড়াও, Gmail অ্যাপের মতো খুব গুরুত্বপূর্ণ শেয়ার টার্গেটে READ_EXTERNAL_STORAGE নেই, যার ফলে এই কলটি সফল হচ্ছে না। পরিবর্তে, আপনি নির্দিষ্ট URI-তে অ্যাক্সেস দেওয়ার জন্য URI অনুমতি ব্যবহার করতে পারেন। URI অনুমতি file:// URI-তে কাজ না করলেও, কন্টেন্ট প্রদানকারীর সাথে যুক্ত URI-তে কাজ করে। Uri.fromFile() শুধুমাত্র এর জন্য আপনার নিজের কোড প্রয়োগ করার পরিবর্তে, আপনি FileProvider ব্যবহার করতে পারেন এবং করা উচিত যা ফাইল শেয়ারিং-এ ব্যাখ্যা করা হয়েছে।

অনুরোধকারী অ্যাপের সাথে ফাইল শেয়ার করুন

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

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
            ...
            if (fileUri != null) {
                ...
                // Put the Uri and MIME type in the result Intent
                resultIntent.setDataAndType(fileUri, contentResolver.getType(fileUri))
                // Set the result
                setResult(Activity.RESULT_OK, resultIntent)
            } else {
                resultIntent.setDataAndType(null, "")
                setResult(RESULT_CANCELED, resultIntent)
            }
        }
    }

জাভা

    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.setOnItemClickListener(
                new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView,
                    View view,
                    int position,
                    long rowId) {
                ...
                if (fileUri != null) {
                    ...
                    // Put the Uri and MIME type in the result Intent
                    resultIntent.setDataAndType(
                            fileUri,
                            getContentResolver().getType(fileUri));
                    // Set the result
                    MainActivity.this.setResult(Activity.RESULT_OK,
                            resultIntent);
                    } else {
                        resultIntent.setDataAndType(null, "");
                        MainActivity.this.setResult(RESULT_CANCELED,
                                resultIntent);
                    }
                }
        });

ব্যবহারকারী কোনও ফাইল বেছে নেওয়ার পরে, ক্লায়েন্ট অ্যাপে অবিলম্বে ফিরে যাওয়ার একটি উপায় দিন। এটি করার একটি উপায় হল, চেকমার্ক বা হয়ে গেছে বোতাম প্রদান করা। বোতামের android:onClick অ্যাট্রিবিউট ব্যবহার করে বোতামের সাথে একটি পদ্ধতি অ্যাসোসিয়েট করুন। মেথডে, কল করুন finish(). যেমন:

Kotlin

    fun onDoneClick(v: View) {
        // Associate a method with the Done button
        finish()
    }

জাভা

    public void onDoneClick(View v) {
        // Associate a method with the Done button
        finish();
    }

এই সম্পর্কিত অতিরিক্ত তথ্যের জন্য, এখানে দেখুন: