06-17-2025 07:08
06-17-2025 07:08
It works up to the point when users can select which permissions they want to grant. After clicking next. I get the error, No token in response. What's wrong?
My code
import AsyncStorage from "@react-native-async-storage/async-storage";
import * as AuthSession from "expo-auth-session";
import { StatusBar } from "expo-status-bar";
import * as WebBrowser from "expo-web-browser";
import React, { JSX, useEffect, useState } from "react";
import {
ActivityIndicator,
Alert,
RefreshControl,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
View,
} from "react-native";
WebBrowser.maybeCompleteAuthSession();
// Fitbit OAuth endpoints
const discovery = {
authorizationEndpoint: "https://www.fitbit.com/oauth2/authorize",
tokenEndpoint: "https://api.fitbit.com/oauth2/token",
};
// Client ID
const CLIENT_ID = "23QF62";
// const redirectUri = "https://auth.expo.dev/@gbemiga/predict_health";
const redirectUri = AuthSession.makeRedirectUri({
// For development (using Expo's proxy)
scheme: __DEV__ ? "exp" : "predicthealth",
path: "redirect",
// For production/standalone apps
native: "predicthealth://redirect",
});
console.log("Using redirect URI:", redirectUri);
type ZoneName = "out of range" | "fat burn" | "cardio" | "peak";
interface StepsDataPoint {
dateTime: string;
value: string;
}
interface StepsData {
"activities-steps": StepsDataPoint[];
}
interface HeartRateZone {
caloriesOut: number;
max: number;
min: number;
minutes: number;
name: string;
}
interface HeartRateValue {
customHeartRateZones: HeartRateZone[];
heartRateZones: HeartRateZone[];
restingHeartRate?: number;
}
interface HeartRateDataPoint {
dateTime: string;
value: HeartRateValue;
}
interface HeartRateData {
"activities-heart": HeartRateDataPoint[];
}
interface UserProfileData {
user: {
displayName: string;
encodedId: string;
fullName: string;
};
}
export default function HomeScreen() {
const [accessToken, setAccessToken] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [stepsData, setStepsData] = useState<StepsData | null>(null);
const [heartRateData, setHeartRateData] = useState<HeartRateData | null>(
null
);
const [userProfile, setUserProfile] = useState<string | null>(null);
const [refreshing, setRefreshing] = useState(false);
const [request, response, promptAsync] = AuthSession.useAuthRequest(
{
clientId: CLIENT_ID,
scopes: ["activity", "heartrate", "profile"],
redirectUri,
usePKCE: true,
responseType: "code",
extraParams: {
prompt: "consent",
},
},
discovery
);
useEffect(() => {
loadAccessToken();
}, []);
useEffect(() => {
if (response?.type === "success") {
const code = response.params.code;
if (code) exchangeCodeForToken(code);
}
}, [response]);
const loadAccessToken = async () => {
const token = await AsyncStorage.getItem("fitbit_access_token");
if (token) {
setAccessToken(token);
fetchHealthData(token);
}
};
const saveAccessToken = async (token: string) => {
await AsyncStorage.setItem("fitbit_access_token", token);
};
const exchangeCodeForToken = async (code: string) => {
setIsLoading(true);
try {
console.log("Exchanging code for token...");
console.log("Using redirect URI:", redirectUri);
const tokenResponse = await AuthSession.exchangeCodeAsync(
{
clientId: CLIENT_ID,
code,
redirectUri,
},
discovery
);
console.log("Token response:", tokenResponse);
if (tokenResponse.accessToken) {
setAccessToken(tokenResponse.accessToken);
await saveAccessToken(tokenResponse.accessToken);
fetchHealthData(tokenResponse.accessToken);
} else {
console.error("No access token in response");
Alert.alert("Error", "Failed to get access token. Please try again.");
}
} catch (err: unknown) {
if (err instanceof Error) {
console.error("Token exchange failed:", err);
Alert.alert(
"Authentication Failed",
err.message || "Could not authenticate with Fitbit"
);
} else {
console.error("Unknown error:", err);
}
} finally {
setIsLoading(false);
}
};
const fetchHealthData = async (token: string) => {
if (!token) return;
setIsLoading(true);
try {
await Promise.all([
fetchStepsData(token),
fetchHeartRateData(token),
fetchUserProfile(token),
]);
} catch (e) {
Alert.alert("Error", "Failed to fetch health data");
} finally {
setIsLoading(false);
}
};
const fetchStepsData = async (token: string) => {
const today = new Date().toISOString().split("T")[0];
const res = await fetch(
`https://api.fitbit.com/1/user/-/activities/steps/date/${today}/1d.json`,
{
headers: { Authorization: `Bearer ${token}` },
}
);
if (res.ok) {
const data: StepsData = await res.json();
setStepsData(data);
} else if (res.status === 401) {
logout();
Alert.alert("Session expired", "Please log in again");
} else throw new Error("Steps fetch failed");
};
const authenticateWithFitbit = async () => {
try {
// First try with the standard promptAsync
const authResult = await promptAsync();
if (authResult?.type === "success") {
await exchangeCodeForToken(authResult.params.code);
} else if (authResult?.type === "error") {
console.error("Auth error:", authResult?.error);
Alert.alert(
"Error",
authResult?.error?.description || "Authentication failed"
);
}
} catch (error) {
console.error("Auth session error:", error);
// Fallback to direct WebBrowser approach if promptAsync fails
try {
const authUrl =
`https://www.fitbit.com/oauth2/authorize?` +
`response_type=code&` +
`client_id=${CLIENT_ID}&` +
`redirect_uri=${encodeURIComponent(redirectUri)}&` +
`scope=activity%20heartrate%20profile&` +
`prompt=consent`;
const result = await WebBrowser.openAuthSessionAsync(
authUrl,
redirectUri
);
if (result.type === "success") {
const url = result.url;
const params = new URLSearchParams(url.split("?")[1]);
const code = params.get("code");
if (code) await exchangeCodeForToken(code);
}
} catch (fallbackError) {
console.error("Fallback auth failed:", fallbackError);
Alert.alert(
"Error",
"Could not connect to Fitbit. Please try again later."
);
}
}
};
const fetchHeartRateData = async (token: string) => {
const today = new Date().toISOString().split("T")[0];
const res = await fetch(
`https://api.fitbit.com/1/user/-/activities/heart/date/${today}/1d.json`,
{
headers: { Authorization: `Bearer ${token}` },
}
);
if (res.ok) {
const data: HeartRateData = await res.json();
setHeartRateData(data);
} else if (res.status === 401) {
logout();
Alert.alert("Session expired", "Please log in again");
} else throw new Error("Heart rate fetch failed");
};
const fetchUserProfile = async (token: string) => {
const res = await fetch("https://api.fitbit.com/1/user/-/profile.json", {
headers: { Authorization: `Bearer ${token}` },
});
if (res.ok) {
const data: UserProfileData = await res.json();
setUserProfile(data.user.displayName);
} else if (res.status === 401) {
logout();
Alert.alert("Session expired", "Please log in again");
}
};
const onRefresh = async () => {
setRefreshing(true);
if (accessToken) await fetchHealthData(accessToken);
setRefreshing(false);
};
const logout = async () => {
await AsyncStorage.removeItem("fitbit_access_token");
setAccessToken(null);
setStepsData(null);
setHeartRateData(null);
setUserProfile(null);
};
const getZoneColor = (zoneName: string): string => {
const name = zoneName.toLowerCase() as ZoneName;
switch (name) {
case "out of range":
return "#4CAF50";
case "fat burn":
return "#FF9800";
case "cardio":
return "#FF5722";
case "peak":
return "#E91E63";
default:
return "#ccc";
}
};
const formatNumber = (num: string | number): string =>
typeof num === "string"
? parseInt(num).toLocaleString()
: num.toLocaleString();06-17-2025 07:22
06-17-2025 07:22
Here's my log
Android Bundled 6328ms node_modules\expo-router\entry.js (1 module)
LOG Using redirect URI: predicthealth://redirect
LOG Using redirect URI: predicthealth://redirect
LOG Exchanging code for token...
LOG Using redirect URI: predicthealth://redirect
LOG Exchanging code for token...
LOG Using redirect URI: predicthealth://redirect
LOG Token response: {"accessToken": undefined, "expiresIn": undefined, "idToken": undefined, "issuedAt": 1750167189, "rawResponse": {"errors": [[Object]], "success": false}, "refreshToken": undefined, "scope": undefined, "state": undefined, "tokenType": "bearer"}
ERROR No access token in response
LOG Token response: {"accessToken": undefined, "expiresIn": undefined, "idToken": undefined, "issuedAt": 1750167190, "rawResponse": {"errors": [[Object]], "success": false}, "refreshToken": undefined, "scope": undefined, "state": undefined, "tokenType": "bearer"}
ERROR No access token in response
Best Answer06-17-2025 17:25
06-17-2025 17:25
What's the message in the "errors" object (shown as [[Object]] in your log)?
Best Answer06-18-2025 07:29
06-18-2025 07:29
I have resolved the issue now. My latest issue is a Sync issue. Would you like to see the code that resolved the issue, but now as Sync issues?
Best Answer07-11-2025 13:44
Fitbit Developers oversee the SDK and API forums. We're here to answer questions about Fitbit developer tools, assist with projects, and make sure your voice is heard by the development team.
07-11-2025 13:44
Hi @Gbemiga
Would you please describe the sync issue that you are having?
Best Answer