ionic-svelte provides seamless integration between Ionic's mobile UI components and Svelte, enabling developers to build cross-platform mobile applications with native device capabilities. This article covers integrating Capacitor to access native device features like camera, geolocation, and push notifications. This is part 11 of a series on using ionic-svelte with Svelte.
This guide walks through creating a production-ready mobile application with native device integration using ionic-svelte, Capacitor, and Svelte, from initial setup to implementing real-world native features.
Prerequisites
Before starting, ensure you have:
- A SvelteKit project (SvelteKit 1.0+ or standalone Svelte 4+)
- Node.js 18+ and npm/pnpm/yarn
- Basic understanding of Svelte components, stores, and lifecycle
- Familiarity with mobile development concepts
- Android Studio (for Android development) or Xcode (for iOS development) - optional but recommended for testing
Action example: The setupIonicBase() function initializes Ionic's base styles and configuration, making all Ionic components available throughout your Svelte application. The @ionic/core package provides web components that work seamlessly with Svelte's reactivity system.
Installation
Install ionic-svelte, Ionic Core, and Capacitor:
npm install @ionic/core ionic-svelte @capacitor/core @capacitor/cli
npm install @capacitor/ios @capacitor/android
For specific native features, install the corresponding Capacitor plugins:
npm install @capacitor/camera @capacitor/geolocation @capacitor/push-notifications
npm install @capacitor/status-bar @capacitor/splash-screen
Project Setup
First, configure your SvelteKit project to work with Ionic. Create the main layout file:
<!-- src/routes/+layout.svelte -->
<script lang="ts">
import { onMount } from 'svelte';
import { setupIonicBase } from 'ionic-svelte';
import '../theme/variables.css';
import 'ionic-svelte/components/all';
onMount(() => {
setupIonicBase();
});
</script>
<ion-app>
<slot />
</ion-app>
Disable server-side rendering in the layout server file:
// src/routes/+layout.ts
export const ssr = false;
Create the Ionic theme variables file:
/* src/theme/variables.css */
:root {
--ion-color-primary: #3880ff;
--ion-color-primary-rgb: 56, 128, 255;
--ion-color-primary-contrast: #ffffff;
--ion-color-primary-contrast-rgb: 255, 255, 255;
--ion-color-primary-shade: #3171e0;
--ion-color-primary-tint: #4c8dff;
--ion-color-secondary: #3dc2ff;
--ion-color-secondary-rgb: 61, 194, 255;
--ion-color-secondary-contrast: #ffffff;
--ion-color-secondary-contrast-rgb: 255, 255, 255;
--ion-color-secondary-shade: #36abe0;
--ion-color-secondary-tint: #50c8ff;
}
Initialize Capacitor in your project:
npx cap init
This will prompt you for app details. Then add platforms:
npx cap add ios
npx cap add android
Create the Capacitor configuration file:
// capacitor.config.ts
import { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.example.app',
appName: 'My Ionic Svelte App',
webDir: 'build',
server: {
androidScheme: 'https'
},
plugins: {
SplashScreen: {
launchShowDuration: 2000,
launchAutoHide: true,
backgroundColor: "#3880ff",
androidSplashResourceName: "splash",
androidScaleType: "CENTER_CROP",
showSpinner: true,
androidSpinnerStyle: "large",
iosSpinnerStyle: "small",
spinnerColor: "#999999"
}
}
};
export default config;
First Example / Basic Usage
Let's start with a simple example that checks if the app is running on a native platform:
<!-- src/routes/+page.svelte -->
<script lang="ts">
import { onMount } from 'svelte';
import { Capacitor } from '@capacitor/core';
import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonButton } from 'ionic-svelte';
let platform = 'web';
let isNative = false;
onMount(() => {
platform = Capacitor.getPlatform();
isNative = Capacitor.isNativePlatform();
});
</script>
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Native Features Demo</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent class="ion-padding">
<h2>Platform: {platform}</h2>
<p>Is Native: {isNative ? 'Yes' : 'No'}</p>
<IonButton expand="block" color="primary">
Get Started
</IonButton>
</IonContent>
</IonPage>
This example demonstrates how to detect the platform and conditionally enable native features. The Capacitor.getPlatform() method returns 'ios', 'android', or 'web', while Capacitor.isNativePlatform() returns a boolean indicating if the app is running on a native device.
Understanding the Basics
Ionic components in Svelte work as web components, which means they integrate seamlessly with Svelte's reactivity. The ionic-svelte package provides Svelte-specific wrappers for complex components like navigation, tabs, and modals.
Key concepts:
- Platform Detection: Use Capacitor to detect the current platform and conditionally render features
- Plugin Access: Capacitor plugins provide access to native device APIs
-
Lifecycle Management: Use Svelte's
onMountandonDestroyto manage native resources
Here's a more advanced example showing platform-specific styling:
<!-- src/lib/PlatformAwareComponent.svelte -->
<script lang="ts">
import { onMount } from 'svelte';
import { Capacitor } from '@capacitor/core';
import { IonButton } from 'ionic-svelte';
let platform: string;
let isIOS: boolean;
let isAndroid: boolean;
onMount(() => {
platform = Capacitor.getPlatform();
isIOS = platform === 'ios';
isAndroid = platform === 'android';
});
</script>
<div class="platform-container" class:ios={isIOS} class:android={isAndroid}>
<IonButton color={isIOS ? 'primary' : 'secondary'}>
Platform-Specific Button
</IonButton>
</div>
<style>
.platform-container.ios {
--ion-color-primary: #007aff;
}
.platform-container.android {
--ion-color-primary: #3880ff;
}
</style>
Practical Example / Building Something Real
Let's build a complete feature that uses the camera, geolocation, and displays the data in an Ionic interface:
<!-- src/routes/camera-demo/+page.svelte -->
<script lang="ts">
import { onMount } from 'svelte';
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
import { Geolocation, Position } from '@capacitor/geolocation';
import {
IonPage,
IonHeader,
IonToolbar,
IonTitle,
IonContent,
IonButton,
IonCard,
IonCardHeader,
IonCardTitle,
IonCardContent,
IonImg,
IonItem,
IonLabel
} from 'ionic-svelte';
interface PhotoData {
webPath: string;
format: string;
}
interface LocationData {
latitude: number;
longitude: number;
accuracy: number;
}
let photo: PhotoData | null = null;
let location: LocationData | null = null;
let loading = false;
let error: string | null = null;
async function takePicture() {
try {
loading = true;
error = null;
const image = await Camera.getPhoto({
quality: 90,
allowEditing: false,
resultType: CameraResultType.Uri,
source: CameraSource.Camera
});
photo = {
webPath: image.webPath || '',
format: image.format || 'jpeg'
};
} catch (err: any) {
error = err.message || 'Failed to take picture';
console.error('Camera error:', err);
} finally {
loading = false;
}
}
async function getCurrentLocation() {
try {
loading = true;
error = null;
const position: Position = await Geolocation.getCurrentPosition({
enableHighAccuracy: true,
timeout: 10000
});
location = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy || 0
};
} catch (err: any) {
error = err.message || 'Failed to get location';
console.error('Geolocation error:', err);
} finally {
loading = false;
}
}
async function requestPermissions() {
try {
await Camera.requestPermissions();
await Geolocation.requestPermissions();
} catch (err: any) {
error = 'Permission request failed: ' + err.message;
}
}
onMount(() => {
requestPermissions();
});
</script>
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Camera & Location Demo</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent class="ion-padding">
{#if error}
<IonCard color="danger">
<IonCardHeader>
<IonCardTitle>Error</IonCardTitle>
</IonCardHeader>
<IonCardContent>
{error}
</IonCardContent>
</IonCard>
{/if}
<IonCard>
<IonCardHeader>
<IonCardTitle>Camera</IonCardTitle>
</IonCardHeader>
<IonCardContent>
<IonButton
expand="block"
color="primary"
on:click={takePicture}
disabled={loading}
>
{loading ? 'Processing...' : 'Take Picture'}
</IonButton>
{#if photo}
<div class="photo-container">
<IonImg src={photo.webPath} alt="Captured photo" />
</div>
{/if}
</IonCardContent>
</IonCard>
<IonCard>
<IonCardHeader>
<IonCardTitle>Geolocation</IonCardTitle>
</IonCardHeader>
<IonCardContent>
<IonButton
expand="block"
color="secondary"
on:click={getCurrentLocation}
disabled={loading}
>
{loading ? 'Getting location...' : 'Get Current Location'}
</IonButton>
{#if location}
<IonItem>
<IonLabel>
<h2>Latitude</h2>
<p>{location.latitude.toFixed(6)}</p>
</IonLabel>
</IonItem>
<IonItem>
<IonLabel>
<h2>Longitude</h2>
<p>{location.longitude.toFixed(6)}</p>
</IonLabel>
</IonItem>
<IonItem>
<IonLabel>
<h2>Accuracy</h2>
<p>{location.accuracy.toFixed(2)} meters</p>
</IonLabel>
</IonItem>
{/if}
</IonCardContent>
</IonCard>
</IonContent>
</IonPage>
<style>
.photo-container {
margin-top: 1rem;
display: flex;
justify-content: center;
}
.photo-container :global(img) {
max-width: 100%;
height: auto;
border-radius: 8px;
}
</style>
This example demonstrates:
- Camera Integration: Using Capacitor's Camera plugin to capture photos
- Geolocation: Getting the device's current location with high accuracy
- Permission Handling: Requesting necessary permissions on mount
- Error Handling: Proper error states and user feedback
- Ionic UI Components: Using Ionic cards, buttons, and layout components
- Loading States: Managing async operations with loading indicators
Common Issues / Troubleshooting
Issue 1: Components not rendering correctly
Problem: Ionic components appear unstyled or don't work properly.
Solution: Ensure you've called setupIonicBase() in your layout and imported the CSS:
<script>
import { setupIonicBase } from 'ionic-svelte';
import '../theme/variables.css';
import 'ionic-svelte/components/all';
setupIonicBase();
</script>
Issue 2: Native plugins return undefined on web
Problem: Camera or Geolocation plugins return undefined when testing in browser.
Solution: These plugins only work on native platforms. Use platform detection:
import { Capacitor } from '@capacitor/core';
if (Capacitor.isNativePlatform()) {
// Use native plugin
} else {
// Use web fallback or mock data
}
Issue 3: Build errors when syncing with Capacitor
Problem: npx cap sync fails or build directory not found.
Solution: Ensure your build output directory matches webDir in capacitor.config.ts. For SvelteKit, update the config:
const config: CapacitorConfig = {
webDir: '.svelte-kit/output/client', // or 'build' depending on your setup
// ...
};
Issue 4: Permissions not working on iOS/Android
Problem: Permission requests are ignored or fail silently.
Solution: Add permission descriptions to native configuration files. For iOS, edit ios/App/App/Info.plist. For Android, edit android/app/src/main/AndroidManifest.xml.
Next Steps
- Explore more Capacitor plugins: Storage, Network, App, Haptics
- Learn about Ionic's navigation system with
IonNavand routing - Implement push notifications for real-time updates
- Add biometric authentication using Capacitor's Biometric plugin
- Optimize performance with lazy loading and code splitting
- Check out the Ionic documentation for component APIs
- Review Capacitor plugins for more native features
Summary
You've learned how to integrate native mobile features with ionic-svelte and Capacitor. You can now detect platforms, access device cameras and geolocation, handle permissions, and build production-ready mobile applications with Svelte. The combination of Ionic's UI components and Capacitor's native APIs provides a powerful foundation for cross-platform mobile development.
Top comments (0)