4 min read
Mobile recipe
The endpoint is HTTPS POST. Anything that speaks JSON or multipart works. Snippets here for React Native and Swift; the pattern translates directly to Kotlin / Flutter / .NET MAUI.
React Native
JSON submission with retry on transient failure:
// useFormspring.ts
import { useState } from 'react';
const ENDPOINT = 'https://formspring.io/f/r2EdO-orF-3S';
export function useFormspring() {
const [sending, setSending] = useState(false);
const [error, setError] = useState<string | null>(null);
async function submit(payload: Record<string, unknown>) {
setSending(true);
setError(null);
for (let attempt = 1; attempt <= 3; attempt++) {
try {
const r = await fetch(ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify(payload),
});
if (r.ok) {
setSending(false);
return true;
}
if (r.status >= 400 && r.status < 500) {
// client error — don't retry
setError((await r.json())?.message ?? `HTTP ${r.status}`);
setSending(false);
return false;
}
// 5xx — fall through to retry
} catch (e: any) {
if (attempt === 3) {
setError(e.message ?? 'Network error');
}
}
await new Promise((r) => setTimeout(r, 500 * 2 ** (attempt - 1)));
}
setSending(false);
return false;
}
return { submit, sending, error };
}
Multipart (file upload)
async function submitWithPhoto(uri: string, fields: Record<string, string>) {
const fd = new FormData();
Object.entries(fields).forEach(([k, v]) => fd.append(k, v));
fd.append('photo', {
uri,
name: 'photo.jpg',
type: 'image/jpeg',
} as any);
const r = await fetch(ENDPOINT, {
method: 'POST',
body: fd,
// do NOT set Content-Type; React Native sets the multipart boundary
});
return r.ok;
}
The { uri, name, type } shape is the React Native convention — the runtime streams the file from disk without loading it into JS memory.
Swift (URLSession)
JSON submission:
import Foundation
struct Submission: Encodable {
let email: String
let message: String
}
enum FormspringError: Error { case http(Int), transport(Error) }
func submit(_ payload: Submission) async throws {
let url = URL(string: "https://formspring.io/f/r2EdO-orF-3S")!
var req = URLRequest(url: url)
req.httpMethod = "POST"
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
req.setValue("application/json", forHTTPHeaderField: "Accept")
req.httpBody = try JSONEncoder().encode(payload)
let (_, resp) = try await URLSession.shared.data(for: req)
guard let http = resp as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
throw FormspringError.http((resp as? HTTPURLResponse)?.statusCode ?? -1)
}
}
Multipart in Swift
func submitMultipart(email: String, imageData: Data) async throws {
let url = URL(string: "https://formspring.io/f/r2EdO-orF-3S")!
let boundary = "Boundary-\(UUID().uuidString)"
var req = URLRequest(url: url)
req.httpMethod = "POST"
req.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
var body = Data()
func append(_ s: String) { body.append(s.data(using: .utf8)!) }
append("--\(boundary)\r\n")
append("Content-Disposition: form-data; name=\"email\"\r\n\r\n")
append("\(email)\r\n")
append("--\(boundary)\r\n")
append("Content-Disposition: form-data; name=\"photo\"; filename=\"photo.jpg\"\r\n")
append("Content-Type: image/jpeg\r\n\r\n")
body.append(imageData)
append("\r\n--\(boundary)--\r\n")
req.httpBody = body
let (_, resp) = try await URLSession.shared.data(for: req)
guard let http = resp as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
throw FormspringError.http((resp as? HTTPURLResponse)?.statusCode ?? -1)
}
}
Network handling
Mobile networks fail in ways desktop ones don't — radio off, captive portals, walking out of range mid-request. Defensive defaults:
- Timeout: 30 seconds for the connection. Don't block UI on it.
- Retry: only on 5xx and transport errors. Never retry on 4xx — the request is bad and will keep being bad.
- Backoff: exponential with jitter. 500ms, 1s, 2s.
- Offline queue: store the payload locally, flush when reachability returns. Use
NetInfo(React Native) orNWPathMonitor(Swift).