Using Google fit API in Flutter - android

I need my app to read step count from Google Fit. I'm using health 3.05 package. For now I copied the example code to see if it works and unfortunately it's not. Of course I did every step from this packge readme. I set up OAuth2 Client ID, I changed gradle.properties as they shown and in AndroidManifest.xml I put <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/> . However after running app I don't get any permission window and when I click the button to get data I got an error "Authorization not granted" in console. What should I do? Thanks
Here is my code that I copied form package example:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:health/health.dart';
class DailyStepsScreen extends StatefulWidget {
#override
_DailyStepsScreenState createState() => _DailyStepsScreenState();
}
enum AppState {
DATA_NOT_FETCHED,
FETCHING_DATA,
DATA_READY,
NO_DATA,
AUTH_NOT_GRANTED
}
class _DailyStepsScreenState extends State<DailyStepsScreen> {
List<HealthDataPoint> _healthDataList = [];
AppState _state = AppState.DATA_NOT_FETCHED;
#override
void initState() {
super.initState();
}
Future<void> fetchData() async {
/// Get everything from midnight until now
DateTime startDate = DateTime(2020, 11, 07, 0, 0, 0);
DateTime endDate = DateTime(2025, 11, 07, 23, 59, 59);
HealthFactory health = HealthFactory();
/// Define the types to get.
List<HealthDataType> types = [
HealthDataType.STEPS,
HealthDataType.WEIGHT,
HealthDataType.HEIGHT,
HealthDataType.BLOOD_GLUCOSE,
HealthDataType.DISTANCE_WALKING_RUNNING,
];
setState(() => _state = AppState.FETCHING_DATA);
/// You MUST request access to the data types before reading them
bool accessWasGranted = await health.requestAuthorization(types);
int steps = 0;
if (accessWasGranted) {
try {
/// Fetch new data
List<HealthDataPoint> healthData =
await health.getHealthDataFromTypes(startDate, endDate, types);
/// Save all the new data points
_healthDataList.addAll(healthData);
} catch (e) {
print("Caught exception in getHealthDataFromTypes: $e");
}
/// Filter out duplicates
_healthDataList = HealthFactory.removeDuplicates(_healthDataList);
/// Print the results
_healthDataList.forEach((x) {
print("Data point: $x");
steps += x.value.round();
});
print("Steps: $steps");
/// Update the UI to display the results
setState(() {
_state =
_healthDataList.isEmpty ? AppState.NO_DATA : AppState.DATA_READY;
});
} else {
print("Authorization not granted");
setState(() => _state = AppState.DATA_NOT_FETCHED);
}
}
Widget _contentFetchingData() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: EdgeInsets.all(20),
child: CircularProgressIndicator(
strokeWidth: 10,
)),
Text('Fetching data...')
],
);
}
Widget _contentDataReady() {
return ListView.builder(
itemCount: _healthDataList.length,
itemBuilder: (_, index) {
HealthDataPoint p = _healthDataList[index];
return ListTile(
title: Text("${p.typeString}: ${p.value}"),
trailing: Text('${p.unitString}'),
subtitle: Text('${p.dateFrom} - ${p.dateTo}'),
);
});
}
Widget _contentNoData() {
return Text('No Data to show');
}
Widget _contentNotFetched() {
return Text('Press the download button to fetch data');
}
Widget _authorizationNotGranted() {
return Text('''Authorization not given.
For Android please check your OAUTH2 client ID is correct in Google Developer Console.
For iOS check your permissions in Apple Health.''');
}
Widget _content() {
if (_state == AppState.DATA_READY)
return _contentDataReady();
else if (_state == AppState.NO_DATA)
return _contentNoData();
else if (_state == AppState.FETCHING_DATA)
return _contentFetchingData();
else if (_state == AppState.AUTH_NOT_GRANTED)
return _authorizationNotGranted();
return _contentNotFetched();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.file_download),
onPressed: () {
fetchData();
},
)
],
),
body: Center(
child: _content(),
)
);
}
}

