Products

UI Widgets

Overview

Our UI Widgets offer a suite of components designed to enrich your application with insightful health information. By integrating our UI Widgets into your app, you provide your users with valuable health insights derived from their activity and wellness data.

Get started with UI Widgets


Key Features

  • Seamless Integration : UI Widgets are designed for easy integration into any mobile application, providing a smooth and cohesive user experience.

  • User Engagement : Engage users with interactive and visually appealing widgets that present health insights in an intuitive and accessible manner.

  • Data-Driven Insights : UI widgets offer valuable insights from processed health data, empowering users with the knowledge to understand and improve their well-being.

  • Cross-Platform Compatibility : With support for iOS, Android, Flutter, and React Native, our widgets ensure a wide-reaching and versatile application across various devices and platforms.


How It Works

Simply use our webview component and call one of our UI Widget endpoints with the profile token provided by our SDK. Instantly, you'll receive a dynamic and interactive chart that suits your needs. Whether you want to display a Score Chart, Score Arc, or Factor List, our widgets make it easy to present valuable insights in a visually appealing way.


Available Widgets

Widgets come in two types - pages and components.

Light and dark theme

By default, UI widgets follow the user's system preferences to determine whether to display in light or dark mode. To override this behavior, add a theme query parameter to the widget URL.

Example: ?theme=light or ?theme=dark


Widget Pages

Widget pages are larger, comprehensive widgets that include a variety of widget components.

Webview Placeholder

Scores Page

The Scores Page widget visually represents an overview of all scores for the current day along with a date selector for browsing historical scores.

URL

https://webview.sahha.ai/app

URL with date

https://webview.sahha.ai/app?date=2024-05-20

Remove date selector

https://webview.sahha.ai/app?hideDateSelect=true


Widget Components

Widget components are lightweight, modular elements designed to display specific data in a compact format.

You can choose from a variety of styles to help you present Sahha data clearly and effectively.See below for examples and available customization options for each widget style.


Score Chart

Displays a 7-day trend of a selected score in a compact line chart, helping you visualize changes over time at a glance.

URL

https://webview.sahha.ai/score/ {score_type}/chart

URL for specific date

https://webview.sahha.ai/score/ {score_type}/chart?date=2024-05-20


Score Arc

Showcases a single score using a radial progress bar, offering a simple, visual representation of progress or performance.

URL

https://webview.sahha.ai/score/ {score_type}/arc

URL for specific date

https://webview.sahha.ai/score/ {score_type}/arc?date=2024-05-20

Remove score message

https://webview.sahha.ai/score/ {score_type}/arc?message=false


Score Bar

Presents a single score with a horizontal progress bar, ideal for quickly communicating how a score measures up within a range.

URL

https://webview.sahha.ai/score/ {score_type}/bar

URL for specific date

https://webview.sahha.ai/score/ {score_type}/bar?date=2024-05-20

Remove score message

https://webview.sahha.ai/score/ {score_type}/arc?message=false


Factors List

Lists the contributing factors associated with a score, providing additional insight into what influences the overall result.

URL

https://webview.sahha.ai/score/ {score_type}/factors

URL for specific date

https://webview.sahha.ai/score/ {score_type}/factors?date=2024-05-20

Factor score with goal

https://webview.sahha.ai/score/ {score_type}/factors?goal=true

Factor score as percentage

https://webview.sahha.ai/score/ {score_type}/factors?percentage=true

Remove score message

https://webview.sahha.ai/score/ {score_type}/arc?message=false


Integration Guide

Follow this guide to easily add UI Widgets directly into your project. Choose your platform:


iOS

You can show a webview in your UIKit or SwiftUI project.

UIKit

Use a WKWebView

If your project uses UIKit views, you will need to create and display a WKWebView .

View the WebKit - WKWebView guide.

// WebViewController.swift
import UIKit
import WebKit
import Sahha
class WebViewController: UIViewController, WKUIDelegate {
var webView: WKWebView!
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
// Use https://webview.sahha.ai/app in production
if let url = URL(string: "https://sandbox.webview.sahha.ai/app") {
var request = URLRequest(url: url)
if let authToken = Sahha.profileToken {
request.setValue(authToken, forHTTPHeaderField: "Authorization")
}
webView.load(request)
}
}
}

SwiftUI

Use a UIViewRepresentable and WKWebview

If your project uses SwiftUI views, you will need to create and display a UIViewRepresentable and WKWebview .

Step 1) Create the UIViewRepresentable

View the SwiftUI - UIViewRepresentable guide.

// WebView.swift
import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
let urlString: String
var profileToken: String?
func makeUIView(context: Context) -> WKWebView {
let webview = WKWebView()
webview.isMultipleTouchEnabled = false
return webview
}
func updateUIView(_ webView: WKWebView, context: Context) {
if let url = URL(string: urlString) {
var request = URLRequest(url: url)
// Add authorization
if let authToken = profileToken {
request.setValue(authToken, forHTTPHeaderField: "Authorization")
}
webView.load(request)
}
}
}

