Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
21 changes: 21 additions & 0 deletions .github/jitpack-publish.init.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Mirrors the maven-publish injection JitPack performs, so CI can rehearse a release.
// singleVariant must be set as the plugin applies; the component exists only after evaluation.
allprojects { project ->
project.plugins.withId('com.android.library') {
project.android.publishing { singleVariant('release') }
project.apply plugin: 'maven-publish'

project.afterEvaluate {
project.publishing {
publications {
release(MavenPublication) {
from project.components.release
groupId = project.findProperty('group')
artifactId = 'android-sdk'
version = project.findProperty('version')
}
}
}
}
}
}
139 changes: 139 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
name: CI

on:
workflow_dispatch:
pull_request:
push:
branches: [main, short-io-sdk]

# Least privilege: nothing here needs more than reading the checkout.
permissions:
contents: read

# Cancel superseded PR runs; branch pushes finish so the badge never shows a cancellation.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
# AGP 8.10 requires 17. Keep in step with jitpack.yml.
JAVA_VERSION: "17"

jobs:
unit-test:
name: unit tests
runs-on: ubuntu-latest
# Otherwise a wedged daemon runs to the 360-minute default.
timeout-minutes: 20
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false

- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
distribution: temurin
java-version: ${{ env.JAVA_VERSION }}

- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1

# Robolectric caches android-all jars outside ~/.gradle, so they need their own key.
- name: Cache Robolectric runtimes
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: ~/.m2/repository/org/robolectric
key: robolectric-${{ runner.os }}-${{ hashFiles('**/gradle/libs.versions.toml', '**/src/test/resources/robolectric.properties') }}
restore-keys: robolectric-${{ runner.os }}-

- name: Run unit tests
run: ./gradlew --no-daemon testDebugUnitTest

# Lint ships with AGP and this module is clean, so any finding is a regression.
- name: Lint
run: ./gradlew --no-daemon lint

- name: Upload reports
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: unit-test-reports
path: |
ShortIOSDK/build/reports/tests/
ShortIOSDK/build/reports/lint-results-*.html
ShortIOSDK/build/test-results/
retention-days: 7

# Type-checks androidTest without an emulator.
instrumentation-compile:
name: compile instrumented tests
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false

- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
distribution: temurin
java-version: ${{ env.JAVA_VERSION }}

- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1

- name: Assemble instrumented test APK
run: ./gradlew --no-daemon assembleAndroidTest

# JitPack publishes with -xtest -xlint, so this is the only place a broken
# publish surfaces before tag time.
jitpack-publish:
name: jitpack publish rehearsal
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false

- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
distribution: temurin
java-version: ${{ env.JAVA_VERSION }}

- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1

- name: Publish to a local repository
run: |
./gradlew --no-daemon -I .github/jitpack-publish.init.gradle clean \
-Pgroup=com.github.Short-io \
-Pversion=ci-${{ github.sha }} \
-xtest -xlint \
publishToMavenLocal

- name: Verify the AAR and POM were produced
run: |
set -euo pipefail
DIR=~/.m2/repository/com/github/Short-io/android-sdk/ci-${{ github.sha }}
if [ ! -d "$DIR" ]; then
echo "::error::Nothing was published to $DIR"
find ~/.m2/repository/com/github -maxdepth 4 -type d || true
exit 1
fi
ls -l "$DIR"
test -n "$(find "$DIR" -name '*.aar' -print -quit)" \
|| { echo "::error::No AAR in the published artifacts."; exit 1; }
test -n "$(find "$DIR" -name '*.pom' -print -quit)" \
|| { echo "::error::No POM in the published artifacts."; exit 1; }

