
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:
Path representations
- Windows uses backslashes (
\
) - Unix/Linux/macOS use slashes (
/
) - iOS requires sandbox restrictions to be considered
- Windows uses backslashes (
Permission handling
- Android needs runtime permission requests
- iOS has strict sandboxing
- Desktop platforms vary by user privilege
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:
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:
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:
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:
fun processLargeFile(path: Path) {
fileSystem.source(path).buffer().use { source ->
// Process data
}
}
4. Consistent Error Handling
Okio provides a uniform error handling strategy:
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.
// 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.