native and web - wurzelsand/flutter-memos GitHub Wiki
We want to create an app that saves text to a file both via a browser and natively (desktop or mobile). For this we use the two packages web and file-picker. The web
package is only available for the web platform. Therefore we need conditional imports.
5 files in one folder:
- main.dart
- multiplatform.dart
- save_text_stub.dart
- save_text_web.dart
- save_text_io.dart
main.dart:
import 'package:flutter/material.dart';
import 'multiplatform.dart';
void main() {
runApp(const MaterialApp(home: MainApp()));
}
class MainApp extends StatefulWidget {
const MainApp({super.key});
@override
State<MainApp> createState() => _MainAppState();
}
class _MainAppState extends State<MainApp> {
late TextEditingController _controller;
@override
void initState() {
super.initState();
_controller = TextEditingController();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.save),
onPressed: () {
saveText(_controller.text, fileName: 'tmp.txt');
}),
),
body: Center(
child: TextField(
controller: _controller,
),
),
);
}
}
multiplatform.dart:
export 'save_text_stub.dart'
if (dart.library.io) 'package:xxxxx/save_text_io.dart'
if (dart.library.js_interop) 'package:xxxxx/safe_text_web.dart';
save_text_stub.dart:
void saveText(String text, {required String fileName}) {
throw UnimplementedError('Neither native nor web');
}
save_text_web.dart:
import 'dart:convert';
import 'package:web/web.dart' as web;
void saveText(String text, {required String fileName}) {
final bytes = utf8.encode(text);
final web.HTMLAnchorElement anchor =
web.document.createElement('a') as web.HTMLAnchorElement
..href = "data:application/octet-stream;base64,${base64Encode(bytes)}"
..style.display = 'none'
..download = fileName;
web.document.body!.appendChild(anchor);
anchor.click();
web.document.body!.removeChild(anchor);
}
save_text_io.dart:
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:file_picker/file_picker.dart';
Future<void> saveText(String text, {required String fileName}) async {
// better: Theme.of(context).platform == TargetPlatform.android...
if (Platform.isAndroid || Platform.isIOS) {
return _saveTextMobile(text, fileName);
} else {
return _saveTextDesktop(text, fileName);
}
}
Future<void> _saveTextMobile(String text, String fileName) async {
final bytes = utf8.encode(text);
await FilePicker.platform
.saveFile(dialogTitle: 'Save Text', fileName: fileName, bytes: bytes);
}
Future<void> _saveTextDesktop(String text, String fileName) async {
final retrievedFileName = await FilePicker.platform
.saveFile(dialogTitle: 'Save Text', fileName: fileName);
if (retrievedFileName != null) {
try {
File file = File(retrievedFileName);
file.writeAsString(text);
} catch (e) {
log('File dialog cancelled');
}
}
}
Better on Desktop: file_selector
Future<void> _saveTextDesktop(String text, String fileName) async {
final fileSaveLocation = await getSaveLocation(suggestedName: fileName);
if (fileSaveLocation == null) {
return;
}
final bytes = utf8.encode(text);
const String mimeType = 'text/plain';
final textFile = XFile.fromData(bytes, mimeType: mimeType, name: fileName);
await textFile.saveTo(fileSaveLocation.path);
}
But you have to add this key to DebugProfile.entitlements:
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
Unfortunately, Googles official file_selector
-package doesn't support saving on Android and iOS. file-picker can do this. But I am using flutter_file_dialog until Google supports saving files on mobile platforms:
Future<void> _saveTextMobile(String text, String fileName) async {
final bytes = utf8.encode(text);
final params = SaveFileDialogParams(data: bytes, fileName: fileName);
await FlutterFileDialog.saveFile(params: params);
}