Skip to content
okio

Never Used Okio? A KMP Library That Unifies Cross-Platform File Operations

When developing cross-platform applications, file operations are a common but tricky problem. Different platforms (like Android, iOS, macOS, Windows, and Linux) have significantly different file system behaviors. Writing platform-specific code for file handling often leads to duplicated code and platform-specific bugs. In this article, we explore how to use the Okio library to unify file operations across platforms.

Challenges from Platform Differences

File I/O varies across platforms in several ways:

  1. Path representations

    • Windows uses backslashes (\)
    • Unix/Linux/macOS use slashes (/)
    • iOS requires sandbox restrictions to be considered
  2. Permission handling

    • Android needs runtime permission requests
    • iOS has strict sandboxing
    • Desktop platforms vary by user privilege
  3. File I/O APIs

    • Each platform has its own APIs
    • Different error-handling mechanisms
    • Varying performance characteristics

As a result, KMP code often looks like this:

kotlin
fun readFile(path: String): String {
    return when (Platform.current) {
        Platform.ANDROID -> {
            // Android-specific implementation
        }
        Platform.IOS -> {
            // iOS-specific implementation
        }
        Platform.DESKTOP -> {
            // Desktop-specific implementation
        }
    }
}

The Advantages of Okio

Okio is a modern I/O library offering a unified API to simplify cross-platform file operations.

1. Unified I/O Model

Okio introduces Source and Sink abstractions for reading and writing:

kotlin
fun copyFile(source: Path, target: Path) {
    fileSystem.source(source).use { input ->
        fileSystem.sink(target).use { output ->
            input.buffer().readAll(output)
        }
    }
}

2. Path Operations

Okio provides a unified Path API that abstracts away platform path differences:

kotlin
fun createPath(base: Path, child: String): Path {
    return base / child  // Correct separator for the platform
}

fun resolvePath() {
    val path = "documents/reports".toPath()
    println(path.normalized()) // Automatically normalized
}

3. Smart Buffering

Okio includes efficient buffering with minimal manual management:

kotlin
fun processLargeFile(path: Path) {
    fileSystem.source(path).buffer().use { source ->
        // Process data
    }
}

4. Consistent Error Handling

Okio provides a uniform error handling strategy:

kotlin
import okio.FileNotFoundException
import okio.IOException
import okio.Path
import okio.use

fun safeFileOperation(path: Path) {
    try {
        fileSystem.source(path).use { source ->
            // Perform file operation
        }
    } catch (e: FileNotFoundException) {
        // Handle file not found
    } catch (e: IOException) {
        // Handle I/O error
    }
}

Real-World Example from CrossPaste

This example is from the open-source crosspaste-desktop project. The UserDataPathProvider manages paths for application data, supporting operations like migration and temp cleanup. It's fully cross-platform and works on Android, iOS, macOS, Windows, and Linux.

UserDataPathProvider.kt

kotlin
// full source code retained in repository

It demonstrates:

  • Platform-agnostic file path resolution
  • Dynamic folder creation
  • Migration logic from old paths to new locations
  • Error handling and configuration updates
  • Clean-up of temporary files

Conclusion

Okio greatly simplifies file I/O in Kotlin Multiplatform projects by:

  • Unifying file handling APIs across platforms
  • Abstracting path differences and buffering
  • Providing consistent error handling
  • Making file code cleaner, safer, and reusable

In CrossPaste, using Okio allowed us to manage files across all target platforms with a single codebase, ensuring stability and consistency. For any KMP developer dealing with file operations, Okio is a must-have tool.