Build a Streamlit app¶
A working Streamlit app on top of SweatStack in under fifteen minutes. It authenticates the user, lets them pick an activity, and renders a heart-rate chart from the timeseries data.
What you'll build¶
A Streamlit app that:
- Authenticates with SweatStack via OAuth2
- Fetches the user's activities
- Renders a heart-rate chart for the selected activity
- Supports switching between accessible users (useful when building for coaches)
In a hurry?
uv init
uv add "sweatstack[streamlit]"
Create a new app at app.sweatstack.no/applications/new with redirect URI http://localhost:8501. Save the credentials in a .env file:
SWEATSTACK_CLIENT_ID=your_client_id_here
SWEATSTACK_CLIENT_SECRET=your_client_secret_here
Create app.py:
import os
import streamlit as st
from sweatstack.streamlit import StreamlitAuth
auth = StreamlitAuth(
client_id=os.getenv("SWEATSTACK_CLIENT_ID"),
client_secret=os.getenv("SWEATSTACK_CLIENT_SECRET"),
redirect_uri="http://localhost:8501",
)
with st.sidebar:
auth.authenticate()
if auth.is_authenticated():
auth.select_user()
if not auth.is_authenticated():
st.write("# Heart-rate dashboard")
st.write("Please log in to view your activity data.")
st.stop()
st.write("# Heart-rate dashboard")
activity = auth.select_activity()
if not activity:
st.warning("No activities found.")
st.stop()
st.write(f"### {activity.sport.display_name()} on {activity.start.date()}")
data = auth.client.get_activity_data(activity.id)
if data is None or data.empty or "heart_rate" not in data.columns:
st.info("No heart-rate data available for this activity.")
st.stop()
st.line_chart(data["heart_rate"])
Run it:
uv run --env-file .env streamlit run app.py
Prerequisites¶
- A SweatStack account (app.sweatstack.no)
- uv installed
Create a SweatStack application¶
Register your app to get OAuth2 credentials:
- Go to app.sweatstack.no/applications/new.
- Use placeholder values for URL, image URL, and privacy statement for now.
- Set the redirect URI to
http://localhost:8501. - Save, then click Create Secret, name it, and copy the secret immediately. It's shown only once.
Save the credentials in a .env file:
SWEATSTACK_CLIENT_ID=your_client_id_here
SWEATSTACK_CLIENT_SECRET=your_client_secret_here
Warning
Treat the client secret like a password. Add .env to .gitignore before committing anything.
Set up the project¶
Create a project folder and install the dependencies:
uv init
uv add "sweatstack[streamlit]"
Wire up authentication¶
Create app.py:
import os
import streamlit as st
from sweatstack.streamlit import StreamlitAuth
auth = StreamlitAuth(
client_id=os.getenv("SWEATSTACK_CLIENT_ID"),
client_secret=os.getenv("SWEATSTACK_CLIENT_SECRET"),
redirect_uri="http://localhost:8501",
)
auth.authenticate()
if not auth.is_authenticated():
st.write("# Heart-rate dashboard")
st.write("Please log in to view your activity data.")
st.stop()
st.write("# Heart-rate dashboard")
st.success("Connected to SweatStack.")
What this does:
StreamlitAuthhandles the OAuth2 flow.auth.authenticate()renders a login or logout button.auth.is_authenticated()checks session state.st.stop()short-circuits the rest of the script for unauthenticated users.
Use auth.client for API calls
StreamlitAuth.client is a per-session client tied to the authenticated user. The module-level singleton (import sweatstack) is shared across Streamlit sessions and can leak data between users. Always use auth.client.<method>().
Run the app¶
uv run --env-file .env streamlit run app.py
The browser opens automatically (or visit http://localhost:8501). Click the login button, authorize on SweatStack, and you're back in your app authenticated.
Add activity selection¶
Replace the st.success(...) line with an activity picker. auth.select_activity() renders a dropdown of the user's recent activities:
st.write("# Heart-rate dashboard")
activity = auth.select_activity()
if not activity:
st.warning("No activities found.")
st.stop()
st.write(f"### {activity.sport.display_name()} on {activity.start.date()}")
Add a heart-rate chart¶
Fetch the activity's timeseries data and render it. Add this after the activity-selection block:
data = auth.client.get_activity_data(activity.id)
if data is None or data.empty or "heart_rate" not in data.columns:
st.info("No heart-rate data available for this activity.")
st.stop()
st.line_chart(data["heart_rate"])
get_activity_data returns a pandas DataFrame indexed by timestamp, with columns for whichever metrics the activity recorded (heart_rate, power, speed, etc.). Not every activity has every metric; the check above guards for missing heart-rate data.
st.line_chart is Streamlit's native chart for a single time-series. For more control over styling, swap in matplotlib or altair.
Switch between users (coaches)¶
If your users are coaches with access to multiple athletes, add a user selector. Move the auth widget into the sidebar and add auth.select_user():
with st.sidebar:
auth.authenticate()
if auth.is_authenticated():
auth.select_user()
For solo apps, skip this section. auth.client always defaults to the authenticated user.
What's next¶
- The data model lists every metric that can show up in
get_activity_data. - The Streamlit framework reference covers the full
StreamlitAuthAPI:select_sport,select_metric,select_tag, and more. - For longer-running analysis (months of activities at once), see the Analyze activity data guide.