I'm using the webview_flutter package in my Flutter app to perform Google Fit authentication and retrieve heartbeat data. However, I'm encountering issues with verification because the requests made by the WebView don't comply with Google's policies.
My objective is to continuously fetch the user's heartbeat data after obtaining access. Is there an alternative approach I can use to achieve this, considering the limitations with webview_flutter?
Here's a summary of my current setup:
handle the Google Fit authentication process. Attempting to retrieve heartbeat data continuously after obtaining access. I'd appreciate any suggestions or guidance on how to overcome this issue or alternative methods to achieve the same goal.
frontend
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:url_launcher/url_launcher.dart';
import 'package:webview_flutter/webview_flutter.dart';
class ConnectSmartwatch extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Google Fit API Demo')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => HeartRatePage()),
);
},
child: Text('Get Heart Rate Data'),
),
),
),
);
}
}
class HeartRatePage extends StatefulWidget {
@override
_HeartRatePageState createState() => _HeartRatePageState();
}
class _HeartRatePageState extends State<HeartRatePage> {
String _heartRateData = '';
@override
void initState() {
super.initState();
initiateAuthentication(); // Call this when a specific action is performed
}
void initiateAuthentication() async {
try {
final url = await _getAuthUrl(); // Fetch authentication URL from backend
Navigator.push(
context,
MaterialPageRoute(builder: (context) => WebViewPage(url, exchangeCodeForTokens)),
);
} catch (e) {
print('Failed to initiate authentication: $e');
throw Exception('Failed to initiate authentication: $e');
}
}
Future<String> _getAuthUrl() async {
try {
final response = await http.get(Uri.parse('https://your-backend-url.com/getAuthUrl'));
if (response.statusCode == 200) {
final url = jsonDecode(response.body)['url'];
return url;
} else {
throw Exception('Failed to load auth URL');
}
} catch (e) {
print('Failed to get auth URL: $e');
throw Exception('Failed to fetch authentication URL: $e');
}
}
void exchangeCodeForTokens(String? code) async {
if (code != null) {
final response = await http.get(Uri.parse('https://your-backend-url.com/exchangeCode?code=$code'));
if (response.statusCode == 200) {
final tokens = jsonDecode(response.body);
_getHeartRateData(tokens);
} else {
throw Exception('Failed to exchange code for tokens');
}
}
}
Future<void> _getHeartRateData(Map<String, dynamic> tokens) async {
final String accessToken = tokens['access_token'];
final response = await http.get(Uri.parse('https://www.googleapis.com/fitness/v1/users/me/dataset:aggregate'),
headers: {
'Authorization': 'Bearer $accessToken',
},
);
if (response.statusCode == 200) {
setState(() {
_heartRateData = jsonDecode(response.body);
});
} else {
throw Exception('Failed to load heart rate data');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Heart Rate Data')),
body: Center(
child: Text('Heart Rate Data: $_heartRateData'),
),
);
}
}
class WebViewPage extends StatelessWidget {
final String url;
final Function(String?) onCodeReceived;
WebViewPage(this.url, this.onCodeReceived);
void launchAuthUrlAgain() async {
if (await canLaunch(url)) {
await launch(url);
} else {
print('Could not launch URL');
throw 'Could not launch $url';
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Auth Page')),
body: WebView(
initialUrl: url,
javascriptMode: JavascriptMode.unrestricted,
onPageFinished: (String url) {
if (url.startsWith('https://your-backend-url.com/redirectPage')) {
var uri = Uri.parse(url);
var code = uri.queryParameters['code'];
launchAuthUrlAgain();
onCodeReceived(code);
}
},
),
);
}
}
backend
import queryParse from 'query-string';
import express from 'express';
import { google } from 'googleapis';
import bodyParser from 'body-parser';
import axios from 'axios';
const app = express();
const port = 3000;
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
app.get("/getAuthUrl", async (req, res) => {
try {
// Your authentication logic here
const oauth2Client = new google.auth.OAuth2(
"YOUR_CLIENT_ID",
"YOUR_CLIENT_SECRET",
"YOUR_REDIRECT_URI"
);
const scopes = ["https://www.googleapis.com/auth/fitness.heart_rate.read"];
const url = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: scopes,
redirect_uri: "YOUR_REDIRECT_URI",
state: JSON.stringify({
callbackURL: req.body.callbackURL,
userID: req.body.userid
})
});
res.send({url});
} catch (e) {
console.log('Failed to generate auth URL:', e);
res.status(500).send("Internal Server Error");
}
});
app.get("/exchangeCode", async (req, res) => {
try {
const queryURL = new urlParse(req.url);
const parsedQuery = queryParse.parse(queryURL.query);
if (!parsedQuery.code) {
console.log('No code in query parameters');
return res.status(400).send('Bad Request: No code in query parameters');
}
const code = parsedQuery.code;
const oauth2Client = new google.auth.OAuth2(
"YOUR_CLIENT_ID",
"YOUR_CLIENT_SECRET",
"YOUR_REDIRECT_URI"
);
const tokens = await oauth2Client.getToken(code);
res.send(tokens);
} catch (e) {
console.log('Failed to exchange code for tokens:', e);
res.status(500).send("Internal Server Error");
}
});
app.listen(port, () => console.log(`Server is listening on port ${port}!`));