DEV Community

Philipp Rich
Philipp Rich

Posted on • Originally published at philrich.dev

2 1

Tauri File Management on Android

How to implement file management in a Tauri-based Android app.

Introduction

Recently, I decided to revive an old project of mine—a personal savings journal. Initially built with Next.js and MongoDB Atlas, this setup required database maintenance and hosting, which felt excessive for such a small application. Additionally, sharing it with the community was challenging, leaving me as the sole user.

My new idea is to develop an offline, mobile-first app that stores all user data locally. This approach eliminates the need for infrastructure maintenance and allows for easy distribution as an Android app (an iOS developer account is too costly for a non-profitable, open-source app).

My research led me to the Tauri framework, a lightweight alternative to Electron. To finalize my tech stack decision, I started by implementing the riskiest and least familiar feature: saving and reading data on an Android device. During this exploration, I wrote an article and created a playground app for experimenting with file management in Tauri.

Tauri Overview

Tauri is a cross-platform framework similar to Electron. Its main advantage is the use of native web views, which reduces the package size significantly—around 30MB for a simple React app. However, this might present challenges for more complex applications. As a JavaScript developer, the primary drawback for me is Tauri's Rust backend, making it tricky to write backend logic. Fortunately, my app only requires saving and reading a local database, so I decided to give it a try.

Setting Up a Project

Starting a new project was straightforward. Refer to the official documentation or check out my playground repo. The most challenging part was setting up PATH variables and installing additional Android SDK packages after installing android-studio:

  1. Install ndk within android-studio: Settings > Languages & Frameworks > SDK Tools.
  2. Set the NDK_HOME and ANDROID_HOME PATH variables (ANDROID_HOME is visible in Settings > Languages & Frameworks):
export ANDROID_HOME=YOUR_PATH/Android/sdk
export PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$PATH

export NDK_HOME=$ANDROID_HOME/ndk/29.0.13113456
export PATH=$PATH:$NDK_HOME
Enter fullscreen mode Exit fullscreen mode
  1. Initialize the Android project:
pnpm tauri android init
Enter fullscreen mode Exit fullscreen mode

After completing these steps, pnpm tauri android dev worked seamlessly, launching the Android emulator or using a connected phone.

Tauri File Management Plugins

Tauri offers several official plugins for file system manipulation:

The file-system plugin is designed for silently reading and writing files, while the dialog plugin explicitly asks the user to pick a file using the system file explorer.

Tauri Capabilities

Capabilities | Tauri

Configuring permissions for plugins was the most frustrating part. Tauri limits permissions by default, requiring manual specification of locations and permissions for the file-system plugin. Restarting the dev server was necessary after changing capabilities, and error messages were often unclear, making it difficult to determine whether the issue was with capabilities configuration or Android permissions.

Silently Managing Files

In my playground project, I created <ReadFileCard>, <ReadFilePath>, and <WriteFileCard> components to experiment with file management. Reading and writing files is straightforward:

import { readTextFile } from "@tauri-apps/plugin-fs";

async function readTxtFile(fileName: string) {
  const file = await readTextFile(fileName);
  // or using BaseDirectory (look at docs)
  return file;
}
Enter fullscreen mode Exit fullscreen mode

The baseDirectory mapping to the Android file system is not obvious. Some paths map as follows:

  • BaseDirectory.Home points to the external shared directory: /storage/emulated/0
  • BaseDirectory.LocalData, BaseDirectory.AppData, and others point to internal private storage: /data/user/0/com.myapp.app
  • BaseDirectory.Document points to internal shared storage: /storage/emulated/0/Android/data/com.myapp.app/documents

As a user, I could only access /storage/emulated/0/* with the built-in file manager, while /storage/emulated/0/Android/data/ showed no paths. However, it was accessible via adb shell.

Android Limitations

You cannot create files or folders directly in ExternalStorage. Instead, use directories like Documents, Download, Pictures, or Videos. For example, use /storage/emulated/0/Documents/MyFolder/file.txt instead of /storage/emulated/0/MyFolder/file.txt.

On Android 10 and above, you can create files and folders in these directories without permissions, but you cannot read files created by other apps. In this case, use a dialog to read the files.

For Android 9 and earlier, the app must explicitly ask for user permission to access files in Documents, Downloads, etc. To access files in /storage/emulated/0/ on any Android version, the app must explicitly ask for user permission.

Currently, Tauri does not provide a plugin for requesting user permission to access external storage. Therefore, I changed the minimum supported Android version to 10:

// src-tauri/tauri.conf.json
"bundle": {
+   "android": {
+      "minSdkVersion": 29
+   }
}
Enter fullscreen mode Exit fullscreen mode

Capabilities

To work with /storage/emulated/0/Documents/*, configure the capabilities as follows:

// src-tauri/capabilities/default.json
{
  "identifier": "fs:allow-write",
  "allow": [
    {
      "path": "$HOME/Documents/*"
    }
  ]
},
{
  "identifier": "fs:allow-create",
  "allow": [
    {
      "path": "$HOME/Documents/*"
    }
  ]
},
{
  "identifier": "fs:allow-read-text-file",
  "allow": [
    {
      "path": "$HOME/Documents/*"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Explicitly Managing Files

In my playground project, I created <ReadExtFileCard> and <WriteExtFileCard> components to experiment with file management using the dialog plugin. This plugin does not require additional permissions and allows access to any file the user selects in the file explorer. For example, to read a file using both dialog and file-system plugins:

import { open } from "@tauri-apps/plugin-dialog";
import { readTextFile } from "@tauri-apps/plugin-fs";

...

onClick={async (e) => {
  try {
    const directory = await loadExtFile(); // `dialog` returns selected path
    const content = await readTxtFile(directory); // Now file system plugin has permissions to selected path
    setState(`File: ${directory} content: ${content}`);
  } catch (error) {
    setState(error as string);
  }
}}

...

async function loadExtFile() {
  const path = await open({
    multiple: false,
    directory: false,
  });
  return path;
}

async function readTxtFile(fileName: string) {
  const fileContent = await readTextFile(fileName);
  return fileContent;
}
Enter fullscreen mode Exit fullscreen mode

Check out playground app: https://github.com/skorphil/tauri-fs-android-starter

Happy coding!

SurveyJS custom survey software

Build Your Own Forms without Manual Coding

SurveyJS UI libraries let you build a JSON-based form management system that integrates with any backend, giving you full control over your data with no user limits. Includes support for custom question types, skip logic, an integrated CSS editor, PDF export, real-time analytics, and more.

Learn more

Top comments (0)

Billboard image

📊 A side-by-side product comparison between Sentry and Crashlytics

A free guide pointing out the differences between Sentry and Crashlytics, that’s it. See which is best for your mobile crash reporting needs.

See Comparison

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay