arrow_back

Building Fitness-Driven dApps: Streaming Google Fit Data with W3bstream

登录 加入
Test and share your knowledge with our community!
done
Get access to over 700 hands-on labs, skill badges, and courses

Building Fitness-Driven dApps: Streaming Google Fit Data with W3bstream

Lab 2 个小时 15 分钟 universal_currency_alt 3 积分 show_chart 中级
Test and share your knowledge with our community!
done
Get access to over 700 hands-on labs, skill badges, and courses

GSP1178

Google Cloud Self-Paced Labs

W3bstream is a decentralized network that allows IoT devices and other data sources to stream and process data, serving as a bridge between real-world data sources and the blockchain. Through it, data like steps, locations, and achieved fitness goals, can be easily streamed and processed to prove real world activities to blockchain contracts. This enables developers to craft gamified experiences using blockchain - healthy behaviors can be incentivised by rewarding users with NFTs or ERC20 tokens.

In this lab you will learn how to channel Google Fit data via W3bstream to develop decentralized applications (dApps) that mint crypto assets based on user fitness activities. This lab is ideal for developers interested in building applications that bridge the worlds of Web2 and Web3, combining the strengths of Web2 services with the advantages of Web3 incentives.

What you'll learn

  1. Integrate Google Fit API in your applications: Connect and access fitness activity data from Google Fit API, including steps, distance, calories burned, and location data.
  2. Gamify Real-World behaviors with Web3: Design engaging challenges, incentivize healthy habits, and reward users for their accomplishments with blockchain incentives.
  3. Set up a W3bstream Project: W3bstream is a web3 streaming platform that allows real-time data streaming from various sources to prove real-world facts to blockchain applications.
  4. Minting Crypto Assets: mint NFTs or ERC20 tokens based on predefined user's goals and achievements and understand the smart contract implementation and integration with the streaming data.

Application workflow overview

The application workflow can be summarized as follows:

  1. User Registration: Users will authorize the web application to access their Google Fit data using a Google Account provided for you after you click Start Lab. Upon authorization, the web app fetches the user's email from Google Fit and hashes it to generate a unique user ID. The user wallet address is also fetched from the user's Metamask plugin. Finally, the binding between the user id and the wallet address is registered in a smart contract on the blockchain.
diagram of user registration process
  1. User Fitness Activity: Users engage in fitness activities using their smartphones with Google Fit or connected fitness apps.
fitness activity diagrams showing exercises and app logos
  1. W3bstream Data Sync & Processing: Users log into the web app using the previously mentioned Google Account to sync the latest sessions data to W3bstream. The web app forwards activity data along with the user IDs to W3bstream. W3bstream processes the data, determines if users deserve NFTs based on predefined conditions, and interacts with the smart contract to increase user balances.

fitness activity diagram to synch latest sessionsdiagram of app synch process

  1. Claiming the rewards: Log into the web app using the same Google Account credentials to view your NFT balances reflecting activity-based rewards. You can optionally claim NFTs associated with achievements and get them sent to your wallet. In this example, they are minted for completing yoga sessions lasting at least 30 minutes.
diagram of claiming rewards process

Prerequisites

  1. This lab must be run on a Chrome browser.
  2. The Metamask Plugin must be installed and configured on your Chrome browser.
  3. Optional: For an enhanced lab experience, add the Google Fit App to your mobile device to perform Task 8.

Setup and requirements

Please do the following BEFORE you click the Start Lab button!

Install Metamask plugin and fund your wallet

  • Metamask Browser Plugin: Install the Metamask browser plugin, which allows you to interact with blockchain networks. Set up an account and configure Metamask to interact with the IoTeX Testnet Blockchain

  • Fund your Metamask wallet with some test IOTX tokens by claiming them from the IoTeX public faucet. If you want more, you can log in to the IoTeX Developer Portal and claim them from your personal dashboard (you can refer to this article for step-by-step instructions on how to configure Metamask and claim test tokens).

  • To Add IoTeX Network Testnet network manually in Metamask wallet, use the below details:

Network Name IoTeX Network Testnet
New RPC URL https://babel-api.testnet.iotex.io
Chain ID 4690
Currency Symbol IOTX

Now you're ready to click Start lab.

Before you click the Start Lab button

Read these instructions. Labs are timed and you cannot pause them. The timer, which starts when you click Start Lab, shows how long Google Cloud resources will be made available to you.

This Qwiklabs hands-on lab lets you do the lab activities yourself in a real cloud environment, not in a simulation or demo environment. It does so by giving you new, temporary credentials that you use to sign in and access Google Cloud for the duration of the lab.

What you need

To complete this lab, you need:

  • Access to a standard internet browser (Chrome browser recommended).
  • Time to complete the lab.

Note: If you already have your own personal Google Cloud account or project, do not use it for this lab.

Note: If you are using a Pixelbook, open an Incognito window to run this lab.

A Windows VM is being provisioned for this lab, so it will take few minutes for the lab to be ready.

To access the Windows VM, click on the Open Windows VM button from Lab Details panel. Use either PowerShell or Visual Studio Code to perform the tasks for this lab.

Task 1. Building the Web App

For this lab, use the temporaroy Google Account that has been provisioned for you as a student. Use this as both the developer account to access the Google Fit API and as the end user account to generate some fitness data and mint the NFT to our wallet.

Allow Google OAuth Client to get access to the Fitness REST API. Follow these steps to obtain a GOOGLE_ID (Client ID) and a GOOGLE_SECRET (Client secret).

  1. Go to the Google API Console.

  2. Select your project

  3. From the Navigation menu choose APIs & Services > Library.

  4. Search for Fitness API and click the MANAGE button.

  5. In the navigation menu of Fitness API, choose Credentials.

  6. Click Create credentials, then select OAuth Client ID.

  7. If it asks you to CONFIGURE CONSENT SCREEN, follow the configuration:

    • Select User type: Internal.

    • Click on CREATE.

    • Enter app name.

    • User support email and Developer contact information can be the one of the lab test credentials.

    • Click SAVE AND CONTINUE.

    • Click on ADD OR REMOVE SCOPES.

    • Under Manually add scopes add https://www.googleapis.com/auth/fitness.activity.write and then click on ADD TO TABLE.

    • Click on UPDATE.

    • Click on SAVE AND CONTINUE two times.

    • Click on BACK TO DASHBOARD.

    • Click on Credentials > Create credentials, then select OAuth Client ID.

    • Under Application type, select Web application.

    • Under Authorized JavaScript origins, add https://developers.google.com.

    • Under Authorized redirect URI, enter the URL of the site where responses will be handled and add http://localhost:3000/api/auth/callback/google.

  8. Click Create. Your new OAuth 2.0 Client ID and secret appear in the list of IDs for your project. An OAuth 2.0 Client ID is a string of characters, something like this: 780816631155-gbvyo1o7r2pn95qc4ei9d61io4uh48hl.apps.googleusercontent.com

Create button for OAuth client ID

Click Check my progress to verify the objective. Enable Fitness API, Configure OAuth Consent Screen and OAuth Client ID

Note:For this lab, Git and Node.js are required, and they have been provisioned for you on the Windows VM.
  1. Clone the application repository - in either Windows PowerShell or a code editor like VSCode, run the following command:
git clone https://github.com/machinefi/google-fit-demo.git && cd google-fit-demo && npm i

Click Check my progress to verify the objective. Clone Application Repository

Task 2. Get your NFT ready

  1. After cloning the repository, navigate to the nft directory within the main directory and install the Node.js dependencies:
cd nft && npm install
  1. Inside the /assets subdirectory, you will find the images for an SBT (Soul Bound Token) and a Rewards NFTs.
  2. Run the following to create a new file named .env in the nft folder and add your developer wallet PRIVATE_KEY to this file:
echo PRIVATE_KEY=93be2d4e.....9c71bad > .env

The provided account will be used to pay Bundlr fees with testnet tokens. Bundlr is a decentralized storage solution that you will use to store the NFT images. The Bundlr fee can be paid using MATIC tokens, so make sure that the corresponding account has some test MATIC tokens by using one of the following links: https://testmatic.vercel.app/ or https://mumbaifaucet.com (make sure you select the MUMBAI Testnet).

  1. Add MUMBAI Testnet network in Metamask wallet.
Network Name Mumbai Testnet
New RPC URL https://rpc-mumbai.maticvigil.com
Chain ID 80001
Currency Symbol MATIC
Block Explorer URL https://polygonscan.com/
  1. Run the following command to upload your images and metadata
npm run upload

You'll find the generated NFT URI's nft/uploads.json which you'll need in the next step.

Task 3. Deploying the NFT Smart Contracts

  1. Navigate to the blockchain directory:
cd ~/google-fit-demo/blockchain
  1. In the blockchain directory, create an .env file, then populate the file with the contents of .env.template:
cp .env.template .env
  1. Edit the file and provide your developer PRIVATE_KEY that will be used for contract deployment and access control tasks.
  2. Update the SBT_URI and REWARDS_URI variables in the .env file as per your set up with the values generated in the Get your NFT ready step. Find them in the nft/uploads.json file.
  3. The OPERATOR_ADDRESS will be obtained in the next step.
  4. Compile the contracts and generate contract types by running:
npm run compile
  1. Ensure everything is working by running a test:
npm run test
  1. Deploy your contracts by executing:
npm run deploy:testnet Note: If you receive any errors related to Node.js, make sure that all packages are installed properly which can be done using npm i command in the directory.

Click Check my progress to verify the objective. Add developer wallet Private Key

Task 4. Deploy the W3bstream applet

  1. Navigate to the applet directory:
cd ~/google-fit-demo/applet
  1. From the applet directory, compile the AssemblyScript code by running:
npm run asbuild
  1. Point your browser to W3bstream Studio, log in with your Metamask developer wallet, and click the Import a Project button.
Note: Before proceeding to the next step, make sure to connect with your wallet.
  1. Use applet/wsproject.json as the Project file and applet/build/release.wasm as the Wasm file.
  2. In the Events tab in W3bstream Studio, select the Smart Contract Monitor tab and click the Start button for each Smart Contract Monitor:
Events tab
  1. Find your W3bstream Operator_Address in your project's Settings, and use Metamask to transfer some IOTX test tokens from your Developer Address to the Operator Address (1 IOTX should suffice):
operator address Note: If you receive any errors related to Nodejs please make sure that all packages are installed properly and can be done using npm i command in the directory.

Click Check my progress to verify the objective. Deploy W3bstream Applet

Task 5. Granting minting permission to W3bstream applet

  1. Return to the blockchain directory and update the Operator_Address from the previous step in your .env file with your W3bstream Operator Address.
  2. Runthe following command to assign the SBT minter role to the W3bstream operator:
npx hardhat grant-sbt-minter --network testnet
  1. Run this command to assign the Rewards NFT minter role to the W3bstream operator:
npx hardhat grant-rewards-minter --network testnet Note: If you receive any errors related to Node.js please make sure that all packages are installed properly and can be done using npm i command in the directory.

Task 6. Configure the Web App

  1. Navigate to the adapter directory:
cd ~/google-fit-demo/adapter
  1. Create a new file named .env.local and populate it with the variables from .env.template.
  2. Navigate to Account settings in the studio interface to create an API key with Publisher and Event read and write permissions and update API_TOKEN.

Account Settings.

  1. NEXTAUTH_SECRET can be acquired in Vercel secret generator
  2. Find the HTTP_ROUTE under Event Sources:
http route under Event Sources
  1. Use the GOOGLE_ID and GOOGLE_SECRET you obtained in the earlier step.
Note: Make sure to update the .env.local with required values.
  1. For the NEXTAUTH_URL use http://localhost:3000 as address.

Click Check my progress to verify the objective. Configure the Web App

Task 7. Add the yoga session (with OAuth Playground)

  1. Follow OAuth Playground steps to submit yoga session to your lab user account.
  2. Use your lab user credentials provided for this lab
  3. Select the following scope for Fitness API v1: https://www.googleapis.com/auth/fitness.activity.write
  4. Use PUT method with the following url: https://www.googleapis.com/fitness/v1/users/me/sessions/1
  5. Click on Enter request body and use the request body below to submit a test yoga session:
{ "id": "1", "name": "Yoga", "description": "", "startTimeMillis": "1692673200000", "endTimeMillis": "1692676800000", "modifiedTimeMillis": "1692676800000", "activityType": 100 }
  1. Click the Send the request button to submit the request

