Your mobile app has to manage connections with SDK-based providers — such as Apple HealthKit and Android Health
Connect — through the Vital Mobile Health SDK, separately from Vital Link.
You may want to have your own custom Link widget to match your app’s UI. This is useful for customers who don’t want to use Vital link but still want to use the Vital API.
It’s worthwhile ensuring that you have read the Auth Types documentation before proceeding. A full code example in React Native can be seen here.
The first step to creating a custom widget is to make a call to the providers endpoint. This will return a list of all available providers. You can then use this list to build your UI.
Once you have a list of all available providers, you can build your UI. You can use the logo field to display the provider’s logo. You can use the name field to display the provider’s name. You can use the supported_resources field to display the resources that the provider supports.
Once a user has selected a provider, you can generate a link token. This link token is used to generate an oauth link, or authorize an email, or email and password. You can read more about the link token here.
Once you have a link token, you can use it to generate an oauth link, or authorize an email, or email and password. You can read more about the link token here.
import{VitalClient,VitalEnvironment}from'@tryvital/vital-node';import{IndividualProviderData,PasswordProviders}from'@tryvital/vital-node/api';constrequest:IndividualProviderData={username:'<username>',password:'<password>',}const data =await client.link.connectPasswordProvider(PasswordProviders.<provider>, request)
Below is an example of how you can bring all of this together to build your own custom widget. This example is in React Native, but the same principles apply to any language.
React Native
importReact,{useEffect, useState}from'react';import{SafeAreaView,StatusBar,Linking,View, useColorScheme,Platform,}from'react-native';import{VitalHealth,VitalResource}from'@tryvital/vital-health-react-native';consthandleOAuth=async(userId: string,item:Provider)=>{const linkToken =awaitClient.Link.getLinkToken( userId,`${AppConfig.slug}://link`,);const link =awaitClient.Providers.getOauthUrl(item.slug, linkToken.link_token);Linking.openURL(link.oauth_url);};constListItem=({ userId, item, navigation,}:{userId: string;item:Provider;navigation: any;})=>{const{colors}=useTheme();const isDarkMode =useColorScheme()==='dark';const[isLoading, setLoading]=useState(false);consthandleNativeHealthKit=async()=>{const providerSlug =Platform.OS=='ios'?ManualProviderSlug.AppleHealthKit:ManualProviderSlug.HealthConnect;setLoading(true);const isHealthSDKAvailable =awaitVitalHealth.isAvailable();if(!isHealthSDKAvailable){console.warn('Health Connect is not available on this device.'); navigation.navigate('ConnectionCallback',{state:'failed',provider: providerSlug,});return;}try{awaitVitalHealth.configure({logsEnabled:true,numberOfDaysToBackFill:30,androidConfig:{syncOnAppStart:true},iOSConfig:{dataPushMode:'automatic',backgroundDeliveryEnabled:true,},});}catch(e){setLoading(false);console.warn(`Failed to configure ${providerSlug}`, e); navigation.navigate('ConnectionCallback',{state:'failed',provider: providerSlug,});}awaitVitalHealth.setUserId(userId);try{awaitVitalHealth.askForResources([VitalResource.Steps,VitalResource.Activity,VitalResource.HeartRate,VitalResource.Sleep,VitalResource.Workout,VitalResource.BloodPressure,VitalResource.Glucose,VitalResource.Body,VitalResource.Profile,VitalResource.ActiveEnergyBurned,VitalResource.BasalEnergyBurned,]);awaitVitalCore.createConnectedSourceIfNotExist(providerSlug);setLoading(false); navigation.navigate('ConnectionCallback',{state:'success',provider: providerSlug,});}catch(e){setLoading(false); navigation.navigate('ConnectionCallback',{state:'failed',provider: providerSlug,});}};constonPress=async()=>{if(item.auth_type==='oauth'){awaithandleOAuth(userId, item);}elseif( item.slug==='apple_health_kit'|| item.slug==='health_connect'){awaithandleNativeHealthKit();}};return(<Pressable onPress={()=>onPress()}>{({isHovered, isFocused, isPressed})=>{return(<HStack space={'md'} justifyContent="flex-start"><VStack><Text color={colors.text} fontType="medium">{item.name}</Text><Text fontType="regular" color={colors.secondary} size="xs" flexShrink={1} flexWrap="wrap">{item.description}</Text></VStack></HStack>);}}</Pressable>);};exportconstLinkDeviceScreen=({navigation})=>{const isDarkMode =useColorScheme()==='dark';const{colors}=useTheme();const styles =makeStyles(colors);const[providers, setProviders]=React.useState<Provider[]>([]);const[devices, setDevices]=React.useState<Provider[]>([]);const[loading, setLoading]=React.useState<boolean>(false);const[searchText, setSearchText]=React.useState('');const[error, setError]=React.useState<string |null>(null);const[userId, setUserId]=React.useState('');consthandleSearch=(text: string)=>{setSearchText(text);if(text ===''&& text.length<=2){setDevices(providers);}else{const filtered = providers.filter(item=> item.name.toLowerCase().includes(text.toLowerCase()),);setDevices(filtered);}};useEffect(()=>{constgetDevices=async()=>{setLoading(true);setError(null);const user_id =awaitgetData('user_id');if(user_id){const providers =awaitClient.Providers.getProviders();setLoading(false);setUserId(user_id);setProviders(providers);}else{setUserId('');setLoading(false);console.warn('Failed to get all supported providers');setError('Failed to get devices');}};},[navigation]);return(<SafeAreaView style={styles.container}><StatusBar barStyle={isDarkMode ?'light-content':'dark-content'} backgroundColor={styles.container.backgroundColor}/><View style={{paddingVertical:10,paddingHorizontal:16,flex:1,width:'100%',}}><VStack pb={10}><HStack justifyContent={'space-between'} py={'$3'} alignItems="center"><H1>Connect a Device</H1><Button onPress={()=> navigation.goBack()} variant="link"><Ionicons name="close-outline" size={25} color={colors.text}/></Button></HStack><Input variant="outline" size="lg" isDisabled={false} isInvalid={false} isReadOnly={false}><InputField color={colors.text} fontFamily={AppConfig.fonts.regular} onChangeText={handleSearch} value={searchText} placeholder="Search"/></Input></VStack>{!loading && error ?(<VStack><Text fontType="light">Failed to get supported Providers</Text></VStack>):(<FlatList data={devices} renderItem={({item})=>(<ListItem userId={userId} item={item} navigation={navigation}/>)} keyExtractor={item=> item.slug}/>)}</View></SafeAreaView>);};