Step 2) Display the UIViewRepresentable in a SwiftUI view

View the WebKit - WKWebview guide.

// ContentView.swift
import SwiftUI
import Sahha
struct ContentView: View {
var body: some View {
// Use https://webview.sahha.ai/app in production
WebView(urlString: "https://sandbox.webview.sahha.ai/app", profileToken: Sahha.profileToken)
}
}

Android

Use a WebView

You will need to create and display a WebView .

View the Webkit - WebView guide.

AndroidView(
factory = {
WebView(context).apply {
val cookieManager = CookieManager.getInstance()
cookieManager.setAcceptCookie(true)
cookieManager.setAcceptThirdPartyCookies(this, true)
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
webViewClient = WebViewClient()
settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
databaseEnabled = true
cacheMode = WebSettings.LOAD_DEFAULT
}
// Use https://webview.sahha.ai/app in production
val webViewUrl = "https://sandbox.webview.sahha.ai/app"
Sahha.profileToken?.also { token ->
loadUrl(webViewUrl, mapOf("Authorization" to token))
} ?: loadUrl(webViewUrl)
}
},
)

Flutter

Use a WebView

You will need to create and display a WebView .

View the Flutter WebView guide.

Step 1) Add webview_flutter as a dependency in your pubspec.yaml file

dependencies:
flutter:
sdk: flutter
webview_flutter: ^4.7.0

Step 2) Instantiate a WebViewController

View the Flutter WebViewController guide.

Step 3) Pass the controller to a WebViewWidget

View the Flutter WebViewWidget guide.

import 'package:flutter/material.dart';
import 'package:sahha_flutter/sahha_flutter.dart';
import 'package:webview_flutter/webview_flutter.dart';
// Import for Android features
import 'package:webview_flutter_android/webview_flutter_android.dart';
// Import for iOS features
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
class WebView extends StatefulWidget {
const WebView({Key? key}) : super(key: key);
WebState createState() => WebState();
}
class WebState extends State<WebView> {
late final WebViewController _controller;
void initState() {
super.initState();
// #docregion platform_features
late final PlatformWebViewControllerCreationParams params;
if (WebViewPlatform.instance is WebKitWebViewPlatform) {
params = WebKitWebViewControllerCreationParams(
allowsInlineMediaPlayback: true,
mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
);
} else {
params = const PlatformWebViewControllerCreationParams();
}
final WebViewController controller =
WebViewController.fromPlatformCreationParams(params);
controller
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {
debugPrint('WebView is loading (progress : $progress%)');
},
onPageStarted: (String url) {
debugPrint('Page started loading: $url');
},
onPageFinished: (String url) {
debugPrint('Page finished loading: $url');
},
onWebResourceError: (WebResourceError error) {
debugPrint('''
Page resource error:
code: ${error.errorCode}
description: ${error.description}
errorType: ${error.errorType}
isForMainFrame: ${error.isForMainFrame}
''');
},
onNavigationRequest: (NavigationRequest request) {
if (request.url.startsWith('https://www.youtube.com/')) {
debugPrint('blocking navigation to ${request.url}');
return NavigationDecision.prevent;
}
debugPrint('allowing navigation to ${request.url}');
return NavigationDecision.navigate;
},
onHttpError: (HttpResponseError error) {
debugPrint('Error occurred on page: ${error.response?.statusCode}');
},
onUrlChange: (UrlChange change) {
debugPrint('url change to ${change.url}');
},
onHttpAuthRequest: (HttpAuthRequest request) {
debugPrint('auth request $request');
},
),
)
// #docregion platform_features
if (controller.platform is AndroidWebViewController) {
AndroidWebViewController.enableDebugging(true);
(controller.platform as AndroidWebViewController)
.setMediaPlaybackRequiresUserGesture(false);
}
// #enddocregion platform_features
_controller = controller;
SahhaFlutter.getProfileToken().then((value) {
debugPrint(value);
if (value != null) {
controller.loadRequest(
// Use https://webview.sahha.ai/app in production
Uri.parse("https://sandbox.webview.sahha.ai/app"),
headers: {"Authorization": value},
);
} else {
controller.loadRequest(
// Use https://webview.sahha.ai/app in production
Uri.parse("https://sandbox.webview.sahha.ai/app"),
);
}
}).catchError((error, stackTrace) {
debugPrint(error.toString());
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Webview Example')),
body: WebViewWidget(controller: _controller),
);
}
}

React Native

Use a WebView

You will need to create and display a WebView .

View the React Native WebView guide.

Step 1) Install react-native-webview via npm

npm install react-native-webview

Step 2) Instantiate a WebViewController

