Note: I’m using a translation tool, so there may be some inappropriate expressions.
Overview
The Android file system is strict and complex. This plugin was created to provide practical file operations. You don’t need to set special permissions or configurations.
This crate aims to provide practicality through detailed and clear documentation and a wide range of options, rather than focusing on clean functions that require minimal attention. If an error or unexpected behavior occurs, please first check the documentation for the function and functions that provided arguments. If the issue persists, it would be helpful if you could let us know on Github. Function requests are also welcome.
Setup
Register this plugin with your Tauri project:
src-tauri/src/lib.rs
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_android_fs::init()) .run(tauri::generate_context!())
.expect("error while running tauri application");
}
The current dialog in this plugin has an issue. To avoid this, please follow these two steps:
src-tauri/Cargo.toml
[dependencies]
tauri-plugin-android-fs = { features = ["avoid-issue1"], .. }
src-tauri/capabilities/default.json
{
..
"permissions": [
"android-fs:default",
..
]
}
Usage
Currently, this plugin only provides a Rust-side API, not a NPM library.
Then, there are three main ways to manipulate files:
1. Dialog
Opens the file/folder picker to read and write user-selected entries.
use tauri_plugin_android_fs::{AndroidFsExt, FileAccessMode, ImageFormat, Size};
fn file_picker_example(app: tauri::AppHandle) -> tauri_plugin_android_fs::Result<()> {
let api = app.android_fs();
let selected_files = api.show_open_file_dialog(
None, &["*/*"], true, )?;
if selected_files.is_empty() {
}
else {
for uri in selected_files {
let file_type = api.get_mime_type(&uri)?.unwrap(); let file_name = api.get_name(&uri)?;
let file_thumbnail = api.get_thumbnail(
&uri,
Size { width: 200, height: 200},
ImageFormat::Jpeg
)?;
{
let file: std::fs::File = api.open_file(&uri, FileAccessMode::Read)?;
}
{
let file: std::fs::File = api.open_file(&uri, FileAccessMode::WriteTruncate)?;
}
let file_path: tauri_plugin_fs::FilePath = uri.into();
}
}
Ok(())
}
use tauri_plugin_android_fs::{AndroidFsExt, Entry};
fn dir_picker_example(app: tauri::AppHandle) -> tauri_plugin_android_fs::Result<()> {
let api = app.android_fs();
let selected_folder = api.show_manage_dir_dialog(
None, )?;
if let Some(dir_uri) = selected_folder {
for entry in api.read_dir(&dir_uri)? {
match entry {
Entry::File { name, uri, last_modified, len, mime_type, .. } => {
},
Entry::Dir { name, uri, last_modified, .. } => {
},
}
}
}
else {
}
Ok(())
}
use tauri_plugin_android_fs::{AndroidFsExt, FileUri, InitialLocation, PersistableAccessMode, PrivateDir};
fn save_file_with_dialog(
app: tauri::AppHandle,
file_name: &str,
mime_type: &str,
contents: &[u8],
) -> tauri_plugin_android_fs::Result<bool> {
let api = app.android_fs();
let file_uri = api.show_save_file_dialog(
None, file_name, Some(mime_type), )?;
let Some(file_uri) = file_uri else {
return Ok(false)
};
if let Err(e) = api.write(&file_uri, contents) {
let _ = api.remove_file(&file_uri);
return Err(e)
}
Ok(true)
}
fn save_file_with_dir_dialog(
app: tauri::AppHandle,
relative_path: &str,
mime_type: &str,
contents: &[u8],
) -> tauri_plugin_android_fs::Result<bool> {
const DEST_DIR_URI_DATA_RELATIVE_PATH: &str = "01JQMFWVH65YNCWM31V3DZG6GR";
let api = app.android_fs();
let dest_dir_uri = api
.private_storage()
.read_to_string(PrivateDir::Data, DEST_DIR_URI_DATA_RELATIVE_PATH)
.and_then(|u| FileUri::from_str(&u))
.ok();
let dest_dir_uri = match dest_dir_uri {
Some(dest_dir_uri) => {
if api.check_persisted_uri_permission(&dest_dir_uri, PersistableAccessMode::ReadAndWrite)? {
Some(dest_dir_uri)
}
else {
None
}
},
None => None
};
let dest_dir_uri = match dest_dir_uri {
Some(dest_dir_uri) => dest_dir_uri,
None => {
let initial_location = api.resolve_initial_location(
InitialLocation::TopPublicDir,
false
)?;
let uri = api.show_manage_dir_dialog(
Some(&initial_location)
)?;
let Some(uri) = uri else {
return Ok(false)
};
api.private_storage().write(
PrivateDir::Data,
DEST_DIR_URI_DATA_RELATIVE_PATH,
uri.to_string()?.as_bytes()
)?;
api.take_persistable_uri_permission(&uri)?;
uri
},
};
let new_file_uri = api.create_file(
&dest_dir_uri,
relative_path,
Some(mime_type)
)?;
if let Err(e) = api.write(&new_file_uri, contents) {
let _ = api.remove_file(&new_file_uri);
return Err(e)
}
Ok(true)
}
2. Public Storage
File storage that is available to other applications and users.
Currently, this is for Android 10 (API level 29) or higher.
use tauri_plugin_android_fs::{AndroidFsExt, PublicGeneralPurposeDir, PublicImageDir};
fn example(app: tauri::AppHandle) -> tauri_plugin_android_fs::Result<()> {
let api = app.android_fs();
let storage = api.public_storage();
let contents = &[];
let uri = storage.create_file_in_public_app_dir(
PublicImageDir::Pictures, "my-image.png", Some("image/png") )?;
if let Err(e) = api.write(&uri, contents) {
let _ = api.remove_file(&uri);
return Err(e)
}
let uri = storage.create_file_in_public_app_dir(
PublicGeneralPurposeDir::Documents, "2025-3-2/data.txt", Some("text/plain") )?;
if let Err(e) = api.write(&uri, contents) {
let _ = api.remove_file(&uri);
return Err(e)
}
Ok(())
}
3. Private Storage
File storage intended for the app’s use only.
use tauri_plugin_android_fs::{AndroidFsExt, PrivateDir};
fn example(app: tauri::AppHandle) -> tauri_plugin_android_fs::Result<()> {
let storage = app.android_fs().private_storage();
let contents = &[];
let _cache_dir_path: std::path::PathBuf = storage.resolve_path(PrivateDir::Cache)?;
let _data_dir_path: std::path::PathBuf = storage.resolve_path(PrivateDir::Data)?;
storage.write(
PrivateDir::Data, "config/data1", contents
)?;
let contents = storage.read(
PrivateDir::Data, "config/data1" )?;
Ok(())
}
Link
License
MIT OR Apache-2.0