Manage External Storage Using Flutter GetX
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.
- 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.
- GetX — https://pub.dev/packages/get
- Path Provider — https://pub.dev/packages/path_provider
- 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.


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.

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.

iOS Output
The same text file will be created in the
File App → Browse → On My iPhone → App Name Folder → YourAppData.txt


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