Adapt your Flutter app to Android 13
Tags: flutter, android
Published: 2022-10-19 14:06:31

Android 13 is the latest major Android version released this year and it comes with some behavioral changes that will force us to make some adaptations to our app to ensure it works properly.

We already have at our disposal this [informative document](https://developer.android.com/about/versions/13/behavior-changes-13) about these changes and links of interest to understand in more detail what they entail. However, in this article I want to take a more pragmatic approach through a simple example.

What I am going to present to you is an application running on Android 12. We are going to see what it does and what happens when we run it on Android 13. Once we have seen what happens, we will make the appropriate adjustments so that it works correctly on the latest version of Android.

> 📽 Video version available on [YouTube](https://youtu.be/GHkq5bQI140) and [Odysee](https://odysee.com/@svprdga:d/adapt-your-flutter-app-to-android-13:a)

{% embed https://youtu.be/GHkq5bQI140 %}

> Note: In this article I am going to focus basically on the changes related to reading files on disk and the new notifications permission, since they are functionalities that the vast majority of applications have. You can see the rest of the changes in the [official documentation](https://developer.android.com/about/versions/13/behavior-changes-13).

### The example app

Our app will consist of the following:
- A floating button that when pressed will ask us for permission to read files
- Once the permission is granted, we can choose a photo and it will be painted on the screen
- Additionally, a notification will be displayed indicating that the photo has been uploaded correctly

![App preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u9d5m329ydk0bugx3dp1.gif)

We will use [file_picker](https://pub.dev/packages/file_picker) to be able to select files and [permission_handler](https://pub.dev/packages/permission_handler) to request the system permissions. To show notifications we will use [flutter_local_notifications](https://pub.dev/packages/flutter_local_notifications). The `pubspec.yaml` file looks like this:

```yaml
name: adapt_android_13
description: Adapt Android 13
publish_to: 'none'
version: 1.0.0+1

environment:
  sdk: '>=2.18.2 <3.0.0'

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  file_picker: ^5.2.0+1
  permission_handler: ^10.2.0
  flutter_local_notifications: ^12.0.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0

flutter:
  uses-material-design: true
```

Let's quickly see the code that compose this application:

```dart
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
    FlutterLocalNotificationsPlugin();
const AndroidInitializationSettings initializationSettingsAndroid =
    AndroidInitializationSettings('ic_notification');

const InitializationSettings initializationSettings = InitializationSettings(
  android: initializationSettingsAndroid,
);

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await flutterLocalNotificationsPlugin.initialize(initializationSettings,
      onDidReceiveNotificationResponse: (NotificationResponse response) {});

  runApp(const App());
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Adapt Android 13',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MainScreen(),
    );
  }
}
```

We first initialize the notifications using the [flutter_local_notifications](https://pub.dev/packages/flutter_local_notifications) plugin. We then launch our app encapsulated in the App widget.

```dart
class _MainScreenState extends State<MainScreen> {
  String? _filepath;

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Adapt Android 13'),
      ),
      body: _filepath != null
          ? Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  SizedBox(
                    width: 250.0,
                    height: 250.0,
                    child: Image.file(File(_filepath!)),
                  ),
                ],
              ),
            )
          : Container(),
      floatingActionButton: FloatingActionButton(
        onPressed: _pickFile,
        tooltip: 'Pick image',
        child: const Icon(Icons.add),
      ),
    );
  }

  Future<void> _pickFile() async {
    final result = await Permission.storage.request();

    if (result == PermissionStatus.granted) {
      final FilePickerResult? result =
          await FilePicker.platform.pickFiles(type: FileType.image);

      final path = result?.files.single.path;

      if (path != null) {
        _postNotification();
        setState(() {
          _filepath = path;
        });
      }
    }
  }

  Future<void> _postNotification() async {
    const AndroidNotificationDetails androidNotificationDetails =
        AndroidNotificationDetails(
      'default_notification_channel_id',
      'Default',
      importance: Importance.max,
      priority: Priority.max,
    );
    const NotificationDetails notificationDetails =
        NotificationDetails(android: androidNotificationDetails);
    await flutterLocalNotificationsPlugin.show(
        0, 'Image successfully loaded', '', notificationDetails);
  }
}
```

In our main screen we have a state variable `_filepath`, which will store the path to the chosen file. When the user clicks the button, the `_pickFile()` method will be called, which will take care of requesting permission to read files and will allow us to choose a file of type image.

Once selected, the status is updated and the picture is displayed on the screen, and the `_postNotification()` method is called to publish the informative notification.

This code snippet works correctly in versions before Android 13, let's see what happens if we run it in the new version of Android:

### Android 13 changes

If we run this app on a device with Android 13 we will see that it works more or less the same as before, except that now, once the image is selected, it asks us for permission to show notifications.

![Notification permission](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/roaeh0nixwoi5ftngu3g.png)

Although the app continues to work, we have the problem that it is the system that is choosing the moment to ask for permission to show notifications, when ideally we should be the ones who decide when it is requested and if any explanatory text must be shown before.

The problem is aggravated if we target Android 13:

```
defaultConfig {
    applicationId "com.example.adaptandroid13"
    minSdkVersion 22
    targetSdkVersion 33 //Target Android 13
    versionCode flutterVersionCode.toInteger()
    versionName flutterVersionName
    multiDexEnabled true
}
```

Now when pressing the floating button we are not even asked for permission to read files. **Why is this happening?**

Android 13 incorporates a granular permission system when it comes to reading files. While before it was enough to have the [READ_EXTERNAL_STORAGE](https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE) permission granted to read any file from external storage, now we will have to specifically define what type of files we intend to read.

In this app we want to read images, so we will ask for the [READ_MEDIA_IMAGES](https://developer.android.com/reference/android/Manifest.permission#READ_MEDIA_IMAGES) permission. If we wanted to read videos we would ask for [READ_MEDIA_VIDEO](https://developer.android.com/reference/android/Manifest.permission#READ_MEDIA_VIDEO) and if we wanted audio files we would have [READ_MEDIA_AUDIO](https://developer.android.com/reference/android/Manifest.permission#READ_MEDIA_AUDIO).

Let's make the following adjustments to the _AndroidManifest_ file to resolve this issue:

```xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
    android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
```

Through this change we are indicating to the system that for Android 12 or lower we continue to depend on [READ_EXTERNAL_STORAGE](https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE), while from Android 13 we will use the new granular permissions system.

Other change that Android 13 brings is the need to have explicit permission from the user to display notifications, as has been the case in iOS for a long time.

Let's declare that permission in _AndroidManifest_:

```xml
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
```

Now we are going to modify `_pickFile()` so that in versions before Android 13 it asks for the storage permission and in later versions the new notification and image permissions, to do this, first install [device_info_plus](https://pub.dev/packages/device_info_plus) to be able to know what version of Android is running on the device:

`flutter pub add device_info_plus`

Now modify `_pickFile()` as follows:

```dart
Future<void> _pickFile() async {
  final androidInfo = await DeviceInfoPlugin().androidInfo;
  late final Map<Permission, PermissionStatus> statusess;

  if (androidInfo.version.sdkInt <= 32) {
    statusess = await [
      Permission.storage,
    ].request();
  } else {
    statusess = await [Permission.photos, Permission.notification].request();
  }

  var allAccepted = true;
  statusess.forEach((permission, status) {
    if (status != PermissionStatus.granted) {
      allAccepted = false;
    }
  });

  if (allAccepted) {
    final FilePickerResult? result =
        await FilePicker.platform.pickFiles(type: FileType.image);

    final path = result?.files.single.path;

    if (path != null) {
      _postNotification();
      setState(() {
        _filepath = path;
      });
    }
  }
}
```

And with this last change we can now choose an image, it is displayed on the screen and the informative notification is also shown.

### Conclusion

In this article I have made a simple review of two of the behavioral changes that Android 13 incorporates: the new granular permission system for reading files and the new permission to be able to show notifications.

In addition to these two, I recommend that you read the [official documentation](https://developer.android.com/about/versions/13/behavior-changes-13#granular-media-permissions) to see these changes in more detail as well as to see the rest of the changes that this version of Android incorporates since they may affect the operation of your application.

You can check the source code of the projecte used in this article [here](https://github.com/svprdga/Adapt-Android-13).

Thanks for reading this far, happy coding!