# The ruleset requires a check named `test`; the jobs above report under their
# own names. always() so a failed job still reports this check.
test:
name: test
needs: [unit-test, instrumentation-compile, jitpack-publish]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- run: |
[ "${{ needs.unit-test.result }}" = "success" ] \
&& [ "${{ needs.instrumentation-compile.result }}" = "success" ] \
&& [ "${{ needs.jitpack-publish.result }}" = "success" ]
8 changes: 2 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
.kotlin
.idea
99 changes: 84 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

# ShortIOSDK for Android

[![CI](https://github.com/Short-io/android-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/Short-io/android-sdk/actions/workflows/ci.yml)

This SDK allows you to create short links using the [Short.io](https://short.io/) API based on a public API key and custom parameters. It also supports Android deep linking integration.

## ✨ Features
Expand Down Expand Up @@ -52,7 +54,7 @@ It will be:
```kotlin
dependencies {
implementation("com.github.User:Repo:Tag") // Example
implementation("com.github.Short-io:android-sdk:v1.0.4") // Use this
implementation("com.github.Short-io:android-sdk:v1.0.9") // Use this
}
```
### 3. Sync the Project
Expand Down Expand Up @@ -80,51 +82,72 @@ import com.github.shortiosdk.ShortioSdk

### 🔗 SDK Usage

#### Initialize the SDK

To start using ShortioSdk, you need to initialize it early in your app lifecycle, preferably in your Activity's onCreate() method or in your custom Application class.

Example: Initialize in Activity

```kotlin
override fun onCreate() {
super.onCreate()
ShortioSdk.initialize(apiKey, domain)
}
```
* apiKey: Your API key string for SDK initialization.
* domain: The default domain to use for URL shortening.

### 💡 How It Works

The app demonstrates:

#### ✅ Generating Short Links

```kotlin
import com.github.shortiosdk.ShortioSdk
import com.github.shortiosdk.ShortIOParameters
import com.github.shortiosdk.ShortIOResult

try {
val params = ShortIOParameters(
domain = "your_domain", // Replace with your Short.io domain
originalURL = "your_originalURL" // Replace with your Short.io domain
)
} catch (e: Exception) {
Log.e("ShortIO", "Error: ${e.message}", e)
}
```

**Note**: Both `domain` and `originalURL` are the required parameters. You can also pass optional parameters such as `path`, `title`, `utmParameters`, etc.
**Note**: Only the `originalURL` is the required parameter as `domain` is passed in the initialize method of SDK. You can also pass optional parameters such as `path`, `title`, `utmParameters`, etc.

```kotlin
val apiKey = "your_public_apiKey" // Replace with your Short.io Public API Key

thread {
try {
when (val result = ShortioSdk.shortenUrl(apiKey, params)) {
when (val result = ShortioSdk.createShortLink(params)) {
is ShortIOResult.Success -> {
println("Shortened URL: ${result.data.shortURL}")
Log.d("ShortIOResult","Shortened URL: ${result.data.shortURL}")
}
is ShortIOResult.Error -> {
val error = result.data
println("Error ${error.statusCode}: ${error.message} (code: ${error.code})")
Log.d("ShortIOResult","Error ${error.statusCode}: ${error.message} (code: ${error.code})")
}
}
} catch (e: Exception) {
Log.e("ShortIO", "Error: ${e.message}", e)
}
}
```
**Note**: Deprecated: `createShortLink(params, apiKey)` is still supported for backward compatibility but is no longer recommended for use. Use `createShortLink(params)` instead.

## 📄 API Parameters

The `ShortIOParameters` struct is used to define the details of the short link you want to create. Below are the available parameters:


| Parameter | Type | Required | Description |
| ------------------- | ----------- | -------- | ------------------------------------------------------------ |
| `domain` | `String` | ✅ | Your Short.io domain (e.g., `example.short.gy`) |
| `originalURL` | `String` | ✅ | The original URL to be shortened |
| `domain` | `String?` | ❌ (Deprecated) | Your Short.io domain (e.g., `example.short.gy`). ⚠️ Deprecated. Optional — when null or blank, the domain passed to `initialize()` is used. |
| `originalURL` | `String` | ✅ | The original URL to be shortened |
| `cloaking` | `Boolean` | ❌ | If `true`, hides the destination URL from the user |
| `password` | `String` | ❌ | Password to protect the short link |
| `redirectType` | `Int` | ❌ | Type of redirect (e.g., 301, 302) |
Expand Down Expand Up @@ -241,7 +264,7 @@ keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -sto

3. Tap on **“Add link”** under the **Open by Default** section.

4. Add your URL and make sure to enable the checkbox for your link.
4. Add your URL if not added and make sure to enable the checkbox for your link.

### 🔗 Step 4: Open the App Using a Deep Link

Expand All @@ -253,30 +276,76 @@ keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -sto

### 🧭 Step 5: Handle Incoming URLs with onNewIntent() Method

To retrieve the original URL from Short.io links in your Android app, you can handle incoming intents in onNewIntent(), which allows your activity to process links that are opened while it is already running.

1. Open your main activity file (e.g., MainActivity.kt).

2. Override the `onNewIntent()` method to receive new intents when the activity is already running:

```kotlin
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
val result = ShortioSdk.handleIntent(intent)
Log.d("New Intent", "Host: ${result?.host}, Path: ${result?.path}")
lifecycleScope.launch {
val result = ShortioSdk.handleIntent(intent)
// Access the original URL
val originalUrl = result?.destinationUrl
Log.d("New Intent",
"Host: ${result?.host},
Path: ${result?.path},
Original URL: $originalUrl"
)
}
}
```

3. In the same activity, also handle the initial intent inside the `onCreate()` method:
3. In the same activity, To handle the initial intent inside the `onCreate()` method:

```kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
val result = ShortioSdk.handleIntent(intent)
Log.d("New Intent", "Host: ${result?.host}, Path: ${result?.path}")
// Access the original URL
val originalUrl = result?.destinationUrl
Log.d("New Intent",
"Host: ${result?.host},
Path: ${result?.path},
Original URL: $originalUrl"
)
}
}
```

### 🔐 Secure Short Link

If you want to encrypt the original URL before shortening it. For privacy or security reasons — the SDK provides a utility function called createSecure. This function encrypts the original URL using AES-GCM and returns a secured URL with a separate decryption key.

```kotlin
val originalURL = "your_original_URL"
val result = ShortioSdk.createSecure(originalURL)
Log.d("SecureURL", "RESULT: ${result}")
Log.d("securedOriginalURL", "URL: ${result.securedOriginalURL}")
Log.d("securedShortUrl", "URL: ${result.securedShortUrl}")
```

### 🔄 Conversion Tracking

Track conversions for your short links to measure campaign effectiveness. The SDK provides a simple method to record conversions.

```kotlin
CoroutineScope(Dispatchers.IO).launch {
// All three are optional. clid falls back to the one captured by handleIntent,
// domain to the one passed to initialize(). Returns false if tracking failed.
val res = ShortioSdk.trackConversion(
clid = "your_clid",
domain = "example.short.gy",
conversionId = "purchase" // e.g. 'signup', 'purchase', 'download'
)
Log.d("Conversion Tracking", "tracked: $res")
}
```

### ✅ Final Checklist
### ✅ Final Checklist for Deep Linking

* App is signed with the correct keystore.

Expand Down
10 changes: 9 additions & 1 deletion ShortIOSDK/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ android {
kotlinOptions {
jvmTarget = "11"
}
testOptions {
unitTests {
isIncludeAndroidResources = true
}
}
}

dependencies {
Expand All @@ -40,6 +45,9 @@ dependencies {
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.google.code.gson:gson:2.10.1")
testImplementation(libs.junit)
testImplementation(libs.robolectric)
testImplementation(libs.mockwebserver)
testImplementation(libs.kotlinx.coroutines.test)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
}
}
Loading
Loading