Skip to content

Instantly share code, notes, and snippets.

@ponnamkarthik
Last active February 4, 2025 09:35
Show Gist options
  • Save ponnamkarthik/8ea95d514e882552f22e4c3cb876599e to your computer and use it in GitHub Desktop.
Save ponnamkarthik/8ea95d514e882552f22e4c3cb876599e to your computer and use it in GitHub Desktop.
Flutter DirectoryListing
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<application
android:requestLegacyExternalStorage="true"
android:label="directorylisting"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:permission_handler/permission_handler.dart';
void main() {
runApp(const MyApp());
}
class FileSystemItem {
final String path;
final bool isFolder;
final String title;
final List<FileSystemItem>? children;
FileSystemItem({
required this.path,
required this.isFolder,
required this.title,
this.children,
});
}
Future<List<FileSystemItem>> getDirectoryStructure(String dirPath) async {
final dir = Directory(dirPath);
if (!await dir.exists()) {
return [];
}
return _getStructure(dir);
}
Future<List<FileSystemItem>> _getStructure(Directory dir) async {
final List<FileSystemItem> structure = [];
final List<FileSystemEntity> entities = dir.listSync();
for (var entity in entities) {
if (entity.path.contains("Android")) {
continue;
}
final isFolder = entity is Directory;
final path = entity.path;
final title = path.split('/').last;
structure.add(FileSystemItem(
path: path,
isFolder: isFolder,
title: title,
children: isFolder ? await _getStructure(Directory(path)) : null,
));
}
return structure;
}
class FileExplorerScreen extends StatelessWidget {
final String path;
FileExplorerScreen({required this.path});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(path.split('/').last)),
body: FutureBuilder<List<FileSystemItem>>(
future: getDirectoryStructure(path),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text("No files found"));
}
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
final item = snapshot.data![index];
return ListTile(
leading: Icon(
item.isFolder ? Icons.folder : Icons.insert_drive_file),
title: Text(item.title),
onTap: item.isFolder
? () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
FileExplorerScreen(path: item.path),
),
);
}
: () {
SnackBar snackBar = SnackBar(
content: Text("File: ${item.title}"),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
},
);
},
);
},
),
);
}
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool isStoragePermissionGranted = false;
Future<void> requestStoragePermission() async {
if (Platform.isAndroid) {
if (await Permission.storage.request().isGranted) {
setState(() {
isStoragePermissionGranted = true;
});
} else if (await Permission.manageExternalStorage.request().isGranted) {
setState(() {
isStoragePermissionGranted = true;
});
} else {
setState(() {
isStoragePermissionGranted = false;
});
}
} else {
setState(() {
isStoragePermissionGranted = true;
});
}
}
@override
void initState() {
super.initState();
requestStoragePermission();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: isStoragePermissionGranted
? FileExplorerScreen(path: "/storage/emulated/0/")
: Scaffold(
body: Center(
child: ElevatedButton(
onPressed: requestStoragePermission,
child: Text("Grant Storage Permission"),
),
),
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment