vivanta_connect_flutter 0.9.2 vivanta_connect_flutter: ^0.9.2 copied to clipboard
Vivanta Connect for Flutter
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
import 'package:vivanta_connect_example/helpers.dart';
import 'package:vivanta_connect_example/vivanta.dart';
import 'package:vivanta_connect_example/vivanta_sync_task_handler.dart';
import 'package:vivanta_connect_example/write_to_health.dart';
import 'package:vivanta_connect_flutter/helpers/vivanta_sync.dart';
import 'package:vivanta_connect_flutter/services/preferences.dart';
import 'package:vivanta_connect_flutter/styles/colors.dart';
import 'package:vivanta_connect_flutter/styles/fonts.dart';
import 'package:vivanta_connect_flutter/views/embedded_graph.dart';
// The callback function should always be a top-level function.
@pragma('vm:entry-point')
void startCallback() {
// The setTaskHandler function must be called to handle the task in the background.
FlutterForegroundTask.setTaskHandler(VivantaSyncTaskHandler());
}
void main() => runApp(const Example());
class Example extends StatelessWidget {
const Example({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Vivanta Connect Example',
home: Home(),
);
}
}
class Home extends StatefulWidget {
Home({Key? key}) : super(key: key);
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
String companyId = 'Vivanta';
final Preferences preferences = Preferences();
late TextEditingController _usernameController;
late TextEditingController _brandController;
late FocusNode _usernameFocusNode;
late FocusNode _brandFocusNode;
@override
void initState() {
_usernameController = TextEditingController();
_usernameFocusNode = FocusNode();
_brandController = TextEditingController();
_brandFocusNode = FocusNode();
_usernameFocusNode.addListener(() {
if (!_usernameFocusNode.hasFocus) {
preferences.setExternalUserId(_usernameController.text);
}
});
_brandFocusNode.addListener(() {
if (!_brandFocusNode.hasFocus) {
preferences.setBrandId(_brandController.text);
}
});
initAsync();
super.initState();
_requestPermissionForAndroid();
}
Future<void> _requestPermissionForAndroid() async {
if (!Platform.isAndroid) {
return;
}
// "android.permission.SYSTEM_ALERT_WINDOW" permission must be granted for
// onNotificationPressed function to be called.
//
// When the notification is pressed while permission is denied,
// the onNotificationPressed function is not called and the app opens.
//
// If you do not use the onNotificationPressed or launchApp function,
// you do not need to write this code.
if (!await FlutterForegroundTask.canDrawOverlays) {
// This function requires `android.permission.SYSTEM_ALERT_WINDOW` permission.
await FlutterForegroundTask.openSystemAlertWindowSettings();
}
// Android 12 or higher, there are restrictions on starting a foreground service.
//
// To restart the service on device reboot or unexpected problem, you need to allow below permission.
if (!await FlutterForegroundTask.isIgnoringBatteryOptimizations) {
// This function requires `android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` permission.
await FlutterForegroundTask.requestIgnoreBatteryOptimization();
}
// Android 13 and higher, you need to allow notification permission to expose foreground service notification.
final NotificationPermission notificationPermissionStatus =
await FlutterForegroundTask.checkNotificationPermission();
if (notificationPermissionStatus != NotificationPermission.granted) {
await FlutterForegroundTask.requestNotificationPermission();
}
}
void _onData(dynamic data) {
if (data is int) {
} else if (data is String) {
if (data == 'onNotificationPressed') {
Navigator.of(context).pushReplacementNamed('/');
}
}
}
@override
void dispose() {
_usernameController.dispose();
_usernameFocusNode.dispose();
_brandController.dispose();
_brandFocusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return WillStartForegroundTask(
onWillStart: () async {
// Return whether to start the foreground service.
return true;
},
androidNotificationOptions: AndroidNotificationOptions(
channelId: 'vivanta_connect_demo_foreground_service',
channelName: 'Vivanta Connect Demo Foreground Service',
channelDescription:
'This notification is used to keep Vivanta Connect running.',
channelImportance: NotificationChannelImportance.DEFAULT,
priority: NotificationPriority.MIN,
isSticky: true, // important
iconData: const NotificationIconData(
resType: ResourceType.mipmap,
resPrefix: ResourcePrefix.ic,
name: 'launcher',
),
buttons: [],
),
iosNotificationOptions: const IOSNotificationOptions(
showNotification: false,
playSound: false,
),
foregroundTaskOptions: const ForegroundTaskOptions(
interval: 300000,
isOnceEvent: false,
allowWakeLock: false,
allowWifiLock: false,
),
notificationTitle: 'Vivanta Connect is running',
notificationText:
'Please don\'t close this notification to keep Vivanta Connect running.',
callback: startCallback,
onData: _onData,
child: Scaffold(
appBar: AppBar(
title: Text('Vivanta Connect Example'),
),
body: Container(
width: MediaQuery.of(context).size.width,
child: ListView(
children: [
_box(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
'External User ID:',
textAlign: TextAlign.start,
),
),
TextField(
keyboardType: TextInputType.text,
controller: _usernameController,
focusNode: _usernameFocusNode,
onEditingComplete: () {
FocusScope.of(context).unfocus();
},
),
],
),
),
_box(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
'Brand ID:',
textAlign: TextAlign.start,
),
),
TextField(
keyboardType: TextInputType.text,
controller: _brandController,
focusNode: _brandFocusNode,
onEditingComplete: () {
FocusScope.of(context).unfocus();
},
),
],
),
),
_box(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
TextButton(
onPressed: _usernameController.text.isEmpty
? null
: _launchVivantaConnect,
style: _buttonStyle(),
child: Text(
'Connect your device with Vivanta',
textAlign: TextAlign.center,
style: VivantaFonts.button,
),
),
if (Platform.isIOS) ...[
_box(),
TextButton(
onPressed: _usernameController.text.isEmpty
? null
: _launchVivantaSync,
style: _buttonStyle(),
child: Text(
'Sync Apple Health Data',
textAlign: TextAlign.center,
style: VivantaFonts.button,
),
),
_box(),
TextButton(
onPressed: () => writeAppleHealthParameter(context),
style: _buttonStyle(),
child: Text(
'Write Apple Health parameter',
textAlign: TextAlign.center,
style: VivantaFonts.button,
),
),
_box(),
TextButton(
onPressed: () => writeAppleHealthActivity(context),
style: _buttonStyle(),
child: Text(
'Write Apple Health activity',
textAlign: TextAlign.center,
style: VivantaFonts.button,
),
),
],
if (Platform.isAndroid) ...[
_box(),
TextButton(
onPressed: _usernameController.text.isEmpty
? null
: _launchVivantaSync,
style: _buttonStyle(),
child: Text(
'Sync Google Health Connect Data',
textAlign: TextAlign.center,
style: VivantaFonts.button,
),
),
_box(),
TextButton(
onPressed: () => writeGoogleHealthParameter(context),
style: _buttonStyle(),
child: Text(
'Write Google Health Connect parameter',
textAlign: TextAlign.center,
style: VivantaFonts.button,
),
),
_box(),
TextButton(
onPressed: () => writeGoogleHealthActivity(context),
style: _buttonStyle(),
child: Text(
'Write Google Health Connect activity',
textAlign: TextAlign.center,
style: VivantaFonts.button,
),
),
],
_box(),
_getButtonToGraph(
'Open activity graph',
GraphType.activity,
),
_box(),
_getButtonToGraph(
'Open active time graph',
GraphType.activeTime,
),
_box(),
_getButtonToGraph(
'Open sleep graph',
GraphType.sleep,
),
_box(),
_getButtonToGraph(
'Open calories graph',
GraphType.calories,
),
_box(),
_getButtonToGraph(
'Open AVG heart rate graph',
GraphType.avgHeartRate,
),
],
),
),
],
),
),
),
);
}
Future<void> initAsync() async {
final externalUserId = await preferences.getExternalUserId();
final brandId = await preferences.getBrandId();
if (externalUserId.isNotEmpty) {
_usernameController.text = externalUserId;
setState(() {});
}
if (brandId.isNotEmpty) {
_brandController.text = brandId;
setState(() {});
}
}
ButtonStyle _buttonStyle({
Color backgroundColor = VivantaColors.primary,
}) =>
ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(backgroundColor),
fixedSize: MaterialStateProperty.all<Size>(
Size(MediaQuery.of(context).size.width - 32, 32),
),
);
Widget _box() => SizedBox(height: 24);
void _launchVivantaConnect() {
showModalBottomSheet(
context: context,
builder: (context) {
return Container(
height: 100,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('What type of start do you want to use?'),
SizedBox(
height: 8,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
child: const Text('With API key'),
onPressed: () async {
await launchVivantaConnectWithKeys(
context,
_usernameController.text,
companyId,
);
Navigator.pop(context);
},
),
ElevatedButton(
child: const Text('With token'),
onPressed: () async {
final token =
await getToken(_usernameController.text, companyId);
await launchVivantaConnectWithToken(context, token);
Navigator.pop(context);
},
),
],
),
SizedBox(
height: 8,
),
],
),
),
);
},
);
}
void _launchVivantaSync() {
showModalBottomSheet(
context: context,
builder: (context) {
return Container(
height: 100,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('What type of sync process do you want to use?'),
SizedBox(
height: 8,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
child: const Text('With API key'),
onPressed: () async {
await _sync(showSuccess: true);
Navigator.pop(context);
},
),
ElevatedButton(
child: const Text('With token'),
onPressed: () async {
final token =
await getToken(_usernameController.text, companyId);
await _syncWithToken(token: token, showSuccess: true);
Navigator.pop(context);
},
),
],
),
SizedBox(
height: 8,
),
],
),
),
);
},
);
}
Widget _getButtonToGraph(String text, GraphType graphType) {
final brandId = int.tryParse(_brandController.text);
return TextButton(
onPressed: _usernameController.text.isEmpty
? null
: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => EmbeddedGraph(
apiKey: Vivanta.apiKey,
customerId: Vivanta.customerId,
externalUserId: _usernameController.text,
graphType: graphType,
brandId: brandId,
),
),
);
},
style: _buttonStyle(
backgroundColor: VivantaColors.accent,
),
child: Text(
text,
textAlign: TextAlign.center,
style: VivantaFonts.button,
),
);
}
Future<void> _sync({
bool showSuccess = true,
}) async {
final sync = VivantaSync(
apiKey: Vivanta.apiKey,
customerId: Vivanta.customerId,
externalUserId: _usernameController.text,
);
sync.executeAll().then((value) {
if (showSuccess) {
showSyncSuccess(context);
}
}).catchError(
(onError) {},
);
}
Future<void> _syncWithToken({
bool showSuccess = true,
String token = '',
}) async {
final sync = VivantaSync(
token: token,
);
sync.executeAll().then((value) {
if (showSuccess) {
showSyncSuccess(context);
}
}).catchError(
(onError) {},
);
}
}