Click Check my progress to verify the objective. Add the Yoga Session (with OAuth Playground)

Task 8. OPTIONAL: Add the yoga session (with Google Fit App)

  1. Make sure you installed the Google Fit App on your mobile device.
  2. Log in to Google Fit with the lab user credentials.
  3. Add at least one 30 min yoga session in your Google Fit App.
plus icon and add activity icon for Google Fit app

Task 9. Run and test the Web App

  1. Start the web app by running the following command:
npm run dev
  1. Navigate to localhost:3000 in your web browser.

  2. Follow the on-screen instructions to register your device and claim your SBT:

your device screen
  1. Sync the Google Fit data and then recheck the studio db and proceed to claim your rewards.
  2. Import the NFTs into Metamask. The NFT contract addresses are present in Environment variables within the Settings tab. The SBT ID is "device id" and the Rewards token ID is "1".

Click Check my progress to verify the objective. Run and Test the Web App

At this point you have successfully completed the lab. If you would like to dig deeper into the most relevant code sections, proceed with Task 10.

Task 10. Code highlights (optional)

Some of the more relevant components of your application:

  1. Auth with google with applied activity scopes

You start with the user authentication handled by the NextAuth library in auth/[...nextauth].ts, where you establish secure user sessions using Google as the identity provider. This forms the basis for secure interactions throughout the application.

// adapter/pages/api/auth/[...nextauth].ts import NextAuth from "next-auth"; import type { NextAuthOptions } from "next-auth"; import GoogleProvider from "next-auth/providers/google"; export const authOptions: NextAuthOptions = { providers: [ GoogleProvider({ clientId: process.env.GOOGLE_ID || "", clientSecret: process.env.GOOGLE_SECRET || "", authorization: { params: { scope: "openid email profile https://www.googleapis.com/auth/fitness.activity.read", }, }, }), ], callbacks: { async jwt({ token, account }) { if (account) { token.accessToken = account.access_token; } return token; }, async session({ session, token }) { session.accessToken = token.accessToken as string; return session; }, }, }; export default NextAuth(authOptions);

This file sets up Google OAuth authentication for a Next.js application using the NextAuth.js library. The key components of the setup are the providers and callbacks within the authOptions object. The providers array contains the Google provider, configured with necessary details such as clientId, clientSecret, and the scope of authorization required from Google's API, which in this case includes basic profile information and read access to Google Fit data.

The callbacks object contains two methods: jwt and session. The jwt callback intercepts the JSON Web Token (JWT) after authentication, and if an account is authenticated, it assigns the access token from the account to the token object. The session callback is called whenever a session is accessed, and it adds the access token to the session object.

Finally, the authOptions object is passed to the NextAuth function and exported as the default from this module. This setup process allows the Next.js application to handle user authentication via Google, and have access to the user's access token in both the JWT and the session for further authenticated requests to Google's APIs.

  1. Device registration

Following this, you have the device registration process in register/route.ts, which registers the user's device on a blockchain network for unique identification, further enhancing our application's security.

// adapter/app/api/register/route.ts import "server-only"; import { getContract } from "viem"; import * as deployments from "../../contracts/deployments.json"; import { walletClient, publicClient } from "./client"; const registryConfig = (deployments as any)[4690][0].contracts.DeviceRegistry; export const registryContract = getContract({ address: registryConfig.address as `0x${string}`, abi: registryConfig.abi, }); export async function registerDevice(deviceId: string) { if (await isRegistered(deviceId)) { return { transactionHash: "already registered" }; } const { request } = await publicClient.simulateContract({ account: walletClient.account, address: registryConfig.address as `0x${string}`, abi: registryConfig.abi, functionName: "registerDevice", args: [deviceId], }); const hash = await walletClient.writeContract(request); return publicClient.waitForTransactionReceipt({ hash, confirmations: 1 }); } async function isRegistered(deviceId: string) { try { const isAuthorized = await publicClient.readContract({ address: registryConfig.address as `0x${string}`, abi: registryConfig.abi, functionName: "isAuthorizedDevice", args: [deviceId], }) return isAuthorized; } catch (e) { return false; } }