Step 1 use health: 3.0.4
Step 2 do proper set up for OAuth2 Client ID, download new google-service.json
Step 3 From Android 10. you have to add ACTIVITY_RECOGNITION for getting STEP Count permission in AndroidManifest.xml.
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
Step 4 And then using permission_handler ask for permission.
if (Platform.isAndroid) {
final permissionStatus = Permission.activityRecognition.request();
if (await permissionStatus.isDenied ||
await permissionStatus.isPermanentlyDenied) {
showToast(
'activityRecognition permission required to fetch your steps count');
return;
}
}

FINALLY GOT THIS ISSUE SOLVED!!
So the problem doesn't lie in the version I am using 3.4.0 but still got the problem solved
Authorization not granted. And stuck in loading screen
Stuck in authorization request screen
When you create your OAuth 2.0 consent screen try to add at least 2 email addresses to the TEST USER section and make sure to login from that emails.
Add 2 email addresses in Test User
After that make sure to verify your application from Google, it will work until you test your app once you release the application, it will not work
Verify Your Application from Google
Final Result

Step 1 use health: 3.0.4
Step 2 Add permission function
Step 3 Start your function inside of initstate

Related

Flutter: How to change state of the widget when a certain function has been called?

How it looks like
I've a got bluetooth in my flutter app based on flutter_blue package (^0.8.0). I can connect with my external device and exchange the data. To read data from bluetooth following function is being called when data arrived :
void parseBleMsg(List<int> data) {
print("Data1: $data");
/* Parse the income message */
msgID = data[0];
luxValue = data[1];
print("lux: $luxValue");
print("msgid: $msgID");
}
The parseBleMsg() callback is being set by using specific flutter_blue package methods like setNotifyValue() and .value.listen() :
late BluetoothCharacteristic colsRX;
void setNotifyRX() async {
await colsRX.setNotifyValue(true);
subscription = colsRX.value.listen(
(event) {
parseBleMsg(event);
},
);
}
In one of my app pages I have got a multiple number of buttons which are a custom statefull widgets :
class MeasurementPoint extends StatefulWidget {
final int id;
final double leftPos;
final double topPos;
const MeasurementPoint(
{required this.id, required this.leftPos, required this.topPos});
#override
State<MeasurementPoint> createState() => _MeasurementPointState();
}
class _MeasurementPointState extends State<MeasurementPoint> {
bool pointState = false;
#override
Widget build(BuildContext context) {
return Positioned(
left: widget.leftPos,
top: widget.topPos,
child: ClipOval(
child: Material(
color: pointState ? Colors.green : Styles.primaryColor,
child: InkWell(
onTap: () async {
Bluetooth().bleWrite();
lastClicked = widget.id;
/* TODO : AWAIT FOR BLUETOOTH RESPONSE AND THEN CHANGE STATE */
// setState(() {
// pointState = !pointState;
// });
},
child: const SizedBox(
width: 25, height: 25, child: Icon(Icons.sensors)),
),
),
),
);
}
}
What I want to achieve
As you can see, there is the "TODO" In onTap method. Inside onTap() method I want to send the data through bluetooth to my external device (it works fine) and after that I want to await for the response and then rebuild the widget, to simply change the color of the button as an indicator that the response frame has been received.
The problem I have is that I have no idea, how to await in onTap(), or how in other way rebuilt that widget with new color when I will receive the response from bluetooth.

Flutter: Adding App Update Dialog for iOS and Android

