vivanta_connect_flutter 0.9.0 copy "vivanta_connect_flutter: ^0.9.0" to clipboard
vivanta_connect_flutter: ^0.9.0 copied to clipboard

Vivanta Connect for Flutter

example/lib/main.dart

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,
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  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) {},
    );
  }
}