This file is for registering devices on a blockchain using the DeviceRegistry contract. It imports necessary modules and contract details from a deployments file, and creates a representation of the contract using the getContract function.

The registerDevice function handles the device registration process. It first checks if a device is already registered. If not, it simulates a contract operation to register the device, writes this transaction to the blockchain using walletClient.writeContract, and waits for the transaction to be confirmed.

The isRegistered function checks if a device is already registered by reading from the DeviceRegistry contract. If any errors occur during this process, it logs the error and returns false.

  1. Fetch fitness data from Google and relay it to W3bstream

With the foundation laid, you look at pull-data/route.ts, where the application interacts with Google's Fit API, pulling and processing fitness data for a specific device. The processed data is then uploaded to a W3bstream, marking a key step in our application's data flow.

// adapter/app/api/pull-data/route.ts import { NextRequest, NextResponse } from "next/server"; import { getToken } from "next-auth/jwt"; import { fetchFitSessions } from "@/features/fitness-data/services/google/get-fit-session"; import { FitSession, FitSessionRaw } from "@/app/types"; import { uploadFitSessionToWS } from "@/features/fitness-data/services/w3bstream/client"; const YOGA_ACTIVITY_TYPE = 100; const DEFAULT_FETCH_DAYS = 7; const DEFAULT_TIME_INCREMENT_MS = 1; export async function POST(req: NextRequest) { const { deviceId } = await req.json(); const token = await getToken({ req }); await processDevice(token.accessToken as string, deviceId); return NextResponse.json({ success: true }); } async function processDevice(accessToken: string, deviceId: string) { const lastFetchDate = await getLastFetchDate(deviceId); const rawData = await fetchFitSessions( accessToken, lastFetchDate, YOGA_ACTIVITY_TYPE ); const processedData = processData(rawData); await uploadFitSessionToWS(deviceId, processedData); await updateLastFetchDate(deviceId, rawData); } async function getLastFetchDate(deviceId: string): Promise {...} function processData(data: FitSessionRaw[]): FitSession[] { const validDataPoints = data.filter((d: FitSessionRaw) => { return !!d.startTimeMillis && !!d.endTimeMillis; }); return validDataPoints.map((d: FitSessionRaw) => { const { id, startTimeMillis, endTimeMillis } = d; return { id, startTimeMillis, endTimeMillis, }; }); } async function updateLastFetchDate(deviceId: string, data: FitSessionRaw[]) {...} // adapter/features/fitness-data/services/google/get-fit-session.ts import "server-only"; import { googleAxiosInstance } from "./client"; import { FitSessionRaw } from "@/app/types"; export async function fetchFitSessions( token: string, startTimestamp: string, activityType: number ): Promise { const res = await googleAxiosInstance(token).get( `/sessions?startTime=${startTimestamp}&activityType=${activityType}` ); return res.data.session; } // adapter/features/fitness-data/services/google/client.ts import axios from "axios"; import { GOOGLE_FIT_BASE_URL } from "@/app/config"; export const googleAxiosInstance = (token: string) => axios.create({ baseURL: GOOGLE_FIT_BASE_URL, headers: { Accept: "application/json", Authorization: `Bearer ${token}`, }, });

This file processes fitness data from Google Fit for a specific device. The POST function extracts a deviceId from the request, obtains an access token, and processes the device's fitness data. The processDevice function fetches the fitness sessions from Google Fit, processes the data to extract valid points, and uploads the data to W3bstream. The fetchFitSessions function makes a GET request to Google Fit's endpoint to fetch fitness data, and googleAxiosInstance returns an Axios instance configured for Google Fit's API.

  1. Send data to W3bstream

Next, you delve into client.ts under services/w3bstream, which manages the actual data upload to the W3bstream, enabling the application to persist and retrieve fitness data.