I am currently working on Notification Feature so when a new Update is availible the User gets a Dialog where he can choose to Update or not. I'm doing it with Firebase Remote Config where i have a Parameter called "force_update_current_version" where i then add the Value for the Version for checking. But I do get following errors.
Thanks for your help and i wish you a healty start into the new Year.
Main.dart Code
import 'checkUpdate.dart';
#override
void initState() {
try {
versionCheck(**context**);
} catch (e) {
print(e);
}
**super**.initState();
}
context error: Undefined name 'context'.
Try correcting the name to one that is defined, or defining the name.
super error: Invalid context for 'super' invocation.
checkUpdate.dart Code
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:firebase_remote_config/firebase_remote_config.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:package_info/package_info.dart';
import 'package:flutter/cupertino.dart';
const APP_STORE_URL = 'https://apps.apple.com/us/app/appname/idAPP-ID';
const PLAY_STORE_URL =
'https://play.google.com/store/apps/details?id=APP-ID';
versionCheck(context) async {
//Get Current installed version of app
final PackageInfo info = await PackageInfo.fromPlatform();
double currentVersion = double.parse(info.version.trim().replaceAll(".", ""));
//Get Latest version info from firebase config
final RemoteConfig remoteConfig = await RemoteConfig.instance;
try {
// Using default duration to force fetching from remote server.
await remoteConfig.fetch(expiration: const Duration(seconds: 0));
await remoteConfig.activateFetched();
remoteConfig.getString('force_update_current_version');
double newVersion = double.parse(remoteConfig
.getString('force_update_current_version')
.trim()
.replaceAll(".", ""));
if (newVersion > currentVersion) {
_showVersionDialog(context);
}
} on FetchThrottledException catch (exception) {
// Fetch throttled.
print(exception);
} catch (exception) {
print('Unable to fetch remote config. Cached or default values will be '
'used');
}
}
//Show Dialog to force user to update
_showVersionDialog(context) async {
await showDialog<String>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
String title = "New Update Available";
String message =
"There is a newer version of app available please update it now.";
String btnLabel = "Update Now";
String btnLabelCancel = "Later";
return Platform.isIOS
? new CupertinoAlertDialog(
title: Text(title),
content: Text(message),
actions: <Widget>[
FlatButton(
child: Text(btnLabel),
onPressed: () => _launchURL(**Config**.APP_STORE_URL),
),
FlatButton(
child: Text(btnLabelCancel),
onPressed: () => Navigator.pop(context),
),
],
)
: new AlertDialog(
title: Text(title),
content: Text(message),
actions: <Widget>[
FlatButton(
child: Text(btnLabel),
onPressed: () => _launchURL(**Config**.PLAY_STORE_URL),
),
FlatButton(
child: Text(btnLabelCancel),
onPressed: () => Navigator.pop(context),
),
],
);
},
);
}
_launchURL(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
Config Error for App and Play Store: Undefined name 'Config'.
Try correcting the name to one that is defined, or defining the name.
In checkUpdate.dart we need to import the firebase_remote_config package that exposes the RemoteConfig class:
import 'package:firebase_remote_config/firebase_remote_config.dart';
Make sure to install it before.
The versionCheck() function shall be invoked from a StatefulWidget, hence, a good place to call it would be inside the first screen Widget, for example:
class FirstScreen extends StatefulWidget {
const FirstScreen({ Key key }) : super(key: key);
#override
_FirstScreenState createState() => _FirstScreenState();
}
class _FirstScreenState extends State<FirstScreen> {
#override
void initState() {
super.initState();
WidgetsBinding.instance
.addPostFrameCallback((_) => versionCheck(context));
}
#override
Widget build(BuildContext context) {
return Container(color: const Color(0xFFFFE306));
}
}

How to show a message before 'Usage data access' opens for user to grant permission in flutter

I am using the package "app_usage" to get user data in my app. It however directly opens the usage page without any context. This is as written in the package
as can be seen in the photo.
Now how do I go about showing a screen where I ask the user to grant this permission? it is a protected permission so the user needs to explicitly consent but I would like the user to see something on my app before being taken to settings. Any help would be much appreciated!
You can display a dialog box that alerts the user:
void _showConfirmation() {
showDialog(
context: context,
builder: (_) => new AlertDialog(
title: new Text("App usage"),
content: new Text("We're getting information about your app usage. If you didn't grant the permission yet, you'll be redirected to the settings page"),
actions: <Widget>[
FlatButton(
child: Text('Ok'),
onPressed: () {
_showConfirmation();
Navigator.of(context).pop();
},
)
],
));
}
void getUsageStats() async {
try {
DateTime startDate = DateTime(2018, 01, 01);
DateTime endDate = new DateTime.now();
List<AppUsageInfo> infos = await AppUsage.getAppUsage(startDate, endDate);
setState(() {
_infos = infos;
});
} on AppUsageException catch (exception) {
print(exception);
}
}
You can execute the _showConfirmation() function immediately after the layout has been loaded:
#override
void initState() {
WidgetsBinding.instance
.addPostFrameCallback((_) => _showConfirmation());
}

flutter app, list retrived from firestore duplicate it self

I have a function that is supposed to fetch me a list of Restaurants objects from firestore based on location.
the function does its job perfectly when i first run the app but after using the app from another device and updating resturants data in firestore documents, i somehow get duplicates of the restaurants list items.
here is the code for the function that fetch the the restaurants objects list:
Future<void> fetchRestaurantsList() async {
try {
Position position = await Geolocator().getCurrentPosition(
desiredAccuracy:
Platform.isIOS ? LocationAccuracy.lowest : LocationAccuracy.high);
final dbRestaurant = firestore
.collection('testing')
.document('users')
.collection('restaurant');
geo.collection(collectionRef: dbRestaurant)
.within(
center: GeoFirePoint(
position.latitude,
position.longitude
),
radius: 45.0,
field: 'resturantLocation')
.listen((event) {
restaurantList.clear();
await event.forEach((element){
final distance = Distance.getDistanceFromLatLonInKm( // calculating distance for each restaurant
position.latitude,
position.longitude,
element.data['location']['geopoint'].latitude,
element.data['location']['geopoint'].longitude)
restaurantList.add(Restaurant(
id: element.documentID,
logo: element.data['logo'],
name: element.data['name'],
distance: distance ,
));
notifyListeners();
});
});
} catch (e) {
print(e.toString());
}
} finally {
notifyListeners();
}
}
and this is the page that contains the list: (its under a parent widget which contains other tabs)
class RestruntsListTab extends StatefulWidget {
final MainModel model;
RestruntsListTab({#required this.model});
#override
State<StatefulWidget> createState() {
return _RestruntsListTabState();
}
}
class _RestruntsListTabState extends State<RestruntsListTab>
#override
void initState() {
widget.model.fetchRestaurantsList();
widget.model.checkLocationService().then((isActive) {
if (isActive) {
} else {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(
language.enableLcation,
style: TextStyle(
fontFamily: 'eff', fontSize: 18, fontWeight: FontWeight.bold),
),
backgroundColor: Colors.grey,
));
}
});
super.initState();
}
#override
Widget build(BuildContext context) {
return ScopedModelDescendant<MainModel>(
builder: (context, child, model) {
return ListView.builder(
itemCount:model.restaurantList.length,
itemBuilder: (context,index) {
return Row(
children: <Widget>[
Text(model.restaurantList[index].name),
Text(model.restaurantList[index].distance),
],
)
}
);
})
}
}
this is a simplified code for demonstration but the actual code is pretty similar.
if you have encountered similar issues kindly share your experience.
thank you all.
check that fetchRestaurantsList() method is not called on widget build
or it is in StreamBuilder method...it's because .listen((event) { this method it is like a stream so you have to use flag like bool variable to run the code inside it
if(mybool==false){// the other code goes.... setStste({mybool=true;})}
in this way it only excute the code once
There might be something wrong with the code, but I don't see it. What you can try doing is wrapping the content of forEach with
if(restaurantList.where((item) => item.id == element.documentID).isEmpty){
}
That should filter out duplicates.

How do I pass user input data from page to page in Flutter?

I am writing an app in Flutter and am trying to do a 2 page sign up process. Page 1 is their email, password and repeat password, and Page 2 is additional details about them for their account. This is part of a personal project.
I am trying to pass the data from the first sign up page, to the second. Once the user fills out the second page and presses sign up. The data is then collated and a FirebaseUser is created in Authentication and in the FireStore database.
a) Is this the right way to do it? AKA passing data from one page to the other. Then completing signup then, but if a user exists before completing second page then they have not created an account.
b) Should I instead just be adding information on the second page to the account created on the first? To me this makes sense, but I'm thinking in terms of usability, a user who doesn't complete the full sign up process, likely did not want an account set up for them.
I have tried countless tutorials on passing data from one page to another, however I always get errors relating to invalid constructor names, to const errors, or I go down a rabbit hole of just creating new objects and passing things along.
Signup.dart (Page 1)
try {
await FirebaseAuth.instance.createUserWithEmailAndPassword(email: _email, password: _password)
.then((user) => {
Firestore.instance.collection('users').document(user.user.uid).setData({"email": _email, "password": _password}),
});
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => ExtraSignUpInfo()));
ExtraSignUpInfo.dart (Page 2)
class ExtraSignUpInfo extends StatefulWidget {
#override
_ExtraSignUpInfoState createState() => _ExtraSignUpInfoState();
}
class _ExtraSignUpInfoState extends State<ExtraSignUpInfo> {
String _name;
String _company;
String _jobTitle;
String _teamName;
String _industry;
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
I want the user just created to be sent to ExtraSignUpInfo() page, so then the email and password can be created later after ExtraSignUpInfo() page form fields are filled in.
you can try to pass the parameters using arguments and sending it with the named route call to the navigator, see this example on cookbook:
https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments
1) In the second screen you have to create the ScreenArguments class like this:
class ScreenArguments {
final String email;
final String password;
ScreenArguments(this.email, this.password);
}
2) Initiate the vars on second screen itself:
String email;
String password;
3) Call the navigator from a button(for example) on the first screen sending the values:
Navigator.pushNamed(context, "/secondScreen", arguments: email, password)
*Add the named route to your main.dart for this to work.
4) Use the values sent from screen1 to screen2.
Hope it helps.
You could also try using a stepper widget where you collect the email, password, etc. on successive steps in sort of a form "wizard." There are many variants (Google 'stepper widget').
Here is a very basic setup adding a TextFormField you can use and add validation to:
import 'package:flutter/material.dart';
class StepperForm extends StatefulWidget {
static Future<void> show(BuildContext context) async {}
#override
_StepperFormState createState() => _StepperFormState();
}
class _StepperFormState extends State<StepperForm> {
///Stepper variables and functions
//declare the currentStep (starting point) as an int 0
int _currentStep = 0;
//Create a list of steps. Use TextFormFields for the email/password. Add validation if needed.
List<Step> _myStepperForm() {
List<Step> _steps = [
Step(
title: Text("Enter Your Email"),
//state: StepState.complete,
isActive: _currentStep >= 0,
content: TextFormField(
decoration: InputDecoration(
labelText: 'Email',
suffixIcon: Icon(Icons.email),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
validator: (value) =>
value.isNotEmpty ? null : 'email can\'t be empty',
//Additional validation code as needed
),
),
Step(
title: Text("Second"),
isActive: _currentStep >= 1,
content: Text("My Second Example"),
),
Step(
title: Text("Third"),
isActive: _currentStep >= 2,
content: Text("My Third Example"),
),
Step(
title: Text("Fourth"),
isActive: _currentStep >= 3,
content: Text("My Fourth Example"),
),
];
return _steps;
}
//Create function for continue button
onStepContinue() {
setState(() {
if (this._currentStep < this._myStepperForm().length - 1) {
this._currentStep = this._currentStep + 1;
} else {
//Completion Code
print('The form is complete.');
}
});
}
//create cancel function
onStepCancel() {
setState(() {
if (this._currentStep > 0) {
this._currentStep = this._currentStep - 1;
} else {
this._currentStep = 0;
}
});
}
//Create the Stepper Widget
Widget _stepperWidget() => Container(
margin: EdgeInsets.only(top: 10),
color: Colors.orangeAccent,
child: Stepper(
//type: StepperType.horizontal,
currentStep: this._currentStep,
steps: _myStepperForm(),
onStepCancel: onStepCancel,
onStepContinue: onStepContinue,
onStepTapped: (step) {
setState(() {
this._currentStep = step;
});
},
),
);
//Call Stepper Function in Scaffold. SingleChildScrollView helps with different screen sizes
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My Stepper Form'),
),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
_stepperWidget(),
SizedBox(height: 600)
],
),
),
);
}
}

Categories

Resources