Как поделиться любым файлом в Kotlin?
Оказывается, начиная с версии targetSdkVersion 24 и выше недостаточно просто написать функцию Share file, а нужно еще настроить provider в файле AndroidManifest.xml. Если вы этого не сделаете то при попытке поделиться например в WhatsApp, Telegram или Email вы получите ошибку “intent Share File.type unsupported attachment PID:…“. Эта ошибка говорит, что ваш вложенный файл не поддерживается.
Что нужно сделать, чтобы всё работало как надо?
1. Добавим разрешение на чтение и запись фалов
Разрешения записываем разумеется в файле AndroidManifest.xml. Тут сразу есть нюанс:
До Android 10 используется разрешение:
<uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE”/>
Начиная с Android 10 и выше:
<uses-permission android:name=”android.permission.MANAGE_EXTERNAL_STORAGE” tools:ignore=”ScopedStorage” />
Ну и конечно просто написать разрешения в файле AndroidManifest.xml это было бы слишком просто, их нужно еще запросить, когда приложение запустится, для этого напишем следующую функцию:
// ----------------- ПРОВЕРКА НА ЗАПИСЬ В ПАМЯТЬ ------------------------------------------------ // 1. Разрешение на ЧТЕНИЕ и ЗАПИСЬ ФАЙЛОВ fun hasManageExternalStoragePermission(): Boolean { val RESULT_CODE = 123 // Если Android 11 и выше if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { return if (Environment.isExternalStorageManager()) { true } else { if (Environment.isExternalStorageLegacy()) { return true } try { val intent = Intent() intent.action = Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION intent.data = Uri.parse("package:ru.jandroid.fileencrypt") APP.startActivityForResult(intent, RESULT_CODE) false } catch (e: Exception) { Toast.makeText(APP, "Разрешение отклонено", Toast.LENGTH_SHORT).show() false } } } // Если Android ниже 10 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { // 2. registerPermissionListener() return checkWritePermissions() } else return true } // 2. Получить разрешение на ЗАПИСЬ (Android < R) fun checkWritePermissions(): Boolean { when{ ContextCompat.checkSelfPermission(APP, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED -> { //Toast.makeText(APP, "Разрешение было получено ранее", Toast.LENGTH_SHORT).show() return true } else -> { permisLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) return false } } } // 2. Слушатель разрешения (Android < R) fun registerPermissionListener(){ permisLauncher = APP.registerForActivityResult(ActivityResultContracts.RequestPermission()){ if(it) { //Toast.makeText(APP, "Вы дали разрешение для записи.", Toast.LENGTH_SHORT).show() }else{ //Toast.makeText(APP, "Разрешение отклонено", Toast.LENGTH_SHORT).show() showDialog() } } } // Диалог поясняющий необходимость разрешения и повтор запроса fun showDialog() { AlertDialog.Builder(APP) .setTitle("Разрешение на запись") .setMessage("Дайте разрешение на запись. В противном случае приложение работать не будет!") .setPositiveButton("Разрешить") { dialog, which -> if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) checkWritePermissions() else hasManageExternalStoragePermission() } .setNegativeButton("Отклонить") { dialog, which -> APP.finish() } .show() }
Теперь в MainActivity.kt в методе onCreate вызовите функцию:
hasManageExternalStoragePermission() и добавим слушатель на действие пользователя:
class MainActivity : AppCompatActivity() { ... override fun onCreate(savedInstanceState: Bundle?) { ... hasManageExternalStoragePermission() ... } // Если версия Android 11 проверяем разрешил ли пользователь запись override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && Environment.isExternalStorageManager()){ //Доступ к памяти разрешен }else { //Отказано в доступе, то открываем диалог showDialog() } }
Теперь у вас будет разрешение на чтение и запись файлов из внутренней памяти.
2. Напишем Provider
В файле MainActivity.kt нужно написать следующий код:
<application ...> ... <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/> </provider> ... </application>
И создать файл provider_paths.xml в res/xml . Нижеприведенный код позволит передавать все файлы.
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <files-path name="files" path="."/> <external-path name="external_files" path="."/> </paths>
3. Основная функция Share files
fun shareFile(file: File){ val uri = FileProvider.getUriForFile(APP, BuildConfig.APPLICATION_ID + ".provider", file) val intent = Intent(Intent.ACTION_SEND) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) intent.setType("*/*") intent.putExtra(Intent.EXTRA_STREAM, uri) intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); APP.startActivity(intent) }
Вот такие вот сложности с маленькой функцией Share file (поделится файликом). Но теперь все должно работать.