// adapter/features/fitness-data/services/w3bstream/client.ts import "server-only"; import { HTTP_ROUTE, DEVICE_TOKEN } from "@/app/config"; import { FitSession } from "@/app/types"; const myHeaders = new Headers(); myHeaders.append("Content-Type", "application/json"); myHeaders.append("Authorization", `Bearer ${DEVICE_TOKEN}`); const requestOptions: any = { method: "POST", headers: myHeaders, }; const FIT_DATA_EVENT_TYPE = "FIT_DATA"; const ANALYZE_FIT_DATA_EVENT_TYPE = "ANALYZE_FIT_DATA"; export async function uploadFitSessionToWS( deviceId: string, data: FitSession[] ) { if (data.length === 0) { return; } requestOptions.body = JSON.stringify({ deviceId, data, }); await sendRequest( HTTP_ROUTE.trim() + `?eventType=${FIT_DATA_EVENT_TYPE}`, requestOptions ); } export async function triggerEvaluation() {...} async function sendRequest(route: string, options: any) { try { await fetch(route, options); } catch (e) { console.log(e); throw e; } }

This TypeScript file manages the upload of fitness data to W3bstream. The uploadFitSessionToWS function checks if there's any fitness data to send, and if so, it prepares a POST request with the device ID and the data, and sends it to a specified HTTP route. The sendRequest function is used to execute the fetch request, and if any errors occur during the process, they are logged and re-thrown.

  1. Collect rewards

Finally, you conclude with the frontend code in CollectButton.tsx, a user-facing React component. It interacts with a rewards contract on the blockchain, allowing users to collect rewards for their fitness activities, tying the whole application together in a user-centric manner.

// adapter/app/rewards/CollectButton.tsx "use client"; import { useContractWrite, usePrepareContractWrite, useWaitForTransaction, } from "wagmi"; import { rewardsContract } from "@/features/web3/services/viem/nft"; export const CollectButton = ({ tokenId }: { tokenId: number }) => { const { config } = usePrepareContractWrite({ address: rewardsContract.address, abi: rewardsContract.abi, functionName: "mintFromAllowance", args: [tokenId, []], }); const { data, isLoading, isSuccess, write } = useContractWrite(config); const { isLoading: isWaiting } = useWaitForTransaction({ hash: data?.hash, confirmations: 1, }); return (
{isLoading &&
Check Wallet
} {isWaiting &&
Collecting...
} {isSuccess &&
Transaction: {JSON.stringify(data)}
}
); };

This component is used for interacting with a rewards contract on a blockchain. It uses hooks from the wagmi library to prepare and execute a contract write operation, and to wait for the transaction's confirmation.

The usePrepareContractWrite hook prepares the contract write operation to call the mintFromAllowance function of the rewards contract with the provided tokenId as an argument.

The useContractWrite hook executes the contract write operation when the write function is called, which is triggered by a click event on the button.

The useWaitForTransaction hook waits for the transaction's confirmation.

The button is disabled while the write operation is being prepared, is in progress, or while waiting for the transaction confirmation. Feedback is given to the user through the text displayed below the button, indicating whether the transaction is being prepared, is in progress, or has been successful. The transaction data is displayed when the transaction has been successful.

Together, these components form the core of our application, managing everything from user authentication, device registration, data retrieval and processing, to enabling users to receive on-chain rewards for their fitness activities.

Congratulations!

You've successfully navigated through a complex landscape of code, learned to integrate the Google Fit API, gamify decentralized applications based on real-world behaviors, set up a W3bstream project, and mint crypto assets.

These skills are not just isolated techniques. They form a holistic approach to building modern, user-centric, and engaging applications. You have learned how to pull real-world data from Google Fit, process it, and use it within a decentralized application to promote healthy behaviors. You've also set up a W3bstream project and used it as a platform for storing and managing fitness data.

Next steps

Were you intrigued by W3bstream and how to use it to build decentralized applications on real world data? Remember that integrating with a cloud solution is only one of the multiple ways of building real world based applications with W3bstream. These other tutorials will help you keep up your progress:

Google Cloud Training & Certification

...helps you make the most of Google Cloud technologies. Our classes include technical skills and best practices to help you get up to speed quickly and continue your learning journey. We offer fundamental to advanced level training, with on-demand, live, and virtual options to suit your busy schedule. Certifications help you validate and prove your skill and expertise in Google Cloud technologies.

Manual Last Updated Date: November 14, 2023

Lab Last Tested Date: September 20, 2023

Copyright 2022 Google LLC All rights reserved. Google and the Google logo are trademarks of Google LLC. All other company and product names may be trademarks of the respective companies with which they are associated.