Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .run/publishComposeToCentral.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="publishComposeToCentral" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="./gradlew :storage-compose:publishToMavenCentral" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/zsh" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="publishToCentral" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="./gradlew :storage:publishAllPublicationsToMavenCentral" />
<configuration default="false" name="publishStorageToCentral" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="./gradlew :storage:publishToMavenCentral" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
Expand Down
2 changes: 1 addition & 1 deletion .run/publishToLocal.run.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="publishToLocal" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="./gradlew :storage:publishToMavenLocal" />
<option name="SCRIPT_TEXT" value="./gradlew :storage:publishToMavenLocal &amp;&amp; ./gradlew :storage-compose:publishToMavenLocal" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright © 2020-2023 Anggrayudi Hardiannico A.
Copyright © 2020-2025 Anggrayudi Hardiannico A.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
85 changes: 82 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Table of Contents
* [Overview](#overview)
+ [Java Compatibility](#java-compatibility)
+ [Jetpack Compose](#jetpack-compose)
* [Terminology](#terminology)
* [Check Accessible Paths](#check-accessible-paths)
* [Read Files](#read-files)
Expand All @@ -16,6 +17,7 @@
+ [`DocumentFile`](#documentfile)
+ [`MediaFile`](#mediafile)
* [Request Storage Access, Pick Folder & Files, Request Create File, etc.](#request-storage-access-pick-folder--files-request-create-file-etc)
* [Activity Result Contracts](#activity-result-contracts)
* [Move & Copy: Files & Folders](#move--copy-files--folders)
* [Search: Files & Folders](#search-files--folders)
* [Compress & Unzip: Files & Folders](#compress--unzip-files--folders)
Expand All @@ -40,11 +42,17 @@ Adding Simple Storage into your project is pretty simple:

```groovy
implementation "com.anggrayudi:storage:X.Y.Z"

// For Jetpack Compose
implementation "com.anggrayudi:storage-compose:X.Y.Z"
```

Where `X.Y.Z` is the library version: ![Maven Central](https://img.shields.io/maven-central/v/com.anggrayudi/storage.svg)

All versions can be found [here](https://oss.sonatype.org/#nexus-search;gav~com.anggrayudi~storage~~~~kw,versionexpand).
All versions can be found here:
- [Simple Storage Core](https://central.sonatype.com/artifact/com.anggrayudi/storage/versions)
- [Simple Storage Jetpack Compose](https://central.sonatype.com/artifact/com.anggrayudi/storage-compose/versions)

To use `SNAPSHOT` version, you need to add this URL to the root Gradle:

```groovy
Expand All @@ -53,7 +61,7 @@ allprojects {
google()
mavenCentral()
// add this line
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
maven { url "https://central.sonatype.com/repository/maven-snapshots/" }
}
}
```
Expand All @@ -67,6 +75,24 @@ They are powered by Kotlin Coroutines & Flow, which are easy to use.
You can still use these Java features in your project, but you will need [v1.5.6](https://github.com/anggrayudi/SimpleStorage/releases/tag/1.5.6) which is the latest version that
supports Java.

### Jetpack Compose

`SimpleStorageHelper` is a traditional class that helps you to request storage access, pick folders/files, and create files.
This class has interactive dialogs, so you don't have to handle storage access & permissions manually.
In Jetpack Compose, you can achieve the same thing with [`SimpleStorageCompose.kt`](storage-compose/src/main/java/com/anggrayudi/storage/compose/SimpleStorageCompose.kt).
This class contains composable functions:
- `rememberLauncherForStoragePermission()`
- `rememberLauncherForStorageAccess()`
- `rememberLauncherForFolderPicker()`
- `rememberLauncherForFilePicker()`

If you think these composable functions has too many UI manipulations and don't suit your needs, then
you can copy the logic from [`SimpleStorageCompose.kt`](storage-compose/src/main/java/com/anggrayudi/storage/compose/SimpleStorageCompose.kt)
and create your own composable functions. Because you might need custom dialogs, custom strings, etc.

For file creation, you can use `rememberLauncherForActivityResult(FileCreationContract(context))`.
Check all available contracts in the [`SimpleStorageResultContracts.kt`](storage/src/main/java/com/anggrayudi/storage/contract/SimpleStorageResultContracts.kt)

## Terminology

![Alt text](art/terminology.png?raw=true "Simple Storage Terms")
Expand Down Expand Up @@ -241,6 +267,59 @@ Simple, right?
This helper class contains default styles for managing storage access.
If you want to use custom dialogs for `SimpleStorageHelper`, just copy the logic from this class.

## Activity Result Contracts

If you want to use `ActivityResultContract` instead of `SimpleStorageHelper`, you can use contracts
provided in [`SimpleStorageResultContracts.kt`](storage/src/main/java/com/anggrayudi/storage/contract/SimpleStorageResultContracts.kt):
- `RequestStorageAccessContract`
- `StoragePermissionContract`
- `FileCreationContract`
- `OpenFilePickerContract`
- `OpenFolderPickerContract`

Then use them like this:
```kotlin
class MainActivity : AppCompatActivity() {
lateinit var requestStorageAccessLauncher: ActivityResultLauncher<RequestStorageAccessContract.Options>

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setContentView(R.layout.activity_main)
val contract = RequestStorageAccessContract(
expectedStorageId = StorageId.PRIMARY,
expectedBasePath = "Documents"
)
requestStorageAccessLauncher = registerForActivityResult(contract) { result ->
when (result) {
is RequestStorageAccessResult.RootPathNotSelected -> {
// Ask user to select the root path.
}
is RequestStorageAccessResult.ExpectedStorageNotSelected -> {
// Ask the user to select the expected storage.
// This can happen if you set expectedBasePath or expectedStorageType to the contract.
}
is RequestStorageAccessResult.RootPathPermissionGranted -> {
// Access granted to the root path
}
}
}

btnRequestStorageAccess.setOnClickListener {
val options = RequestStorageAccessContract.Options(
initialPath = FileFullPath(
baseContext,
storageId = StorageId.PRIMARY,
basePath = "Documents"
)
)
requestStorageAccessLauncher.launch(options)
}
}
}
```

This way, you don't need to maintain the instance of `SimpleStorageHelper`, dealing with `onActivityResult()`, `onSaveInstanceState()`, etc.

## Move & Copy: Files & Folders

Simple Storage helps you in copying/moving files & folders via:
Expand Down Expand Up @@ -402,7 +481,7 @@ Check how these repositories use it:

## License

Copyright © 2020-2024 Anggrayudi Hardiannico A.
Copyright © 2020-2025 Anggrayudi Hardiannico A.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
9 changes: 4 additions & 5 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,7 @@ kotlin.code.style=official
org.jetbrains.dokka.experimental.gradle.pluginMode=V2EnabledWithHelpers
# For publishing:
GROUP=com.anggrayudi
POM_ARTIFACT_ID=storage
VERSION_NAME=2.1.0-SNAPSHOT
RELEASE_SIGNING_ENABLED=false
SONATYPE_AUTOMATIC_RELEASE=true
SONATYPE_HOST=DEFAULT
VERSION_NAME=2.2.0-SNAPSHOT
POM_NAME=storage
POM_DESCRIPTION=Simplify Android Storage Access Framework for file management across API levels.
POM_INCEPTION_YEAR=2020
Expand All @@ -40,3 +36,6 @@ POM_SCM_DEV_CONNECTION=scm:git:ssh://github.com:anggrayudi/SimpleStorage.git
POM_DEVELOPER_ID=anggrayudi
POM_DEVELOPER_NAME=Anggrayudi H
POM_DEVELOPER_URL=https://github.com/anggrayudi/
mavenCentralAutomaticPublishing=true
mavenCentralPublishing=true
signAllPublications=true
18 changes: 10 additions & 8 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[versions]
kotlin = "2.1.20"
agp = "8.11.1"
kotlin = "2.2.0"
activityCompose = "1.10.1"
coroutines = "1.10.2"
mockito = "3.10.0"
Expand All @@ -10,11 +11,11 @@ androidx-core = { group = "androidx.core", name = "core-ktx", version = "1.16.0"
junit = { group = "junit", name = "junit", version = "4.13.2" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version = "1.2.1" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version = "3.6.1" }
androidx-lifecycle-runtime = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version = "2.9.1" }
androidx-lifecycle-runtime = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version = "2.9.2" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version = "1.7.1" }
androidx-activity = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityCompose" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version = "2025.06.00" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version = "2025.07.00" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
Expand All @@ -23,7 +24,7 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-multidex = { group = "androidx.multidex", name = "multidex", version = "2.0.1" }
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version = "2.9.0" }
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version = "2.9.2" }
androidx-datastore = { group = "androidx.datastore", name = "datastore-preferences-android", version = "1.1.7" }
androidx-preference = { group = "androidx.preference", name = "preference-ktx", version = "1.2.1" }
androidx-document-file = { group = "androidx.documentfile", name = "documentfile", version = "1.1.0" }
Expand All @@ -36,9 +37,9 @@ timber = { group = "com.jakewharton.timber", name = "timber", version = "5.0.1"
coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" }
coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" }

mockk = { group = "io.mockk", name = "mockk", version = "1.13.17" }
mockk = { group = "io.mockk", name = "mockk", version = "1.14.5" }
kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.ref = "kotlin" }
robolectric = { group = "org.robolectric", name = "robolectric", version = "4.10.3" }
robolectric = { group = "org.robolectric", name = "robolectric", version = "4.15.1" }
mockito-core = { group = "org.mockito", name = "mockito-core", version.ref = "mockito" }
mockito-inline = { group = "org.mockito", name = "mockito-inline", version.ref = "mockito" }
mockito-all = { group = "org.mockito", name = "mockito-all", version = "1.10.19" }
Expand All @@ -47,9 +48,10 @@ powermock-junit4 = { group = "org.powermock", name = "powermock-module-junit4",
powermock-api-mockito = { group = "org.powermock", name = "powermock-api-mockito2", version.ref = "powermock" }

[plugins]
android-application = { id = "com.android.application", version = "8.9.3" }
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version = "2.1.20-1.0.32" }
maven-publish = { id = "com.vanniktech.maven.publish", version = "0.22.0" }
maven-publish = { id = "com.vanniktech.maven.publish", version = "0.34.0" }
dokka = { id = "org.jetbrains.dokka", version = "2.0.0" }
4 changes: 2 additions & 2 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Tue Jun 10 19:14:26 WIB 2025
#Sat Jul 26 20:40:46 WIB 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
16 changes: 11 additions & 5 deletions sample/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
Expand All @@ -7,7 +9,7 @@ plugins {

android {
namespace = "com.anggrayudi.storage.sample"
compileSdk = 35
compileSdk = 36

signingConfigs {
val debugKeystore =
Expand All @@ -31,7 +33,7 @@ android {
defaultConfig {
applicationId = "com.anggrayudi.storage.sample"
minSdk = 21
targetSdk = 35
targetSdk = 36
versionCode = 1
versionName = rootProject.extra["VERSION_NAME"] as String
multiDexEnabled = true
Expand All @@ -56,7 +58,11 @@ android {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions { jvmTarget = "11" }
kotlin {
compilerOptions {
jvmTarget = JvmTarget.JVM_11
}
}

flavorDimensions += "libSource"
productFlavors {
Expand All @@ -78,8 +84,8 @@ android {
}

dependencies {
implementation(project(":storage"))
// implementation("com.anggrayudi:storage:${rootProject.extra["VERSION_NAME"]}")
implementation(project(":storage-compose"))
// implementation("com.anggrayudi:storage-compose:${rootProject.extra["VERSION_NAME"]}")

implementation(libs.androidx.core)
implementation(libs.androidx.lifecycle.runtime)
Expand Down
5 changes: 5 additions & 0 deletions sample/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@
</intent-filter>
</activity>

<activity
android:name=".compose.StorageComposeActivity"
android:label="@string/compose_screen"
android:theme="@style/ComposeTheme" />

<activity
android:name=".activity.SampleFragmentActivity"
android:screenOrientation="portrait" />
Expand Down
Loading