From 4038f458085806cb952816b7306967d323ec4a4a Mon Sep 17 00:00:00 2001 From: rajpootathar Date: Mon, 30 Jun 2025 11:50:49 +0500 Subject: [PATCH 01/15] integrated encrypted links functionality and updated the README.md file --- README.md | 31 ++++++- app/build.gradle.kts | 2 +- .../com/github/shortioapp/MainActivity.kt | 90 ++++++++++++++++++- 3 files changed, 116 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d6e4ada..9c4151d 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,19 @@ when (val result = ShortioSdk.shortenUrl(apiKey, params)) { } } ``` + +### 🔐 Secure Short Link + +If you want to encrypt the original URL, the SDK provides a `createSecure` function that uses AES-GCM encryption. + +```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}") +``` + ## 🤖 Deep Linking Setup To handle deep links via Short.io on Android, you'll need to set up Android App Links properly using your domain's Digital Asset Links and intent filters. @@ -158,20 +171,30 @@ keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -sto 2. Override the onNewIntent() method to receive new intents when the activity is already running: ```kotlin +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.launch + 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) + Log.d("New Intent", "Host: ${result?.host}, Path: ${result?.path}") + } } ``` 3. In the same activity, you can also handle the initial intent inside the `onCreate()` method: ```kotlin // Optional +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.launch + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val result = ShortioSdk.handleIntent(intent) - Log.d("New Intent", "Host: ${result?.host}, Path: ${result?.path}") + lifecycleScope.launch { + val result = ShortioSdk.handleIntent(intent) + Log.d("New Intent", "Host: ${result?.host}, Path: ${result?.path}") + } } ``` diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 293b176..722ea43 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -49,7 +49,7 @@ dependencies { implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.material3) - implementation("com.github.Short-io:android-sdk:v1.0.5") + implementation("com.github.Short-io:android-sdk:v1.0.6") testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/app/src/main/java/com/github/shortioapp/MainActivity.kt b/app/src/main/java/com/github/shortioapp/MainActivity.kt index 2261505..4d98d05 100644 --- a/app/src/main/java/com/github/shortioapp/MainActivity.kt +++ b/app/src/main/java/com/github/shortioapp/MainActivity.kt @@ -34,6 +34,8 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.widget.Toast +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.launch class MainActivity : ComponentActivity() { @@ -59,6 +61,9 @@ class MainActivity : ComponentActivity() { Spacer(modifier = Modifier.height(16.dp)) LinkShorteningButton(apiKey = apiKey) + + Spacer(modifier = Modifier.height(16.dp)) + CreateSecureUrlButton() } } } @@ -67,8 +72,10 @@ class MainActivity : ComponentActivity() { override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) - val response = ShortioSdk.handleIntent(intent) - Log.d("New Intent", "Host: ${response?.host}, Path: ${response?.path}") + lifecycleScope.launch { + val result = ShortioSdk.handleIntent(intent) + Log.d("New Intent", "Host: ${result?.host}, Path: ${result?.path}") + } } } @@ -171,6 +178,85 @@ fun LinkShorteningButton(apiKey: String) { } } +@Composable +fun CreateSecureUrlButton() { + var resultMessage by remember { mutableStateOf(null) } + var isLoading by remember { mutableStateOf(false) } + val context = LocalContext.current + + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Button( + onClick = { + isLoading = true + thread { + try { + val originalUrl = "https://{your_domain}" + val result = ShortioSdk.createSecure(originalUrl) + + Log.d("SecureURL", "RESULT: $result") + Log.d("SecureURL", "URL: ${result.securedOriginalURL}") + Log.d("SecureURL", "KEY: ${result.securedShortUrl}") + + (context as ComponentActivity).runOnUiThread { + isLoading = false + resultMessage = "Secure URL: ${result.securedShortUrl}" + } + } catch (e: Exception) { + Log.e("SecureURL", "Exception: ${e.message}", e) + (context as ComponentActivity).runOnUiThread { + isLoading = false + resultMessage = "Error: ${e.message}" + } + } + } + }, + enabled = !isLoading + ) { + Text(text = if (isLoading) "Generating..." else "Create Secure Short Link") + } + + if (isLoading) { + Spacer(modifier = Modifier.height(16.dp)) + CircularProgressIndicator() + } + + resultMessage?.let { + Spacer(modifier = Modifier.height(16.dp)) + if (it.startsWith("Error")) { + Text( + text = it, + fontSize = 16.sp, + color = Color.Red, + modifier = Modifier.padding(horizontal = 8.dp) + ) + } else { + val secureUrl = it.substringAfter("Secure URL:").trim() + + Text( + text = secureUrl, + fontSize = 16.sp, + color = Color(0xFF4CAF50), + modifier = Modifier.padding(horizontal = 8.dp) + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Button( + onClick = { + val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newPlainText("secureURL", secureUrl) + clipboard.setPrimaryClip(clip) + Toast.makeText(context, "Secure URL copied to clipboard", Toast.LENGTH_SHORT).show() + } + ) { + Text("Copy Secure URL") + } + } + } + } +} + + @Composable fun LinkShortnerTitle() { From 358eb3e91876103109ae480901a3bee7817959ae Mon Sep 17 00:00:00 2001 From: rajpootathar Date: Tue, 1 Jul 2025 10:26:53 +0500 Subject: [PATCH 02/15] optimized code --- app/src/main/java/com/github/shortioapp/MainActivity.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/github/shortioapp/MainActivity.kt b/app/src/main/java/com/github/shortioapp/MainActivity.kt index 4d98d05..79d1d98 100644 --- a/app/src/main/java/com/github/shortioapp/MainActivity.kt +++ b/app/src/main/java/com/github/shortioapp/MainActivity.kt @@ -183,12 +183,13 @@ fun CreateSecureUrlButton() { var resultMessage by remember { mutableStateOf(null) } var isLoading by remember { mutableStateOf(false) } val context = LocalContext.current + val coroutineScope = rememberCoroutineScope() Column(horizontalAlignment = Alignment.CenterHorizontally) { Button( onClick = { isLoading = true - thread { + coroutineScope.launch { try { val originalUrl = "https://{your_domain}" val result = ShortioSdk.createSecure(originalUrl) From 866c1c4f17e72caee8be229c3158346aae0f1f30 Mon Sep 17 00:00:00 2001 From: rajpootathar Date: Fri, 8 Aug 2025 20:05:55 +0500 Subject: [PATCH 03/15] implemented destination URL handling from SDK in example app --- app/src/main/java/com/github/shortioapp/MainActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/github/shortioapp/MainActivity.kt b/app/src/main/java/com/github/shortioapp/MainActivity.kt index 79d1d98..5e6a16c 100644 --- a/app/src/main/java/com/github/shortioapp/MainActivity.kt +++ b/app/src/main/java/com/github/shortioapp/MainActivity.kt @@ -74,7 +74,7 @@ class MainActivity : ComponentActivity() { super.onNewIntent(intent) lifecycleScope.launch { val result = ShortioSdk.handleIntent(intent) - Log.d("New Intent", "Host: ${result?.host}, Path: ${result?.path}") + Log.d("New Intent", "Host: ${result?.host}, Path: ${result?.path}, DestinationURL: ${result?.destinationUrl}") } } } From 92cebb67237f0ff80216df2036bfd9fc50480c5c Mon Sep 17 00:00:00 2001 From: rajpootathar Date: Tue, 12 Aug 2025 20:21:30 +0500 Subject: [PATCH 04/15] * updated code --- README.md | 22 ++++++++--- .../java/com/github/shortioapp/Constants.kt | 8 ++++ .../com/github/shortioapp/MainActivity.kt | 38 +++++++++++++++---- 3 files changed, 55 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/com/github/shortioapp/Constants.kt diff --git a/README.md b/README.md index 9c4151d..d397af8 100644 --- a/README.md +++ b/README.md @@ -28,13 +28,25 @@ Open Android Studio, and open the `android-sdk-example` folder in Android Studio ## 🛠 Setup Instructions -### 1. Add Your API Key -Open the Main Activity file and replace the placeholder with your Short.io Public API Key: +### Initialization -```bash -val apiKey = "your_api_key" +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) ////Replace with your Short.io API KEY and Domain in Constants File + } ``` +* apiKey: Your API key string for authenticating requests. +* domain: The default domain to use for URL shortening. + + + + ### 🔗 Need help finding your API key? @@ -46,7 +58,6 @@ In your MainActivity file replace the placeholder with your Short.io domain and ```kotlin val params = ShortIOParameters( - domain = "your_domain", // e.g., example.short.gy originalURL = "https://{your_domain}" // The destination URL ) ``` @@ -63,7 +74,6 @@ Using your domain and original URL, you can generate a short link like this: val apiKey = "your_api_key" val params = ShortIOParameters( - domain = "your_domain", // e.g., example.short.gy originalURL = "https://{your_domain}" // The destination URL ) diff --git a/app/src/main/java/com/github/shortioapp/Constants.kt b/app/src/main/java/com/github/shortioapp/Constants.kt new file mode 100644 index 0000000..b224e11 --- /dev/null +++ b/app/src/main/java/com/github/shortioapp/Constants.kt @@ -0,0 +1,8 @@ +package com.github.shortioapp + + +//Replace with your Short.io API KEY +val apiKey = "YOUR_API_KEY" + +//Replace with your Short.io domain +val domain = "YOUR_DOMAIN" \ No newline at end of file diff --git a/app/src/main/java/com/github/shortioapp/MainActivity.kt b/app/src/main/java/com/github/shortioapp/MainActivity.kt index 5e6a16c..d44c5ab 100644 --- a/app/src/main/java/com/github/shortioapp/MainActivity.kt +++ b/app/src/main/java/com/github/shortioapp/MainActivity.kt @@ -35,16 +35,18 @@ import android.content.ClipboardManager import android.content.Context import android.widget.Toast import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + ShortioSdk.initialize( apiKey, domain) ////Replace with your Short.io API KEY and Domain in Constants File super.onCreate(savedInstanceState) enableEdgeToEdge() - val apiKey = "your_api_key" - setContent { ShortIOAppTheme { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> @@ -60,10 +62,13 @@ class MainActivity : ComponentActivity() { Spacer(modifier = Modifier.height(16.dp)) - LinkShorteningButton(apiKey = apiKey) + LinkShorteningButton() Spacer(modifier = Modifier.height(16.dp)) CreateSecureUrlButton() + + Spacer(modifier = Modifier.height(16.dp)) + trackConversionButton() } } } @@ -80,7 +85,7 @@ class MainActivity : ComponentActivity() { } @Composable -fun LinkShorteningButton(apiKey: String) { +fun LinkShorteningButton() { var isLoading by remember { mutableStateOf(false) } var resultMessage by remember { mutableStateOf(null) } var isError by remember { mutableStateOf(false) } @@ -95,11 +100,10 @@ fun LinkShorteningButton(apiKey: String) { thread { try { val params = ShortIOParameters( - originalURL = "https://{your_domain}", - domain = "your_domain" + originalURL = "https://demodeeplinkapp.short.gy/", ) - when (val result = ShortioSdk.shortenUrl(apiKey, params)) { + when (val result = ShortioSdk.shortenUrl(params)) { is ShortIOResult.Success -> { val shortUrl = result.data.shortURL Log.d("ShortIO", "Shortened URL: $shortUrl") @@ -257,7 +261,27 @@ fun CreateSecureUrlButton() { } } +@Composable +fun trackConversionButton() { + Button( + onClick = { + CoroutineScope(Dispatchers.IO).launch { + try { + val res = ShortioSdk.trackConversion( + "https://demodeeplinkapp.short.gy/", + conversionId = null + ) + Log.d("Handle Conversion Tracking", "Handle Conversion Tracking: $res") + } catch (e: Exception) { + Log.e("Handle Conversion Tracking", "Error calling trackConversion", e) + } + } + } + ) { + Text(text = "Conversion Tracking") + } +} @Composable fun LinkShortnerTitle() { From bc4df248b730b3b1367a1081f07300aa532b84ee Mon Sep 17 00:00:00 2001 From: rajpootathar Date: Wed, 13 Aug 2025 12:28:31 +0500 Subject: [PATCH 05/15] * fixed the readme and updated the code --- README.md | 31 +++++++--- app/build.gradle.kts | 2 +- .../com/github/shortioapp/MainActivity.kt | 56 +++++++++++++------ 3 files changed, 63 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index d397af8..799f775 100644 --- a/README.md +++ b/README.md @@ -36,12 +36,12 @@ To start using ShortioSdk, you need to initialize it early in your app lifecycle Example: Initialize in Activity ```kotlin - override fun onCreate() { - super.onCreate() - ShortioSdk.initialize(apiKey, domain) ////Replace with your Short.io API KEY and Domain in Constants File - } +override fun onCreate() { + super.onCreate() + ShortioSdk.initialize(apiKey, domain) ////Replace with your Short.io API KEY and Domain in Constants File +} ``` -* apiKey: Your API key string for authenticating requests. +* apiKey: Your API key string for initialization. * domain: The default domain to use for URL shortening. @@ -71,13 +71,12 @@ The app demonstrates: Using your domain and original URL, you can generate a short link like this: ```kotlin -val apiKey = "your_api_key" val params = ShortIOParameters( originalURL = "https://{your_domain}" // The destination URL ) -when (val result = ShortioSdk.shortenUrl(apiKey, params)) { +when (val result = ShortioSdk.shortenUrl(params)) { is ShortIOResult.Success -> { val shortUrl = result.data.shortURL Log.d("ShortIO", "Shortened URL: $shortUrl") @@ -88,6 +87,7 @@ when (val result = ShortioSdk.shortenUrl(apiKey, params)) { } } ``` +**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. ### 🔐 Secure Short Link @@ -101,6 +101,23 @@ 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 { + try { + val res = ShortioSdk.trackConversion() + // You can pass originalUrl, clid and conversionId as parameters to trackConversion() + // conversionId can be 'signup', 'purchase', 'download', etc. + Log.d("Handle Conversion Tracking", "Handle Conversion Tracking: $res") + } catch (e: Exception) { + Log.e("Handle Conversion Tracking", "Error calling trackConversion", e) + } +} +``` + ## 🤖 Deep Linking Setup To handle deep links via Short.io on Android, you'll need to set up Android App Links properly using your domain's Digital Asset Links and intent filters. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 722ea43..ff69040 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -49,7 +49,7 @@ dependencies { implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.material3) - implementation("com.github.Short-io:android-sdk:v1.0.6") + implementation("com.github.Short-io:android-sdk:v1.0.8") testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/app/src/main/java/com/github/shortioapp/MainActivity.kt b/app/src/main/java/com/github/shortioapp/MainActivity.kt index d44c5ab..7d8d2ef 100644 --- a/app/src/main/java/com/github/shortioapp/MainActivity.kt +++ b/app/src/main/java/com/github/shortioapp/MainActivity.kt @@ -43,8 +43,8 @@ import kotlinx.coroutines.launch class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { - ShortioSdk.initialize( apiKey, domain) ////Replace with your Short.io API KEY and Domain in Constants File super.onCreate(savedInstanceState) + ShortioSdk.initialize( apiKey, domain) //Replace with your Short.io API KEY and Domain in Constants File enableEdgeToEdge() setContent { @@ -68,7 +68,7 @@ class MainActivity : ComponentActivity() { CreateSecureUrlButton() Spacer(modifier = Modifier.height(16.dp)) - trackConversionButton() + TrackConversionButton() } } } @@ -100,7 +100,7 @@ fun LinkShorteningButton() { thread { try { val params = ShortIOParameters( - originalURL = "https://demodeeplinkapp.short.gy/", + originalURL = "https://{YOUR_DOMAIN}/", ) when (val result = ShortioSdk.shortenUrl(params)) { @@ -262,24 +262,44 @@ fun CreateSecureUrlButton() { } @Composable -fun trackConversionButton() { - Button( - onClick = { - CoroutineScope(Dispatchers.IO).launch { - try { - val res = ShortioSdk.trackConversion( - "https://demodeeplinkapp.short.gy/", - conversionId = null - ) - Log.d("Handle Conversion Tracking", "Handle Conversion Tracking: $res") - } catch (e: Exception) { - Log.e("Handle Conversion Tracking", "Error calling trackConversion", e) +fun TrackConversionButton() { + var conversionResult by remember { mutableStateOf(null) } + var errorMessage by remember { mutableStateOf(null) } + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Button( + onClick = { + CoroutineScope(Dispatchers.IO).launch { + try { + val res = ShortioSdk.trackConversion() + // You can pass originalUrl, clid and conversionId as parameters to trackConversion() + conversionResult = res + errorMessage = null + } catch (e: Exception) { + conversionResult = null + errorMessage = e.message + Log.e("Handle Conversion Tracking", "Error calling trackConversion", e) + } } } - + ) { + Text(text = "Conversion Tracking") + } + conversionResult?.let { success -> + Text( + text = if (success) "Conversion successful" else "Conversion failed", + color = if (success) Color.Green else Color.Red + ) + } + errorMessage?.let { + Text( + text = it, + color = Color.Red + ) } - ) { - Text(text = "Conversion Tracking") } } From 6b8f128402eaddc0e171cd8bb8d0e171af8feeac Mon Sep 17 00:00:00 2001 From: rajpootathar Date: Wed, 13 Aug 2025 17:34:07 +0500 Subject: [PATCH 06/15] * updated code --- app/src/main/java/com/github/shortioapp/MainActivity.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/github/shortioapp/MainActivity.kt b/app/src/main/java/com/github/shortioapp/MainActivity.kt index 7d8d2ef..bc5ea4a 100644 --- a/app/src/main/java/com/github/shortioapp/MainActivity.kt +++ b/app/src/main/java/com/github/shortioapp/MainActivity.kt @@ -274,8 +274,8 @@ fun TrackConversionButton() { onClick = { CoroutineScope(Dispatchers.IO).launch { try { - val res = ShortioSdk.trackConversion() - // You can pass originalUrl, clid and conversionId as parameters to trackConversion() + val res = ShortioSdk.trackConversion("Your_OriginalUrl", "CLID", "conversionId") + // You can pass originalUrl, clid and conversionId as parameters to trackConversion(). These are Optional Parameters conversionResult = res errorMessage = null } catch (e: Exception) { @@ -291,7 +291,7 @@ fun TrackConversionButton() { conversionResult?.let { success -> Text( text = if (success) "Conversion successful" else "Conversion failed", - color = if (success) Color.Green else Color.Red + color = if (success) Color(0xFF4CAF50) else Color.Red ) } errorMessage?.let { From 13743936cf74f3254e5f692eb5daca993db250d1 Mon Sep 17 00:00:00 2001 From: rajpootathar Date: Wed, 13 Aug 2025 20:30:18 +0500 Subject: [PATCH 07/15] * new fix --- app/src/main/java/com/github/shortioapp/MainActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/github/shortioapp/MainActivity.kt b/app/src/main/java/com/github/shortioapp/MainActivity.kt index bc5ea4a..39ebe8b 100644 --- a/app/src/main/java/com/github/shortioapp/MainActivity.kt +++ b/app/src/main/java/com/github/shortioapp/MainActivity.kt @@ -274,7 +274,7 @@ fun TrackConversionButton() { onClick = { CoroutineScope(Dispatchers.IO).launch { try { - val res = ShortioSdk.trackConversion("Your_OriginalUrl", "CLID", "conversionId") + val res = ShortioSdk.trackConversion("your_originalUrl", "your_clid", "your_conversionId") // You can pass originalUrl, clid and conversionId as parameters to trackConversion(). These are Optional Parameters conversionResult = res errorMessage = null From 5b79395d1ec5d5b6c0a2bd31eda7a609646de0f8 Mon Sep 17 00:00:00 2001 From: rajpootathar Date: Mon, 18 Aug 2025 12:08:33 +0500 Subject: [PATCH 08/15] * updated the readme file and code --- README.md | 15 +++++++++------ .../java/com/github/shortioapp/MainActivity.kt | 9 ++++++--- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 799f775..6361c06 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Open Android Studio, and open the `android-sdk-example` folder in Android Studio ## 🛠 Setup Instructions -### Initialization +### 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. @@ -76,7 +76,7 @@ val params = ShortIOParameters( originalURL = "https://{your_domain}" // The destination URL ) -when (val result = ShortioSdk.shortenUrl(params)) { +when (val result = ShortioSdk.createShortLink(params)) { is ShortIOResult.Success -> { val shortUrl = result.data.shortURL Log.d("ShortIO", "Shortened URL: $shortUrl") @@ -108,8 +108,11 @@ Track conversions for your short links to measure campaign effectiveness. The SD ```kotlin CoroutineScope(Dispatchers.IO).launch { try { - val res = ShortioSdk.trackConversion() - // You can pass originalUrl, clid and conversionId as parameters to trackConversion() + val res = ShortioSdk.trackConversion( + domain: "https://{your_domain}", // ⚠️ Deprecated (optional): + clid: "your_clid", // ⚠️ Deprecated (optional): + conversionId: "your_conversionID" // (optional) + ) // conversionId can be 'signup', 'purchase', 'download', etc. Log.d("Handle Conversion Tracking", "Handle Conversion Tracking: $res") } catch (e: Exception) { @@ -205,7 +208,7 @@ override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) lifecycleScope.launch { val result = ShortioSdk.handleIntent(intent) - Log.d("New Intent", "Host: ${result?.host}, Path: ${result?.path}") + Log.d("New Intent", "Host: ${result?.host}, Path: ${result?.path}, DestinationURL: ${result?.destinationUrl}") } } ``` @@ -220,7 +223,7 @@ 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}") + Log.d("New Intent", "Host: ${result?.host}, Path: ${result?.path}, DestinationURL: ${result?.destinationUrl}") } } ``` diff --git a/app/src/main/java/com/github/shortioapp/MainActivity.kt b/app/src/main/java/com/github/shortioapp/MainActivity.kt index 39ebe8b..04908ff 100644 --- a/app/src/main/java/com/github/shortioapp/MainActivity.kt +++ b/app/src/main/java/com/github/shortioapp/MainActivity.kt @@ -103,7 +103,7 @@ fun LinkShorteningButton() { originalURL = "https://{YOUR_DOMAIN}/", ) - when (val result = ShortioSdk.shortenUrl(params)) { + when (val result = ShortioSdk.createShortLink(params)) { is ShortIOResult.Success -> { val shortUrl = result.data.shortURL Log.d("ShortIO", "Shortened URL: $shortUrl") @@ -274,8 +274,11 @@ fun TrackConversionButton() { onClick = { CoroutineScope(Dispatchers.IO).launch { try { - val res = ShortioSdk.trackConversion("your_originalUrl", "your_clid", "your_conversionId") - // You can pass originalUrl, clid and conversionId as parameters to trackConversion(). These are Optional Parameters + val res = ShortioSdk.trackConversion( + "your_clid", // ⚠️ Deprecated (optional) + "your_domain", // ⚠️ Deprecated (optional) + "your_conversionID" //(optional) + ) conversionResult = res errorMessage = null } catch (e: Exception) { From 6b8702457d365d1d168064f9f0f2afd1b92ab429 Mon Sep 17 00:00:00 2001 From: rajpootathar Date: Mon, 18 Aug 2025 12:18:11 +0500 Subject: [PATCH 09/15] * changed sdk installation version --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ff69040..a5d35ad 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -49,7 +49,7 @@ dependencies { implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.material3) - implementation("com.github.Short-io:android-sdk:v1.0.8") + implementation("com.github.Short-io:android-sdk:v1.0.9") testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) From 252f5795236d614597a9a292fae0bbb29439b705 Mon Sep 17 00:00:00 2001 From: rajpootathar Date: Mon, 25 Aug 2025 17:28:12 +0500 Subject: [PATCH 10/15] * updated readme for getting the original url --- README.md | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6361c06..2a01f75 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,8 @@ 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: @@ -208,23 +210,31 @@ override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) lifecycleScope.launch { val result = ShortioSdk.handleIntent(intent) - Log.d("New Intent", "Host: ${result?.host}, Path: ${result?.path}, DestinationURL: ${result?.destinationUrl}") + // 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, you can also handle the initial intent inside the `onCreate()` method: ```kotlin -// Optional -import androidx.lifecycle.lifecycleScope -import kotlinx.coroutines.launch - 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}, DestinationURL: ${result?.destinationUrl}") - } + 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" + ) + } } ``` From 0087bcfc3113ce9f222d5e314dc2c81716952382 Mon Sep 17 00:00:00 2001 From: rajpootathar Date: Mon, 31 Aug 2026 15:07:44 +0500 Subject: [PATCH 11/15] test: add Compose UI tests for the sample screen Replaces the Android Studio placeholder with four tests covering launch, SDK initialization, empty initial state, and secure URL generation. Runs on the JVM under Robolectric rather than an emulator. Only the secure URL button is clicked: createSecure is local AES-GCM, while the other two buttons call the live Short.io API and must never run from a test. --- app/build.gradle.kts | 8 +++ .../com/github/shortioapp/ExampleUnitTest.kt | 17 ------- .../github/shortioapp/MainActivityTests.kt | 51 +++++++++++++++++++ app/src/test/resources/robolectric.properties | 1 + gradle/libs.versions.toml | 2 + 5 files changed, 62 insertions(+), 17 deletions(-) delete mode 100644 app/src/test/java/com/github/shortioapp/ExampleUnitTest.kt create mode 100644 app/src/test/java/com/github/shortioapp/MainActivityTests.kt create mode 100644 app/src/test/resources/robolectric.properties diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a5d35ad..c5ae43a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -37,6 +37,11 @@ android { buildFeatures { compose = true } + testOptions { + unitTests { + isIncludeAndroidResources = true + } + } } dependencies { @@ -51,6 +56,9 @@ dependencies { implementation(libs.androidx.material3) implementation("com.github.Short-io:android-sdk:v1.0.9") testImplementation(libs.junit) + testImplementation(libs.robolectric) + testImplementation(platform(libs.androidx.compose.bom)) + testImplementation(libs.androidx.ui.test.junit4) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(platform(libs.androidx.compose.bom)) diff --git a/app/src/test/java/com/github/shortioapp/ExampleUnitTest.kt b/app/src/test/java/com/github/shortioapp/ExampleUnitTest.kt deleted file mode 100644 index 56a97e5..0000000 --- a/app/src/test/java/com/github/shortioapp/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.github.shortioapp - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} \ No newline at end of file diff --git a/app/src/test/java/com/github/shortioapp/MainActivityTests.kt b/app/src/test/java/com/github/shortioapp/MainActivityTests.kt new file mode 100644 index 0000000..03a92a8 --- /dev/null +++ b/app/src/test/java/com/github/shortioapp/MainActivityTests.kt @@ -0,0 +1,51 @@ +package com.github.shortioapp + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import com.github.shortiosdk.ShortioSdk +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +/** + * Only createSecure is exercised here; it is pure local crypto. The other two + * buttons call the live Short.io API and must never be clicked from a test. + */ +@RunWith(RobolectricTestRunner::class) +class MainActivityTests { + + @get:Rule + val compose = createAndroidComposeRule() + + @Test + fun everyFeatureIsOfferedOnLaunch() { + compose.onNodeWithText("Short Link Generator").assertIsDisplayed() + compose.onNodeWithText("Create Short Link").assertIsDisplayed() + compose.onNodeWithText("Create Secure Short Link").assertIsDisplayed() + compose.onNodeWithText("Conversion Tracking").assertIsDisplayed() + } + + @Test + fun launchingInitializesTheSdkFromConstants() { + assertTrue(ShortioSdk.isSdkInitialized()) + compose.onNodeWithText("Short Link Generator").assertIsDisplayed() + } + + @Test + fun noResultIsShownBeforeAnyButtonIsPressed() { + compose.onNodeWithText("Copy URL").assertDoesNotExist() + compose.onNodeWithText("Copy Secure URL").assertDoesNotExist() + } + + @Test + fun creatingASecureUrlRendersTheKeyAndACopyButton() { + compose.onNodeWithText("Create Secure Short Link").performClick() + compose.waitForIdle() + + compose.onNodeWithText("Copy Secure URL").assertIsDisplayed() + } +} diff --git a/app/src/test/resources/robolectric.properties b/app/src/test/resources/robolectric.properties new file mode 100644 index 0000000..979b5ee --- /dev/null +++ b/app/src/test/resources/robolectric.properties @@ -0,0 +1 @@ +sdk=34 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6cb5d1c..4ec5eca 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,6 +8,7 @@ espressoCore = "3.6.1" lifecycleRuntimeKtx = "2.9.1" activityCompose = "1.10.1" composeBom = "2024.09.00" +robolectric = "4.14.1" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -24,6 +25,7 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" } +robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } From 86c8d54e84f2fcc8228e6d8a5c2ae18be3f4ab0c Mon Sep 17 00:00:00 2001 From: rajpootathar Date: Mon, 31 Aug 2026 15:07:44 +0500 Subject: [PATCH 12/15] ci: add GitHub Actions workflow Resolves the SDK from JitPack as its own step so an outage reads as a dependency failure, then assembles, runs the unit tests, and compiles the instrumented tests without an emulator. Lint is not run: the sample ships android:host="{your_domain}" as a fill-in-the-blank placeholder, which lint reports as an AppLinkUrlError. Also ignores .idea and .kotlin, and untracks the .idea files already committed. --- .github/workflows/ci.yml | 72 ++++++++++++++++++++++++++++++++++++++++ .gitignore | 8 ++--- README.md | 2 ++ 3 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..541f9a7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,72 @@ +name: CI + +on: + workflow_dispatch: + pull_request: + push: + branches: [main, short-io-app] + +# 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: + JAVA_VERSION: "17" + +jobs: + # Compose tests run under Robolectric; they never click the two buttons that + # would call the live Short.io API. + build: + name: build + 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 + + - 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 + + # Split out so a JitPack outage reads as a dependency failure, not a compile failure. + # 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: Resolve dependencies + run: ./gradlew --no-daemon :app:dependencies --configuration debugRuntimeClasspath + + - name: Assemble + run: ./gradlew --no-daemon assembleDebug + + - name: Unit tests + run: ./gradlew --no-daemon testDebugUnitTest + + # Type-checks androidTest without an emulator. + - name: Compile instrumented tests + run: ./gradlew --no-daemon assembleAndroidTest + + # Lint is not run: android:host="{your_domain}" is an intentional placeholder + # that lint reports as an AppLinkUrlError. + + - name: Upload reports + if: failure() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: build-reports + path: | + app/build/reports/ + app/build/test-results/ + retention-days: 7 diff --git a/.gitignore b/.gitignore index aa724b7..06500f5 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/README.md b/README.md index 2a01f75..dfc7b2e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # 📱 ShortIOApp – Android Sample Project for ShortIOSDK +[![CI](https://github.com/Short-io/android-sdk-example/actions/workflows/ci.yml/badge.svg)](https://github.com/Short-io/android-sdk-example/actions/workflows/ci.yml) + **ShortIOApp** is a sample Android project that demonstrates how to integrate and use the [ShortIOSDK](https://github.com/Short-io/android-sdk.git) for generating short links and handling deep links using [Short.io](https://short.io/). This project helps developers understand how to: From 5f28db259814308f04edef48be4d708ebdcaddaa Mon Sep 17 00:00:00 2001 From: rajpootathar Date: Mon, 31 Aug 2026 15:55:29 +0500 Subject: [PATCH 13/15] chore: stop checkout persisting credentials Checkout leaves a repo-scoped token in .git/config for the rest of the job; nothing here pushes, so it does not need one. --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 541f9a7..4e49d21 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,8 @@ jobs: timeout-minutes: 20 steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: From 693c303fb8dfa8bab5618d6c775b108b49f1ce65 Mon Sep 17 00:00:00 2001 From: rajpootathar Date: Mon, 31 Aug 2026 16:00:10 +0500 Subject: [PATCH 14/15] ci: report a check named test The main ruleset requires a status check called test; the job above reports as build, so nothing satisfied it and PRs stayed blocked. --- .github/workflows/ci.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4e49d21..2accbed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,3 +72,14 @@ jobs: app/build/reports/ app/build/test-results/ retention-days: 7 + + # The ruleset requires a check named `test`; the job above reports as `build`. + # always() so a failed build still reports this check. + test: + name: test + needs: build + if: always() + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - run: '[ "${{ needs.build.result }}" = "success" ]' From 0f701be6072a4556ef8651ed377145a844740838 Mon Sep 17 00:00:00 2001 From: rajpootathar Date: Tue, 1 Sep 2026 10:12:56 +0500 Subject: [PATCH 15/15] fix: shorten the ciphertext so the secure button yields a real link The button displayed securedShortUrl on its own, which is only the "#" fragment, so the screen showed a bare AES key labelled as a URL and the ciphertext went nowhere but logcat. It now shortens the ciphertext and appends the key to the returned short URL. createShortLink blocks, so the work moves off the Compose main dispatcher onto a background thread. --- .../com/github/shortioapp/MainActivity.kt | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/github/shortioapp/MainActivity.kt b/app/src/main/java/com/github/shortioapp/MainActivity.kt index 04908ff..cecce6e 100644 --- a/app/src/main/java/com/github/shortioapp/MainActivity.kt +++ b/app/src/main/java/com/github/shortioapp/MainActivity.kt @@ -187,24 +187,35 @@ fun CreateSecureUrlButton() { var resultMessage by remember { mutableStateOf(null) } var isLoading by remember { mutableStateOf(false) } val context = LocalContext.current - val coroutineScope = rememberCoroutineScope() Column(horizontalAlignment = Alignment.CenterHorizontally) { Button( onClick = { isLoading = true - coroutineScope.launch { + resultMessage = null + thread { try { val originalUrl = "https://{your_domain}" - val result = ShortioSdk.createSecure(originalUrl) + val secure = ShortioSdk.createSecure(originalUrl) - Log.d("SecureURL", "RESULT: $result") - Log.d("SecureURL", "URL: ${result.securedOriginalURL}") - Log.d("SecureURL", "KEY: ${result.securedShortUrl}") - - (context as ComponentActivity).runOnUiThread { - isLoading = false - resultMessage = "Secure URL: ${result.securedShortUrl}" + // The ciphertext is what gets shortened; the key never reaches the server. + val params = ShortIOParameters(originalURL = secure.securedOriginalURL) + when (val result = ShortioSdk.createShortLink(params)) { + is ShortIOResult.Success -> { + // securedShortUrl is the "#" fragment the browser decrypts with. + val secureUrl = result.data.shortURL + secure.securedShortUrl + (context as ComponentActivity).runOnUiThread { + isLoading = false + resultMessage = "Secure URL: $secureUrl" + } + } + is ShortIOResult.Error -> { + Log.e("SecureURL", "Error: ${result.data.message}") + (context as ComponentActivity).runOnUiThread { + isLoading = false + resultMessage = "Error: ${result.data.message}" + } + } } } catch (e: Exception) { Log.e("SecureURL", "Exception: ${e.message}", e)