Manage External Storage Using Flutter GetX

Dinesh Falwadiya
5 min readJan 29, 2025

Dealing with external storage in mobile app development can be very useful for example reading & writing a text file to external storage which can act as a Log file for our app. Later on, clients or users can share this with the developer for further investigation. This is just one case of using external storage we can use this for multiple purposes based on the requirement.

So for this blog, we are going to write a text file in the external storage of both Android & iOS.

  1. Android the file will be stored in the Downloads folder.

2. iOS The file will be saved at File App On My iPhone location.

So let’s begin with prerequisites first, we need to install all the necessary plugins to make our work easy.

  1. GetX — https://pub.dev/packages/get
  2. Path Provider — https://pub.dev/packages/path_provider
  3. Permission Handler — https://pub.dev/packages/permission_handler

Below is the sample UI of the App having one TextField & two ElevatedButtons one for the write file & one for the read file.

Write & Read File UI

Android Implementation

Add the following permissions in the AndroidManifest.xml to manage the external storage.

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

I added the above permission in all the AndroidManifest.xml files that are main, debug & profile for the safer side.

Additionally do add the requestLegacyExternalStorage to true in the application tag of the main AndroidManifest.xml file.

<application
android:requestLegacyExternalStorage="true"

If you are facing a Gradle Kotlin version error make the following changes in the settings.gradle file’s plugin section.

plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.2.2" apply false
id "org.jetbrains.kotlin.android" version "2.0.21" apply false
}

iOS Implementation

Add the following permissions in the info.plist file located in

ios → Runner → info.plist

<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>UIFileSharingEnabled</key>
<true/>

After adding these 2 permissions the same will look as given below in the XCode.

info.plist

Code

The main.dart file code is as follows.

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'pages/HomePage.dart';
import 'bindings/AppBinding.dart';

void main() async {
WidgetsFlutterBinding.ensureInitialized();
AppBindings().dependencies();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});

// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return GetMaterialApp(
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}

AppBindings.dart

import 'package:get/get.dart';
import '../controllers/HomeController.dart';

class AppBindings implements Bindings {
@override
void dependencies() {
Get.lazyPut(() => HomeController());
}
}

HomePage.dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/HomeController.dart';

//ignore: must_be_immutable
class HomePage extends StatelessWidget {
HomeController homeController = Get.find<HomeController>();

HomePage({super.key});

@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: Colors.white,
body: _createUI(),
),
);
}

Widget _createUI() {
return Column(
children: [
Container(
color: Color(0xff29b6f6),
padding: EdgeInsets.fromLTRB(20, 0, 0, 0),
height: 80,
width: double.infinity,
child: Align(
alignment: Alignment.centerLeft,
child: Text(
'Read & Write File',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18),
),
),
),
Padding(
padding: EdgeInsets.fromLTRB(12, 20, 12, 20),
child: SizedBox(
height: 150,
child: TextField(
controller: homeController.dataController,
decoration: InputDecoration(
hintText: 'Enter your awesome content',
hintStyle: TextStyle(color: Colors.grey),
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black, width: 1),
borderRadius: BorderRadius.circular(15)),
),
keyboardType: TextInputType.multiline,
maxLines: null,
expands: true,
),
),
),
Padding(
padding: EdgeInsets.fromLTRB(12, 0, 12, 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
homeController.writeFile();
},
style: ElevatedButton.styleFrom(
fixedSize: Size(150, 50),
foregroundColor: Colors.white,
backgroundColor: Color(0xff29b6f6),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), // <-- Radius
),
),
child: Text('Write File'),
),
ElevatedButton(
onPressed: () {
homeController.readFile();
},
style: ElevatedButton.styleFrom(
fixedSize: Size(150, 50),
foregroundColor: Colors.white,
backgroundColor: Color(0xff29b6f6),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), // <-- Radius
),
),
child: Text('Read File'),
)
],
),
),
SizedBox(
height: 20,
),
Divider(
height: 1,
color: Colors.black,
),
SizedBox(
height: 20,
),
Obx(() => Text(homeController.content.value)),
],
);
}
}

HomeController.dart

import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';

class HomeController extends GetxController {
TextEditingController dataController = TextEditingController();

RxString content = 'Data Read from file will appear here'.obs;

Future<PermissionStatus> _getStoragePermission() async {
var permissionStatus = Platform.isIOS
? await Permission.storage.request()
: await Permission.manageExternalStorage.request();
if (permissionStatus.isDenied || permissionStatus.isRestricted) {
// Here just ask for the permission for the first time
permissionStatus = await Permission.storage.request();

// I noticed that sometimes popup won't show after user press deny
// so I do the check once again but now go straight to appSettings
if (permissionStatus.isDenied) {
await openAppSettings();
}
} else if (permissionStatus.isPermanentlyDenied) {
// Here open app settings for user to manually enable permission in case
// where permission was permanently denied
await openAppSettings();
}
return permissionStatus;
}

void writeFile() async {
if (dataController.text.isNotEmpty) {
PermissionStatus permissionStatus = await _getStoragePermission();
if (permissionStatus == PermissionStatus.granted) {
try {
String? externalStoragePath = await _getDownloadPath();
if (externalStoragePath != null) {
externalStoragePath += "/YourAppData.txt";
File file = File(externalStoragePath);
//Platform.lineTerminator is used to add new line to the text
file.writeAsString(dataController.text + Platform.lineTerminator,
mode: FileMode.writeOnlyAppend);
content.value = 'File written successfully!';
}
} catch (e) {
print('Error in writing file=$e');
content.value = 'Error in writing file=$e';
}
}
} else {
print('Enter some content first in the textbox');
content.value = 'Enter some content first in the textbox';
}
}

void readFile() async {
PermissionStatus permissionStatus = await _getStoragePermission();
if (permissionStatus == PermissionStatus.granted) {
try {
String? externalStoragePath = await _getDownloadPath();
if (externalStoragePath != null) {
externalStoragePath += "/YourAppData.txt";
File file = File(externalStoragePath);
content.value =
'File read successfully:${Platform.lineTerminator}${Platform.lineTerminator}${await file.readAsString()}';
}
} catch (e) {
print('Error in reading file=$e');
content.value = 'Error in reading file=$e';
}
}
}

Future<String?> _getDownloadPath() async {
Directory? directory;
try {
if (Platform.isIOS) {
directory = await getApplicationDocumentsDirectory();
} else {
directory = Directory('/storage/emulated/0/Download');
// Put file in global download folder, if for an unknown reason it didn't exist, we fallback
// ignore: avoid_slow_async_io
if (!await directory.exists()) {
directory = await getExternalStorageDirectory();
}
}
} catch (e) {
print("Cannot get download folder path with error=$e");
}
return directory?.path;
}
}

Android Output

The text file will be generated in the Downloads folder of the Android Device.

Android Output

iOS Output

The same text file will be created in the

File App → Browse → On My iPhone → App Name Folder → YourAppData.txt

iOS Output

Thanks for following the blog till the end, hope this helped you learn something new today.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Dinesh Falwadiya
Dinesh Falwadiya

Written by Dinesh Falwadiya

Mobile app developer who loves to share his knowledge and get improve with others.

No responses yet

Write a response