Skip to content

Commit d6c1224

Browse files
committed
Add native examples and script generation
Add Android and iOS example apps demonstrating Snapfill integration. Add generate-native-scripts.mjs to copy built JS assets from @snapfill/core into both native library resource directories.
1 parent 1949cf1 commit d6c1224

17 files changed

Lines changed: 810 additions & 2 deletions

File tree

example/android/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Snapfill Android Example
2+
3+
Minimal Android app demonstrating the Snapfill library with a WebView checkout form.
4+
5+
## Prerequisites
6+
7+
- Android Studio (Arctic Fox or later)
8+
- Android SDK 35
9+
- JDK 11+
10+
11+
## Setup
12+
13+
```bash
14+
# From the monorepo root:
15+
pnpm install
16+
pnpm build
17+
pnpm generate:native # Generates JS assets into packages/android/src/main/assets/
18+
```
19+
20+
## Running
21+
22+
1. Open `example/android/` in Android Studio
23+
2. Sync Gradle
24+
3. Run on emulator or device (API 24+)
25+
26+
The app loads a local checkout HTML page into a WebView, detects form fields and cart data, and provides buttons to autofill shipping and payment fields.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
plugins {
2+
id("com.android.application")
3+
id("org.jetbrains.kotlin.android")
4+
}
5+
6+
android {
7+
namespace = "com.snapfill.example"
8+
compileSdk = 35
9+
10+
defaultConfig {
11+
applicationId = "com.snapfill.example"
12+
minSdk = 24
13+
targetSdk = 35
14+
versionCode = 1
15+
versionName = "1.0"
16+
}
17+
18+
compileOptions {
19+
sourceCompatibility = JavaVersion.VERSION_11
20+
targetCompatibility = JavaVersion.VERSION_11
21+
}
22+
23+
kotlinOptions {
24+
jvmTarget = "11"
25+
}
26+
}
27+
28+
dependencies {
29+
implementation(project(":snapfill"))
30+
implementation("androidx.appcompat:appcompat:1.7.0")
31+
implementation("androidx.core:core-ktx:1.13.1")
32+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<uses-permission android:name="android.permission.INTERNET" />
5+
6+
<application
7+
android:allowBackup="false"
8+
android:label="Snapfill Example"
9+
android:theme="@style/Theme.SnapfillExample">
10+
<activity
11+
android:name=".MainActivity"
12+
android:exported="true">
13+
<intent-filter>
14+
<action android:name="android.intent.action.MAIN" />
15+
<category android:name="android.intent.category.LAUNCHER" />
16+
</intent-filter>
17+
</activity>
18+
</application>
19+
</manifest>
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Checkout</title>
7+
8+
<!-- JSON-LD structured data (cart detector source) -->
9+
<script type="application/ld+json">
10+
{
11+
"@context": "https://schema.org",
12+
"@type": "Product",
13+
"name": "Premium Wireless Headphones",
14+
"image": "https://via.placeholder.com/100x100.png?text=Headphones",
15+
"offers": { "@type": "Offer", "price": "89.99", "priceCurrency": "USD" }
16+
}
17+
</script>
18+
<script type="application/ld+json">
19+
{
20+
"@context": "https://schema.org",
21+
"@type": "Product",
22+
"name": "USB-C Charging Cable",
23+
"image": "https://via.placeholder.com/100x100.png?text=Cable",
24+
"offers": { "@type": "Offer", "price": "12.50", "priceCurrency": "USD" }
25+
}
26+
</script>
27+
28+
<style>
29+
* { box-sizing: border-box; margin: 0; padding: 0; }
30+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #fff; color: #333; padding: 16px; }
31+
h1 { font-size: 18px; margin-bottom: 4px; }
32+
.subtitle { color: #888; font-size: 12px; margin-bottom: 20px; }
33+
.section { margin-bottom: 24px; }
34+
.section h2 { font-size: 14px; font-weight: 600; margin-bottom: 10px; padding-bottom: 6px; border-bottom: 1px solid #eee; color: #555; }
35+
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 10px; }
36+
.form-row.single { grid-template-columns: 1fr; }
37+
.form-row.triple { grid-template-columns: 1fr 1fr 1fr; }
38+
.form-group { display: flex; flex-direction: column; }
39+
.form-group label { font-size: 11px; font-weight: 500; color: #666; margin-bottom: 3px; }
40+
.form-group input, .form-group select {
41+
padding: 8px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px;
42+
}
43+
.form-group input:focus, .form-group select:focus { outline: none; border-color: #007bff; }
44+
.cart-container { background: #fafafa; border: 1px solid #eee; border-radius: 8px; padding: 12px; }
45+
.cart-item { display: flex; align-items: center; gap: 10px; padding: 6px 0; border-bottom: 1px solid #eee; }
46+
.cart-item:last-child { border-bottom: none; }
47+
.cart-item .item-name { font-size: 13px; font-weight: 500; flex: 1; }
48+
.cart-item .item-price { font-size: 14px; font-weight: 600; }
49+
.cart-total { display: flex; justify-content: space-between; padding-top: 10px; margin-top: 6px; border-top: 2px solid #ddd; font-weight: 700; font-size: 15px; }
50+
</style>
51+
</head>
52+
<body>
53+
<h1>Checkout</h1>
54+
<p class="subtitle">Mock checkout page for testing Snapfill</p>
55+
56+
<div class="section">
57+
<h2>Order Summary</h2>
58+
<div class="cart-container" id="cart-container">
59+
<div class="cart-item">
60+
<div class="item-name">Premium Wireless Headphones</div>
61+
<div class="item-price">$89.99</div>
62+
</div>
63+
<div class="cart-item">
64+
<div class="item-name">USB-C Charging Cable</div>
65+
<div class="item-price">$12.50</div>
66+
</div>
67+
<div class="cart-total"><span>Total</span><span>$102.49</span></div>
68+
</div>
69+
</div>
70+
71+
<div class="section">
72+
<h2>Shipping Address</h2>
73+
<div class="form-row">
74+
<div class="form-group"><label for="ship-first">First Name</label><input type="text" id="ship-first" autocomplete="shipping given-name" placeholder="John"></div>
75+
<div class="form-group"><label for="ship-last">Last Name</label><input type="text" id="ship-last" autocomplete="shipping family-name" placeholder="Smith"></div>
76+
</div>
77+
<div class="form-row single"><div class="form-group"><label for="ship-email">Email</label><input type="email" id="ship-email" autocomplete="email" placeholder="john@example.com"></div></div>
78+
<div class="form-row single"><div class="form-group"><label for="ship-phone">Phone</label><input type="tel" id="ship-phone" autocomplete="tel" placeholder="+1 (555) 123-4567"></div></div>
79+
<div class="form-row single"><div class="form-group"><label for="ship-addr1">Address Line 1</label><input type="text" id="ship-addr1" autocomplete="shipping address-line1" placeholder="123 Main Street"></div></div>
80+
<div class="form-row single"><div class="form-group"><label for="ship-addr2">Address Line 2</label><input type="text" id="ship-addr2" autocomplete="shipping address-line2" placeholder="Apt 4B"></div></div>
81+
<div class="form-row triple">
82+
<div class="form-group"><label for="ship-city">City</label><input type="text" id="ship-city" autocomplete="shipping address-level2" placeholder="New York"></div>
83+
<div class="form-group"><label for="ship-state">State</label>
84+
<select id="ship-state" autocomplete="shipping address-level1"><option value="">Select...</option><option value="NY">New York</option><option value="CA">California</option><option value="TX">Texas</option></select>
85+
</div>
86+
<div class="form-group"><label for="ship-zip">ZIP Code</label><input type="text" id="ship-zip" autocomplete="shipping postal-code" placeholder="10001"></div>
87+
</div>
88+
<div class="form-row single">
89+
<div class="form-group"><label for="ship-country">Country</label>
90+
<select id="ship-country" autocomplete="shipping country"><option value="">Select...</option><option value="US">United States</option><option value="AU">Australia</option><option value="GB">United Kingdom</option></select>
91+
</div>
92+
</div>
93+
</div>
94+
95+
<div class="section">
96+
<h2>Payment Details</h2>
97+
<div class="form-row single"><div class="form-group"><label for="cardNumber">Card Number</label><input type="text" id="cardNumber" name="cardNumber" placeholder="4111 1111 1111 1111" maxlength="19"></div></div>
98+
<div class="form-row single"><div class="form-group"><label for="nameOnCard">Name on Card</label><input type="text" id="nameOnCard" name="nameOnCard" placeholder="JOHN SMITH"></div></div>
99+
<div class="form-row triple">
100+
<div class="form-group"><label for="expiryMonth">Exp Month</label>
101+
<select id="expiryMonth" name="expiryMonth"><option value="">MM</option><option value="01">01</option><option value="06">06</option><option value="12">12</option></select>
102+
</div>
103+
<div class="form-group"><label for="expiryYear">Exp Year</label>
104+
<select id="expiryYear" name="expiryYear"><option value="">YYYY</option><option value="2026">2026</option><option value="2028">2028</option><option value="2030">2030</option></select>
105+
</div>
106+
<div class="form-group"><label for="securityCode">Security Code</label><input type="text" id="securityCode" name="securityCode" placeholder="123" maxlength="4"></div>
107+
</div>
108+
</div>
109+
</body>
110+
</html>
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package com.snapfill.example
2+
3+
import android.os.Bundle
4+
import android.widget.Button
5+
import android.widget.TextView
6+
import android.webkit.WebView
7+
import androidx.appcompat.app.AppCompatActivity
8+
import com.snapfill.Snapfill
9+
import com.snapfill.SnapfillCart
10+
import com.snapfill.SnapfillFillResult
11+
import com.snapfill.SnapfillListener
12+
import java.text.SimpleDateFormat
13+
import java.util.Date
14+
import java.util.Locale
15+
16+
class MainActivity : AppCompatActivity() {
17+
18+
private lateinit var snapfill: Snapfill
19+
private lateinit var eventLog: TextView
20+
21+
private val sampleAddress = mapOf(
22+
"firstName" to "Jane",
23+
"lastName" to "Doe",
24+
"email" to "jane.doe@example.com",
25+
"phoneNumber" to "+1 555-867-5309",
26+
"postalAddressLine1" to "350 Fifth Avenue",
27+
"postalAddressLine2" to "Suite 3400",
28+
"postalSuburb" to "New York",
29+
"postalState" to "NY",
30+
"postalPostCode" to "10118",
31+
"postalCountry" to "US"
32+
)
33+
34+
private val sampleCard = mapOf(
35+
"ccNumber" to "4111111111111111",
36+
"ccName" to "JANE DOE",
37+
"ccExpiryMonth" to "06",
38+
"ccExpiryYear" to "2028",
39+
"ccCCV" to "737"
40+
)
41+
42+
override fun onCreate(savedInstanceState: Bundle?) {
43+
super.onCreate(savedInstanceState)
44+
setContentView(R.layout.activity_main)
45+
46+
val webView = findViewById<WebView>(R.id.webview)
47+
eventLog = findViewById(R.id.event_log)
48+
49+
snapfill = Snapfill(webView)
50+
snapfill.listener = object : SnapfillListener {
51+
override fun onFormDetected(fields: List<String>) {
52+
log("formDetected", "${fields.size} fields: ${fields.joinToString(", ")}")
53+
}
54+
55+
override fun onCartDetected(cart: SnapfillCart) {
56+
val total = String.format("%.2f", cart.total / 100.0)
57+
log("cartDetected", "$$total ${cart.currency ?: ""}${cart.products.size} item(s)")
58+
}
59+
60+
override fun onValuesCaptured(mappings: Map<String, String>) {
61+
log("valuesCaptured", "${mappings.size} values captured")
62+
}
63+
64+
override fun onFormFillComplete(result: SnapfillFillResult) {
65+
log("formFillComplete", "${result.filled}/${result.total} filled")
66+
}
67+
}
68+
snapfill.attach()
69+
70+
// Load the checkout HTML from assets
71+
webView.loadUrl("file:///android_asset/checkout.html")
72+
73+
findViewById<Button>(R.id.btn_fill_address).setOnClickListener {
74+
snapfill.fillForm(sampleAddress)
75+
}
76+
findViewById<Button>(R.id.btn_fill_card).setOnClickListener {
77+
snapfill.fillForm(sampleCard)
78+
}
79+
findViewById<Button>(R.id.btn_fill_all).setOnClickListener {
80+
snapfill.fillForm(sampleAddress + sampleCard)
81+
}
82+
}
83+
84+
private fun log(type: String, message: String) {
85+
val time = SimpleDateFormat("HH:mm:ss", Locale.US).format(Date())
86+
val line = "[$time] $type: $message\n"
87+
eventLog.text = "${line}${eventLog.text}"
88+
}
89+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<LinearLayout
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
android:layout_width="match_parent"
5+
android:layout_height="match_parent"
6+
android:orientation="vertical">
7+
8+
<WebView
9+
android:id="@+id/webview"
10+
android:layout_width="match_parent"
11+
android:layout_height="0dp"
12+
android:layout_weight="1" />
13+
14+
<LinearLayout
15+
android:layout_width="match_parent"
16+
android:layout_height="wrap_content"
17+
android:orientation="vertical"
18+
android:background="#1C1C1E"
19+
android:padding="12dp">
20+
21+
<TextView
22+
android:layout_width="wrap_content"
23+
android:layout_height="wrap_content"
24+
android:text="Snapfill Example"
25+
android:textColor="#FFFFFF"
26+
android:textSize="14sp"
27+
android:textStyle="bold"
28+
android:layout_marginBottom="8dp" />
29+
30+
<LinearLayout
31+
android:layout_width="match_parent"
32+
android:layout_height="wrap_content"
33+
android:orientation="horizontal">
34+
35+
<Button
36+
android:id="@+id/btn_fill_address"
37+
android:layout_width="0dp"
38+
android:layout_height="40dp"
39+
android:layout_weight="1"
40+
android:layout_marginEnd="4dp"
41+
android:text="Fill Address"
42+
android:textSize="12sp"
43+
android:textAllCaps="false" />
44+
45+
<Button
46+
android:id="@+id/btn_fill_card"
47+
android:layout_width="0dp"
48+
android:layout_height="40dp"
49+
android:layout_weight="1"
50+
android:layout_marginStart="4dp"
51+
android:layout_marginEnd="4dp"
52+
android:text="Fill Card"
53+
android:textSize="12sp"
54+
android:textAllCaps="false" />
55+
56+
<Button
57+
android:id="@+id/btn_fill_all"
58+
android:layout_width="0dp"
59+
android:layout_height="40dp"
60+
android:layout_weight="1"
61+
android:layout_marginStart="4dp"
62+
android:text="Fill All"
63+
android:textSize="12sp"
64+
android:textAllCaps="false" />
65+
</LinearLayout>
66+
67+
<ScrollView
68+
android:layout_width="match_parent"
69+
android:layout_height="120dp"
70+
android:layout_marginTop="8dp">
71+
72+
<TextView
73+
android:id="@+id/event_log"
74+
android:layout_width="match_parent"
75+
android:layout_height="wrap_content"
76+
android:textColor="#AEAEB2"
77+
android:textSize="11sp"
78+
android:fontFamily="monospace"
79+
android:text="Waiting for events..." />
80+
</ScrollView>
81+
</LinearLayout>
82+
</LinearLayout>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<style name="Theme.SnapfillExample" parent="Theme.AppCompat.Light.NoActionBar" />
4+
</resources>

example/android/build.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Top-level build file
2+
plugins {
3+
id("com.android.application") version "8.2.0" apply false
4+
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
5+
}

example/android/gradle.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
android.useAndroidX=true
2+
kotlin.code.style=official
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
pluginManagement {
2+
repositories {
3+
google()
4+
mavenCentral()
5+
gradlePluginPortal()
6+
}
7+
}
8+
9+
dependencyResolution {
10+
repositories {
11+
google()
12+
mavenCentral()
13+
}
14+
}
15+
16+
rootProject.name = "SnapfillExample"
17+
18+
include(":app")
19+
include(":snapfill")
20+
project(":snapfill").projectDir = file("../../packages/android")

0 commit comments

Comments
 (0)