Creating a Simple Flutter File Server Using Shelf
In this post, we will walk you through creating a simple Flutter app that serves files over a local web server using the Shelf package. The goal is to let users select files from their device, which the app will serve via an HTTP server. This can be particularly useful for creating a local file sharing service for testing or sharing files within a local network.
What You Will Need
- Flutter SDK: To build the app.
- Dart packages:
file_picker
: To select files from the user's device.shelf
: A simple web server library for Dart.shelf_router
: For easy routing within the server.shelf_io
: To serve HTTP requests.
Step-by-Step Guide
Let’s break down the app into two main parts:
- The Flutter app — Where users can select files.
- The Web Server — A local HTTP server that serves those files.
Step 1: Set Up Your Flutter Project
Create a new Flutter project:
flutter create file_server_app
cd file_server_app
Then add the necessary dependencies in pubspec.yaml
:
dependencies:
flutter:
sdk: flutter
file_picker: ^5.0.0
shelf: ^1.1.0
shelf_router: ^1.0.0
shelf_io: ^1.0.0
Step 2: The Flutter UI
The UI consists of a simple button to allow users to pick files and a display area to show the selected files. We will use FilePicker to select multiple files and store them in a list. main.dart
import 'package:flutter/material.dart';
import 'package:file_picker/file_picker.dart';
import 'dart:io';
import 'dart:convert';
import 'server.dart'; // The server.dart file that we created earlier.
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// List<File> selectedFiles = []; // List to store the selected files
@override
void initState() {
super.initState();
startServer(); // Start the web server when the app launches
// startServervi();
}
// Function to open the file picker and allow user to select files
Future<void> pickFiles() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(allowMultiple: true);
if (result != null) {
// Add the selected files to the list
setState(() {
selectedFiles = result.paths.map((path) => File(path!)).toList();
});
} else {
// User canceled file selection
print("No files selected");
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter File Server',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Flutter File Server'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: pickFiles, // Call the file picker on button click
child: Text('Select Files'),
),
SizedBox(height: 20),
Text('Selected Files:'),
...selectedFiles.map((file) => Text(file.path.split('/').last)), // List selected files
],
),
),
),
);
}
}
Step 3: Set Up the Server
Next, we create a server.dart file that will handle the file serving. This server listens for HTTP requests and serves files based on the selected file paths. server.dart
import 'dart:io';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:shelf_router/shelf_router.dart';
List<File> selectedFiles = []; // List to store selected files
void startServer() async {
var handler = const Pipeline()
.addMiddleware(logRequests()) // Optional: log requests
.addHandler(_requestHandler);
// Get the device's local IP address
var ip = await _getLocalIpAddress();
// Start the server on port 8080
var server = await shelf_io.serve(handler, ip, 8080);
print('Server running at http://$ip:8080');
}
Future<String> _getLocalIpAddress() async {
var interfaces = await NetworkInterface.list();
for (var interface in interfaces) {
if (interface.addresses.isNotEmpty && interface.name != 'lo0') {
return interface.addresses.first.address;
}
}
return 'localhost';
}
// Request handler to serve files (images, videos, or other files)
Response _requestHandler(Request request) {
final path = request.url.path; // Path is already a String
if (path == '' || path == '/') {
return Response.ok(_generateHtml(), headers: {'Content-Type': 'text/html'}); // Show the homepage
}
// Check if the requested path corresponds to a file in the selected files
for (var file in selectedFiles) {
if (file.uri.pathSegments.last == path) {
try {
return Response.ok(file.readAsBytesSync(), headers: {
'Content-Type': _getContentType(file.path), // Set proper content type for images, videos, etc.
});
} catch (e) {
return Response.notFound('File not found');
}
}
}
return Response.notFound('File not found');
}
// Generate an HTML page showing the files available for download or viewing
String _generateHtml() {
String filesList = selectedFiles.map((file) {
final fileName = file.uri.pathSegments.last;
final filePath = '/$fileName';
// Check file type and generate HTML accordingly
if (fileName.endsWith('.mp4') || fileName.endsWith('.avi') || fileName.endsWith('.mov')) {
// Video file handling
return '''
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">$fileName</h5>
<a href="$filePath" download class="btn btn-primary">Download</a><br>
<video width="100%" controls>
<source src="$filePath" type="${_getContentType(file.path)}">
Your browser does not support the video tag.
</video>
</div>
</div>
</div>
''';
} else if (fileName.endsWith('.pdf')) {
// PDF file handling
return '''
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">$fileName</h5>
<a href="$filePath" target="_blank" class="btn btn-primary">View</a><br>
<embed src="$filePath" width="100%" height="300" type="application/pdf">
</div>
</div>
</div>
''';
} else if (fileName.endsWith('.jpg') || fileName.endsWith('.jpeg') || fileName.endsWith('.png') || fileName.endsWith('.gif')) {
// Image file handling
return '''
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">$fileName</h5>
<a href="$filePath" download class="btn btn-primary">Download</a><br>
<img src="$filePath" class="img-fluid" alt="$fileName">
</div>
</div>
</div>
''';
} else {
// Generic file handling (e.g., documents, text, etc.)
return '''
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">$fileName</h5>
<a href="$filePath" download class="btn btn-primary">Download</a>
</div>
</div>
</div>
''';
}
}).join('');
return '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>File Server</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
<div class="container mt-4">
<h1 class="mb-4 text-center">Files Available</h1>
<div class="row">
$filesList
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
''';
}
// Helper function to determine file type (to serve images, videos, documents, etc.)
String _getContentType(String path) {
if (path.endsWith('.jpg') || path.endsWith('.jpeg') || path.endsWith('.webp')) {
return 'image/jpeg';
} else if (path.endsWith('.png')) {
return 'image/png';
} else if (path.endsWith('.gif')) {
return 'image/gif';
} else if (path.endsWith('.bmp')) {
return 'image/bmp';
} else if (path.endsWith('.mp4')) {
return 'video/mp4'; // Video files (mp4)
} else if (path.endsWith('.avi')) {
return 'video/x-msvideo'; // AVI video files
} else if (path.endsWith('.mov')) {
return 'video/quicktime'; // MOV video files
} else if (path.endsWith('.pdf')) {
return 'application/pdf'; // PDF files
} else if (path.endsWith('.txt')) {
return 'text/plain'; // Text files
} else if (path.endsWith('.docx')) {
return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; // DOCX files
} else {
return 'application/octet-stream'; // Default for unknown files
}
}
void startServervi() async {
// Create a simple handler that serves a basic HTML page
var router = Router()
..get('/', (Request request) {
return Response.ok('<h1>Welcome to my server!</h1>', headers: {
'Content-Type': 'text/html',
});
});
// Create a handler pipeline, optional logging
var handler = const Pipeline()
.addMiddleware(logRequests()) // Optional: log requests
.addHandler(router);
// Use your local IP (replace with your actual local IP)
var ip = '0.0.0.0'; // Listens on all interfaces (or you can use a specific local IP like '192.168.x.x')
var server = await shelf_io.serve(handler, ip, 3000);
print('Server running at http://$ip:3000');
}
How It Works
- UI Part: The user can pick multiple files using
FilePicker
. The selected files are stored in theselectedFiles
list. - Server Part: When the app starts, the
startServer()
function starts an HTTP server that listens on the local IP address. When a file is requested, it checks if the requested file exists in theselectedFiles
list and serves it. - HTML Response: The server serves a basic HTML page listing the available files, with options to view or download them.
Running the App
- Run the Flutter app on your device or emulator.
- After selecting files, you should see the available files listed.
- Open a browser and navigate to the IP address displayed in the console (e.g.,
http://192.168.x.x:8080
). - You can download or view files directly from the browser.
Conclusion
With this simple Flutter app, you can create a local file server to serve any files you choose. It can be a great way to quickly share files across devices in the same network. You can extend this server to include additional features, such as authentication, file metadata display, or even support for file uploads.