[:ru]В этом уроке мы рассмотрим, что такое запросы разрешений во время исполнения приложения и как с ними работать. Я покажу, как инициировать запрос разрешения, как обрабатывать ответы пользователя, а также что делать, если пользователь отказал в разрешении и установил флаг «больше не спрашивать» в диалоге запроса разрешения.
Кто не в теме, немного теории:
Как вы помните, до версии Android 6.0 все запрашиваемые разрешения отображались пользователю при установке приложения. И если пользователь не предоставлял приложению хотя бы одно разрешение из списка, приложение не устанавливалось.
Теперь все изменилось.
Начиная с Android API 23 разрешения поделили на обычные (normal) и опасные (dangerous).
Обычные разрешения — это те, которые не несут прямой угрозы конфиденциальности пользователя. К ним относятся разрешения на доступ к интернету и сетевым соединениям, доступ к беспроводным модулям, управлению оповещениями, звуком и т.д.
К опасным относятся разрешения на доступ к календарю, камере, контактам, местоположению, микрофону, звонкам, смс, датчикам, а также разрешение на чтение и запись данных во внешнюю память устройства.
Полный список обычных и опасных разрешений можно посмотреть по ссылке.
Во вторых, в связи с этим изменилось поведение приложений при установке, и здесь возможны варианты.
Например, рассмотрим случай, когда устройство работает под управлением Android версии 5.1 или ниже, или целевая версия SDK в файле сборки приложения имеет значение 22 или ниже.
При установке такого приложения все обычные разрешения, указанные в манифесте, будут предоставлены без запроса.
Если в манифесте есть опасное разрешение, то для его предоставления будет отображаться запрос пользователю. И если пользователь откажет, приложение не будет установлено.
Если же устройство работает под управлением Android 6.0 или выше, или целевая версия SDK установлена на 23 или выше, то приложение может быть установлено без запросов разрешений. Но при этом ему будут предоставлены только обычные, безопасные разрешения.
Опасные разрешения могут быть запрошены в момент работы приложения, когда пользователь попытается использовать функцию, для которой необходимо это разрешение. При этом пользователь может отказать в разрешении, и приложение спокойно продолжит работу с ограниченными функциями.
Теперь давайте рассмотрим пример работы с разрешениями на практике.
Это приложение с одной кнопкой, по нажатию которой создается папка в хранилище устройства, для чего приложению требуется разрешение на запись во внешнюю память.
Исходный код этого проекта вы можете посмотреть ниже:
В макете экрана одна кнопка.
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/activity_view" tools:context="info.fandroid.runtimepermissions.MainActivity"> <Button android:layout_width="wrap_content" android:id="@+id/btn" android:text="make folder" android:layout_height="wrap_content" /> </android.support.constraint.ConstraintLayout>
Также нужен идентификатор для корневого view, это понадобится для работы снекбара.
Поскольку снекбар является компонентом библиотеки материального дизайна, добавьте ее в зависимости файла сборки модуля проекта.
apply plugin: 'com.android.application' android { compileSdkVersion 25 buildToolsVersion "25.0.2" defaultConfig { applicationId "info.fandroid.runtimepermissions" minSdkVersion 19 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.3.0' compile 'com.android.support.constraint:constraint-layout:1.0.0-beta5' compile "com.android.support:design:25.3.0" testCompile 'junit:junit:4.12' }
Также нам понадобится разрешение на запись в память, пропишем его в манифесте.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="info.fandroid.runtimepermissions"> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
Теперь рассмотрим код класса MainActivity.
package info.fandroid.runtimepermissions; import android.Manifest; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.provider.Settings; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; import android.support.design.widget.Snackbar; import java.io.File; public class MainActivity extends AppCompatActivity { private static final int PERMISSION_REQUEST_CODE = 123; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button btn = (Button) findViewById(R.id.btn); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (hasPermissions()){ // our app has permissions. makeFolder(); } else { //our app doesn't have permissions, So i m requesting permissions. requestPermissionWithRationale(); } } }); } private void makeFolder(){ File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(),"fandroid"); if (!file.exists()){ Boolean ff = file.mkdir(); if (ff){ Toast.makeText(MainActivity.this, "Folder created successfully", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(MainActivity.this, "Failed to create folder", Toast.LENGTH_SHORT).show(); } } else { Toast.makeText(MainActivity.this, "Folder already exist", Toast.LENGTH_SHORT).show(); } } private boolean hasPermissions(){ int res = 0; //string array of permissions, String[] permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; for (String perms : permissions){ res = checkCallingOrSelfPermission(perms); if (!(res == PackageManager.PERMISSION_GRANTED)){ return false; } } return true; } private void requestPerms(){ String[] permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){ requestPermissions(permissions,PERMISSION_REQUEST_CODE); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { boolean allowed = true; switch (requestCode){ case PERMISSION_REQUEST_CODE: for (int res : grantResults){ // if user granted all permissions. allowed = allowed && (res == PackageManager.PERMISSION_GRANTED); } break; default: // if user not granted permissions. allowed = false; break; } if (allowed){ //user granted all permissions we can perform our task. makeFolder(); } else { // we will give warning to user that they haven't granted permissions. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)){ Toast.makeText(this, "Storage Permissions denied.", Toast.LENGTH_SHORT).show(); } else { showNoStoragePermissionSnackbar(); } } } } public void showNoStoragePermissionSnackbar() { Snackbar.make(MainActivity.this.findViewById(R.id.activity_view), "Storage permission isn't granted" , Snackbar.LENGTH_LONG) .setAction("SETTINGS", new View.OnClickListener() { @Override public void onClick(View v) { openApplicationSettings(); Toast.makeText(getApplicationContext(), "Open Permissions and grant the Storage permission", Toast.LENGTH_SHORT) .show(); } }) .show(); } public void openApplicationSettings() { Intent appSettingsIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + getPackageName())); startActivityForResult(appSettingsIntent, PERMISSION_REQUEST_CODE); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == PERMISSION_REQUEST_CODE) { makeFolder(); return; } super.onActivityResult(requestCode, resultCode, data); } public void requestPermissionWithRationale() { if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) { final String message = "Storage permission is needed to show files count"; Snackbar.make(MainActivity.this.findViewById(R.id.activity_view), message, Snackbar.LENGTH_LONG) .setAction("GRANT", new View.OnClickListener() { @Override public void onClick(View v) { requestPerms(); } }) .show(); } else { requestPerms(); } } }
Константа PERMISSION_REQUEST_CODE хранит произвольное значение, по которому в дальнейшем можно определить, на какой запрос разрешения вам пришел ответ.
Аналогично мы получали результат от activity, используя startActivityForResult, вспоминаем уроки по этой теме на нашем канале StartAndroid.
В методе onCreate создадим кнопку и присвоим ей слушатель нажатия.
В методе onClick проверяем логический результат метода hasPermissions, и вызываем метод создания папки, makeFolder. В этом методе мы просто создаем новый файл с именем fandroid и выводим несколько тостов с сообщениями, в зависимости от того, удалось или не удалось создать файл, или он уже создан.
Если же результат в методе onClick равен false, то вызываем метод requestPermissionWithRationale, который будет в свою очередь вызывать метод запроса разрешения. Мы перейдем к нему позже.
Ниже метод hasPermissions, который с помощью метода checkCallingOrSelfPermission в цикле проверяет предоставленные приложению разрешения и сравнивает их с тем, которое нам необходимо.
При отсутствии разрешения метод будет возвращать false, а при наличии разрешения — true.
В методе requestPerms создаем массив permissions , который содержит ссылки на константы класса Manifest с кодами разрешений. Поскольку используется массив, то одновременно можно запрашивать несколько разрешений.
После проверки версии устройства Запрос разрешения выполняет метод requestPermissions, которому мы передаем массив нужных нам разрешений и константу PERMISSION_REQUEST_CODE
После вызова этого метода пользователю отображается диалоговое окно с запросом разрешения.
Ответ пользователя приходит в метод onRequestPermissionsResult. Параметры requestCode и permissions содержат данные, которые вы передавали при запросе разрешений. Основные данные здесь несет массив grantResults, в котором находится информация о том, получены разрешения или нет. Каждому i-му элементу permissions соответствует i-ый элемент из grantResults.
Здесь мы обрабатываем ответ — проверяем, если разрешение предоставлено пользователем, о чем будет свидетельствовать ответ PERMISSION_GRANTED, то вызываем метод makeFolder, а если нет, то проверяем версию устройства,
и если она равна Андроид 6.0 или выше — проверяем, отказывался ли ранее пользователь предоставлять это разрешение. В таком случае метод с длинным названием shouldShowRequestPermissionRationale вернет нам true.
Одной из проблем может стать опция “Don’t ask again”, которая появляется при повторном запросе разрешения. При её выборе диалог запроса не будет больше появляться.
Метод shouldShowRequestPermissionRationale в таком случае будет возвращать false, а в onRequestPermissionsResult будет получен результат PERMISSION_DENIED — отказ в получении разрешения.
И останется единственный способ — попросить пользователя включить разрешение непосредственно через настройки приложения в разделе Permissions.
В этом случае мы будем вызывать метод showNoStoragePermissionSnackbar, в котором создаем Snackbar с кнопкой действия, ведущей в настройки разрешений приложения, и генерирующей тост для пользователя с инструкцией.
настройки открывает метод openApplicationSettings(), где мы создаем и отправляем соответствующий интент. В примере используются startActivityForResult и onActivityResult чтобы определить, что пользователь вернулся из activity настроек обратно в приложение и попробовать выполнить действие, которое нельзя было сделать без нужного разрешения.
Если вы ранее уже запрашивали разрешение, но пользователь отказался предоставить его, необходимо объяснить ему причину запроса.
это мы обыграем в методе requestPermissionWithRationale() — Запрос разрешения с обоснованием
Здесь также проверяем ответ метода shouldShowRequestPermissionRationale и если он true, создаем снекбар с сообщением для пользователя и кнопкой, по нажатию на которую будем вызывать метод получения разрешения.
Здесь же будет вызываться метод получения разрешения в случае первого запроса.
Теперь установим приложение на устройство и проверим его работу на эмуляторе с Андроид 6.0 (процесс тестирования смотрите в видео выше).
При нажатии кнопки появляется запрос разрешения.
Если пользователь отказал, то приложение продолжает работу, но папка не создается.
При повторном нажатии кнопки всплывает снекбар с объяснением необходимости предоставления запроса и кнопкой, при нажатии которой вновь отображается запрос.
Если же пользователь откажет снова и при этом выберет опцию «Не спрашивать больше», то при дальнейших попытках использования функции отображается снекбар с предложением открыть настройки и предоставить разрешение для приложения непосредственно в настройках.
После установки разрешения и возврата в приложение папка успешно создается.
На этом все. Вопросы задавайте в комментариях к уроку.[:en]In this lesson we will look at what the permission requests during the execution of the application and how to work with them. I’ll show you how to initiate a request for permission, how to handle the user’s answers and what to do if the user refused permission and planted the flag of «don’t ask» in the dialog asking permission.
In my life, a little theory:
As you remember, to Android 6.0, all the requested permissions is displayed to the user when the application is installed. And if the user does not provide the application to at least one solution from the list, the application could not be installed.
Now everything has changed.
Starting with Android 23 API permissions were divided into normal (normal) and hazardous (dangerous).
Regular permissions are those that are not a direct threat to user privacy. These include permissions to access Internet and network connections, access to wireless modules, management alerts, sound, etc.
Threat to include permissions to access calendar, camera, contacts, location, microphone, calls, SMS, sensors, as well as permission to read and write data to the external memory device.
Full list of normal and dangerous permissions, you can view the link.
Secondly, in connection with this changed the behavior of applications when you install, there may be options.
For example, consider the case when the device is running Android version 5.1 or lower, or target SDK version in the Assembly file of the application has a value of 22 or below.
When you install this app all the usual permissions specified in the manifest will be provided without prompting.
If the manifest is a dangerous permission to grant you will be prompted to the user. And if the user refuses, the application will not be installed.
If the device is running Android 6.0 or higher, or target SDK version set to 23 or higher, the application can be installed without your permission requests. But it received only conventional, safe solutions.
Dangerous permissions may be requested at the time of the application, when the user tries to use a feature that requires this permission. The user may refuse permission and the app will quietly continue to work with limited functions.
Now let’s look at an example of working with permissions in practice.
This application with one button which creates a folder in the storage device for which the app needs permission to write to external memory.
The source code of this project you can watch below:
The screen layout has one button.
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/activity_view" tools:context="info.fandroid.runtimepermissions.MainActivity"> <Button android:layout_width="wrap_content" android:id="@+id/btn" android:text="make folder" android:layout_height="wrap_content" /> </android.support.constraint.ConstraintLayout>
Also need the ID for the root view, it is necessary for snackbar.
Since snackbar is a component library for material design, add it to the dependencies of the build file of the module project.
apply plugin: 'com.android.application' android { compileSdkVersion 25 buildToolsVersion "25.0.2" defaultConfig { applicationId "info.fandroid.runtimepermissions" minSdkVersion 19 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.3.0' compile 'com.android.support.constraint:constraint-layout:1.0.0-beta5' compile "com.android.support:design:25.3.0" testCompile 'junit:junit:4.12' }
Also, we need permission to write to memory, write it into the manifest.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="info.fandroid.runtimepermissions"> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
Now consider the code of the MainActivity class.
package info.fandroid.runtimepermissions; import android.Manifest; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.provider.Settings; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; import android.support.design.widget.Snackbar; import java.io.File; public class MainActivity extends AppCompatActivity { private static final int PERMISSION_REQUEST_CODE = 123; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button btn = (Button) findViewById(R.id.btn); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (hasPermissions()){ // our app has permissions. makeFolder(); } else { //our app doesn't have permissions, So i m requesting permissions. requestPermissionWithRationale(); } } }); } private void makeFolder(){ File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(),"fandroid"); if (!file.exists()){ Boolean ff = file.mkdir(); if (ff){ Toast.makeText(MainActivity.this, "Folder created successfully", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(MainActivity.this, "Failed to create folder", Toast.LENGTH_SHORT).show(); } } else { Toast.makeText(MainActivity.this, "Folder already exist", Toast.LENGTH_SHORT).show(); } } private boolean hasPermissions(){ int res = 0; //string array of permissions, String[] permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; for (String perms : permissions){ res = checkCallingOrSelfPermission(perms); if (!(res == PackageManager.PERMISSION_GRANTED)){ return false; } } return true; } private void requestPerms(){ String[] permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){ requestPermissions(permissions,PERMISSION_REQUEST_CODE); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { boolean allowed = true; switch (requestCode){ case PERMISSION_REQUEST_CODE: for (int res : grantResults){ // if user granted all permissions. allowed = allowed && (res == PackageManager.PERMISSION_GRANTED); } break; default: // if user not granted permissions. allowed = false; break; } if (allowed){ //user granted all permissions we can perform our task. makeFolder(); } else { // we will give warning to user that they haven't granted permissions. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)){ Toast.makeText(this, "Storage Permissions denied.", Toast.LENGTH_SHORT).show(); } else { showNoStoragePermissionSnackbar(); } } } } public void showNoStoragePermissionSnackbar() { Snackbar.make(MainActivity.this.findViewById(R.id.activity_view), "Storage permission isn't granted" , Snackbar.LENGTH_LONG) .setAction("SETTINGS", new View.OnClickListener() { @Override public void onClick(View v) { openApplicationSettings(); Toast.makeText(getApplicationContext(), "Open Permissions and grant the Storage permission", Toast.LENGTH_SHORT) .show(); } }) .show(); } public void openApplicationSettings() { Intent appSettingsIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + getPackageName())); startActivityForResult(appSettingsIntent, PERMISSION_REQUEST_CODE); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == PERMISSION_REQUEST_CODE) { makeFolder(); return; } super.onActivityResult(requestCode, resultCode, data); } public void requestPermissionWithRationale() { if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) { final String message = "Storage permission is needed to show files count"; Snackbar.make(MainActivity.this.findViewById(R.id.activity_view), message, Snackbar.LENGTH_LONG) .setAction("GRANT", new View.OnClickListener() { @Override public void onClick(View v) { requestPerms(); } }) .show(); } else { requestPerms(); } } }
Constant PERMISSION_REQUEST_CODE stores arbitrary value that can later determine which prompt did you get the answer.
Similarly we get the result from activity using startActivityForResult, remembering the lessons on this topic on our channel StartAndroid.
In the onCreate method, create a button and assign a listener to depression.
In onClick method, check the Boolean result of the method hasPermissions and the called method to create a folder makeFolder. In this method we simply create a new file with the name fandroid and displaying some toast message, depending on whether you succeeded or failed to create the file, or it has already been created.
If the result of the onClick method set to false, then the called method requestPermissionWithRationale, which will in turn call the method request. We’ll get to that later.
Below method hasPermissions that using the method checkCallingOrSelfPermission in the loop provided the application checks the permissions and compares them with those that we need.
In the absence of permission, the method will return false, and if the permission is true.
In the method requestPerms create a permissions array that contains references to class constants Manifest codes and permits. Since you are using array, you can simultaneously request multiple permissions.
After checking version of device the Request executes the method requestPermissions which we pass the array we need permits and a constant PERMISSION_REQUEST_CODE
After calling this method the user is presented with a dialog box requesting authorization.
The user’s response comes in the method onRequestPermissionsResult. Parameters requestCode and permissions contain the data that you passed when you request permission. The basic data here carries an array grantResults, which is information about the permit or not. Each i-th element permissions corresponds to the i-th element of grantResults.
Here we handle the response — check if the resolution provided by the user, as will be seen in the response PERMISSION_GRANTED, called makeFolder method, and if not, then check the version of the device
and if it is equal to Android 6.0 or higher — check, refused informed whether the user can grant this permission. In this case, the method with the long name shouldShowRequestPermissionRationale will return true.
One of the problems may be the option “Don’t ask again”, which appears when you re-request permission. The prompt dialog will no longer appear.
ShouldShowRequestPermissionRationale method in this case will return false, and in onRequestPermissionsResult will be the result PERMISSION_DENIED — denial of permit.
And will remain the only way is to ask the user to enable a resolution directly through the app settings in the Permissions tab.
In this case we will call the method showNoStoragePermissionSnackbar, which create a Snackbar with the action button leading to the settings app permissions, and generating a toast to the user with instructions.
settings opens the method openApplicationSettings () where we create and send the appropriate intent. The example uses startActivityForResult and onActivityResult to determine that the user back from settings activity back to the app and try to perform an action that could not be done without the right permits.
If you had previously requested authorization, but the user refused to provide it, you need to explain to him the reason for the request.
we beat the method requestPermissionWithRationale() — the resolve Request with justification
Here also check the response method shouldShowRequestPermissionRationale and if it is true, create SeekBar with a message for the user and a button which will call a method of obtaining a permit.
Here is called the method of obtaining a permit in the case of the first request.
Now install the app on your device and check out his work on the emulator with Android 6.0 (testing process, see the video above).
When you press the button, a permission request.
If the user has denied the application continues working, but the folder is not created.
Pressing the button POPs up snackbar explaining the necessity of a request and a button which, when pressed, you are prompted again.
If the user refuses again and choose the option «do Not ask again», then further attempts to use the function displayed snackbar a proposal to open the settings and grant permission for the application directly in the settings.
After you set the permissions and return to the application folder successfully created.
That’s about it. Questions to ask in the comments to the lesson.[:]
В Android 10 or higher в манифест нужно добавить
android:requestLegacyExternalStorage=»true»
иначе «кина не будэ»
src: https://developer.android.com/training/data-storage/use-cases
Добрый день!
Данный пример — https://www.fandroid.info/android-runtime-permissions-primer-realizatsii/ — демонстрирует, как в рантайме давать разрешение на запись, правильно?
Почему тогда в нижепроцитированном отрывке — он начинается со строки 155 — запрашивается разрешение на чтение?
public void requestPermissionWithRationale() {
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.READ_EXTERNAL_STORAGE)) {
final String message = «Storage permission is needed to show files count»;
Спасибо за ответ.
P.S. При изменении типа разрешения на WRITE_EXTERNAL_STORAGE пример так же отрабатывает корректно. Потому что дается разрешение на группу? Проверялось на 29 версии.