import React, { Component } from 'react';
import { WebView } from 'react-native-webview';
import Sahha from 'sahha-react-native';
const MyWebComponent = () => {
const [profileToken, setProfileToken] = useState<string>('');
useEffect(() => {
Sahha.getProfileToken((error: string, token?: string) => {
if (error) {
console.error(`Error: ${error}`);
} else if (token) {
console.log(`Profile Token: ${token}`);
setProfileToken(token);
} else {
console.log(`Profile Token: null`);
}
});
}, []);
// Ensure profile token has a value before rendering the webview
if(!profileToken) return null;
// Use https://webview.sahha.ai/app in production
return <WebView source={{
uri: "https://sandbox.webview.sahha.ai/app",
headers: {
'Authorization': profileToken,
},
}} style={{ flex: 1 }} />;
}

Web

Use an HTML iFrame

You will need to create and display an HTML iFrame .

View the HTML iFrame guide.

Route prefix required

When embedding webviews inside of an iFrame, all route names must be prefixed with /web .

For example, to use the sleep chart widget, the URL should be: https://webview.sahha.ai/web/score/sleep/chart

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sahha UI Widget Example</title>
<script>
// You will need to authenticate your user profile via the Sahha SDK or API
// Once you receive a profile token, set it here
const profileToken = 'PROFILE_TOKEN'; // Replace 'PROFILE_TOKEN' with the real profile token
// Origin for the webview
// Use 'https://webview.sahha.ai' for your production app
const origin = 'https://sandbox.webview.sahha.ai';
// The source URL for the iFrame
// This should be the full URL of the Sahha widget you want to render inside the iFrame.
// The URL must use the same `origin` as defined above to ensure proper communication between the parent page and the iFrame.
const source = origin + '/web/app'; // Example widget path: "/web/app"
// Event listener for message event
window.addEventListener('message', (event) => {
if (event.origin !== origin) return;
if (event.data.loaded) {
event.source?.postMessage(profileToken, origin);
}
});
// Create and inject the iFrame into your HTML page
document.addEventListener('DOMContentLoaded', () => {
const iframe = document.createElement('iframe');
iframe.src = source;
iframe.title = 'Sahha Webview';
iframe.style.width = '100%';
iframe.style.height = '1000px';
iframe.style.border = 'none';
document.body.appendChild(iframe);
});
</script>
</head>
<body></body>
</html>

Retrieving Widget Height

Overview

The Sahha WebView automatically sends its current height to the native application whenever its size changes. This allows dynamic resizing of the container holding the WebView to match its content.

Communication happens via:

  • Calling NativeBridge.postMessage (if injected into the page)
  • Fallback to window.parent.postMessage if running inside an iframe

Additionally, each WebView can accept a unique id query parameter, so your app knows which WebView the message originated from.


Requirements

To enable communication:

  • Inject a NativeBridge into the WebView's JavaScript context.
  • Handle postMessage in your platform's native WebView API.
  • Pass an id as a query parameter on the WebView's URL.

Example URL:

https://webview.sahha.ai/score/activity/arc?id=my-custom-webview-id

Injecting NativeBridge

The WebView must have this small JavaScript snippet injected before page scripts run:

(function () {
if (typeof window.NativeBridge === 'undefined') {
window.NativeBridge = {
postMessage: function (message) {
// Forward message to your platform's onMessage or equivalent handler
window.ReactNativeWebView?.postMessage?.(message); // Example for React Native using react-native-webview
}
};
}
})();

Important:

  • You must adapt window.ReactNativeWebView.postMessage to match your platform's WebView message handling method.
  • If you are using Flutter, Android, iOS, or others, replace the postMessage line appropriately.

Message Format

The Sahha WebView sends messages to the host application when its height changes. The structure is always:

{
"type": "webview:height",
"height": 1234, // Content height in pixels
"id": "your-webview-id" // From URL query param
}

But the message type depends on the transport:

Context Sent As Parse Required?
NativeBridge Stringified JSON ✅ Yes
window.parent.postMessage (iframe) Raw object ❌ No parsing needed

Why This Matters

Your platform must detect which environment it's in:

  • If you inject a NativeBridge, your handler receives a JSON string, and you must parse it .
  • If you're embedding Sahha in an iframe , the message will arrive in a message event as a JavaScript object , and is ready to use.

Troubleshooting

Problem Cause Solution
No messages received NativeBridge not injected Ensure injection code runs early
Message received but no id Missing id in WebView URL Add ?id=your-id to WebView URL
Wrong platform code in injection postMessage incorrectly mapped Match NativeBridge.postMessage to platform
Messages received but can't access data Message is a JSON string and not a JS object Ensure you are parsing messages properly

Custom UI

It's easy to build your own Custom UI

You can use Sahha data to easily build your own Custom UI.

Follow our handy guides to see what you can build:

- Build a webapp with Sahha in 30 minutes

- Visualizing Sahha Scores in React-Native

Previous
Data Logs