I'm new to flutter and developing an app in which vehicle speed is shown in the floating action button in Scaffold. But I want it to change according to speed automatically so that it doesn't need to refresh/restart manually every time.
Here's my code.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
double speedInMps;
double speedInKph;
var geolocator = Geolocator();
var locationOptions = LocationOptions(accuracy: LocationAccuracy.high,
distanceFilter: 10);
Future<void> getVehicleSpeed()async{
try{
geolocator.getPositionStream((locationOptions)).listen((position) async
{
speedInMps = await position.speed;
speedInKph = speedInMps * 1.609344;
print(speedInKph.round());
});
}catch(e){
print(e);
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold( floatingActionButton: FloatingActionButton(
onPressed: () {getVehicleSpeed();
},
child: Text(speedInKph.round().toString() +'Km/h'),//Need Improvments
Here
backgroundColor: Colors.green,
),
appBar: AppBar(
title: Text('speed'),
centerTitle: true,
),
body: Center(
child: FlatButton(
onPressed: getVehicleSpeed,
child: Text(
speedInKph.toString(),
style: TextStyle(fontSize: 16.0),
),
color: Color(0xffdd4b39),
textColor: Colors.white,
padding: const EdgeInsets.all(20.0),
),
),
)
);
}
}
I have to hot reload/restart to get updated speed, but I want it to refresh speed automatically.
You need to listen location only once. So put in initState which called when widget is initialized.
#override
void initState() {
super.initState();
getVehicleSpeed();
}
And than call setState method when data is change. It will rebuild the widget.
Future<void> getVehicleSpeed() async {
try {
geolocator.getPositionStream((locationOptions)).listen((position) async {
speedInMps = position.speed;
setState(() {
speedInKph = speedInMps * 1.609344;
});
print(speedInKph.round());
});
} catch (e) {
print(e);
}
}
Related
I have a small app here, i will check buildNumber of current app and compare to my remote api data, based on this condition i will show the user interfaces.
I have home and updateApp screen where home is the normal webview screen and UpdateApp is a screen where user is required to update the new version of my app.
But condition satisfies but update screen is not showing.
// ignore_for_file: prefer_const_constructors, prefer_const_literals_to_create_immutables, use_build_context_synchronously, unrelated_type_equality_checks, unused_element
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
import 'package:webview_test/models/app_version.dart';
import 'package:webview_test/services/remote_service.dart';
import 'package:webview_test/views/update_app.dart';
import 'package:package_info_plus/package_info_plus.dart';
void main() {
runApp(MyHomePage());
}
class MyHomePage extends StatefulWidget {
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final flutterWebViewPlugin = FlutterWebviewPlugin();
bool isLoading = true;
double webProgress = 0;
bool isLoaded = false;
List<AppVersion>? appVersions;
int buildNumber = 0;
late String packageName;
#override
#override
void initState() {
super.initState();
flutterWebViewPlugin.onProgressChanged.listen((double progress) {
setState(() {
this.webProgress = progress;
});
print("The progress is $progress");
});
getVersions();
getBuild();
}
//Fetching remote data for app versions.
getVersions() async {
appVersions = await RemoteService().getAppVersion();
if (appVersions != null) {
setState(() {
isLoaded = true;
});
}
}
//getting app information to compare remote app versions.
getBuild() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
packageName = packageInfo.packageName;
buildNumber = int.parse(packageInfo.buildNumber);
print("build number is $buildNumber");
if (buildNumber == 1) {
print("Build number is $buildNumber");
}
}
#override
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
systemNavigationBarColor: Colors.white,
systemNavigationBarIconBrightness: Brightness.dark));
return MaterialApp(
home: buildNumber == 1
? proceedToUpdate(context)
: SafeArea(
child: Scaffold(
body: WillPopScope(
onWillPop: () async {
if (await flutterWebViewPlugin.canGoBack()) {
flutterWebViewPlugin.goBack();
return false;
} else {
SystemNavigator.pop();
return true;
}
},
child: Stack(
children: [
Positioned.fill(
child: Column(
children: [
webProgress < 1
? SizedBox(
height: 5,
child: LinearProgressIndicator(
value: webProgress,
color: Colors.blue,
backgroundColor: Colors.white,
),
)
: SizedBox(),
Expanded(
child: WebviewScaffold(
url: "https://google.com",
mediaPlaybackRequiresUserGesture: false,
withLocalStorage: true,
),
),
// isLoading
// ? Center(
// child: CircularProgressIndicator(),
// )
// : Stack(),
],
),
),
],
)),
),
),
);
}
proceedToUpdate(context) {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => UpdateApp()));
}
}
Your variable context in Navigator.of(context).push(...) isn't correct.
You're trying to navigate outside build(BuildContext context), so it won't work. Function build(BuildContext context) is the place where it build your mobile interface - UI screen.
Now in your StatefulWidget MyHomePage -> initState() -> getBuild() -> _proceedToUpdate() -> Navigator.of(context).push(...). The variable context in your Navigator command is not context of your screen UI. Even though function _proceedToUpdate() can run, it cannot navigate.
You may try to show an dialog. Each dialog also has a context. You can show an dialog and then navigate to somewhere when press "OK" button. It'll success.
Good luck!
Update:
Seems like you don't want to show any dialog, therefore we need another approach. You could check the build version in main() async {}. Then pass value buildNumber to somewhere (directly pass to MyApp() or use singleton to make it more professional :D). Then you can make it like: home: _getFirstScreen()
_getFirstScreen() {
if (buildNumber == 1) return UpdateScreen();
else return MyHomePage();
}
Whenever I click Run Query button I get the expected result in the terminal but I have to click it once again to be able to display the result in the UI!
I guess there is something wrong in setState usage. May you please help me to find out what's wrong?
PS. I tried to use both suggested cases of setState mentioned in this post but I always get the same result.
import 'package:flutter/material.dart';
import 'package:mysql1/mysql1.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MySQL Test',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'MySQL Test'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String? _barcode;
late bool visible;
#override
var Connection;
String? res;
void initsql() async {
try {
var settings = ConnectionSettings(
host: '127.0.0.1',
port: 3306,
user: 'root',
db: 'testo',
password: '1234',
timeout: Duration(minutes: 2));
Connection = await MySqlConnection.connect(settings);
print("Connected");
} catch (e) {
print(e);
}
}
void getResult() async {
var results = await Connection.query('SELECT id, Name FROM testo.mytable');
for (var row in results) {
res = 'ID: ${row[0]}, Name: ${row[1]}'; //---> Needs Double click!
print('ID: ${row[0]}, Name: ${row[1]}');
}
}
void initState() {
initsql();
super.initState();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
res == null ? 'Disconnected!' : '$res',
style: Theme.of(context).textTheme.headline5,
),
Padding(
padding: const EdgeInsets.all(15.0),
child: Column(
children: [
ElevatedButton(
onPressed: () => setState(() {
//---> Needs Double click!!
getResult();
}),
child: Center(child: Text('Run Query'))),
],
),
)
],
),
),
);
}
}
First of all your getResult method is asynchronous, so the return type is not void but Future<void>.
Future<void> getResult() async {
var results = await Connection.query('SELECT id, Name FROM testo.mytable');
for (var row in results) {
res = 'ID: ${row[0]}, Name: ${row[1]}'; //---> Needs Double click!
print('ID: ${row[0]}, Name: ${row[1]}');
}
}
The reason why you UI doesn't update the widget on your first tap is, that your getResult is asynchronous. In your button you call setState, which triggers a rebuild of your widget, but since the method is asynchronous, it cannot provide a result immediately when it is started. This means you call setState, it updates your UI, then your method retrieves the new value of getResult, but the UI got updated before that. To fix this you have to wait for your method until it has finished and then call setState to update your UI.
ElevatedButton(
onPressed: () async {
await getResult();
setState((){});
} ,
child: Center(
child: Text('Run Query'),
),
);
#qouci explained well instead converting you fuction future.you can also use like this .add setstate after asyn query execution.
Widget
ElevatedButton(
onPressed: () => getResult(),
child: Center(child: Text('Run Query')))
QueryExecution
void getResult() async {
var results = await Connection.query('SELECT * FROM test.sys_config;');
for (var row in results) {
res = 'ID: ${row[0]}, Name: ${row[1]}'; //---> Needs Double click!
print('ID: ${row[0]}, Name: ${row[1]}');
}
// ------------------------------------here
setState(() {
res;
//---> Needs Double click!!
});
}
I need to dismiss an alert dialogue when a callback is being called. How can i achieve it?
One way is you can add timer for it.
Try this code:-
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(GetMaterialApp(title: 'Flutter', home: Flutter()));
}
class Flutter extends StatefulWidget {
const Flutter({Key? key}) : super(key: key);
#override
State<Flutter> createState() => _FlutterState();
}
class _FlutterState extends State<Flutter> {
int seconds = 3;
bool visible = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter'),
centerTitle: true,
),
body: Center(
child: ElevatedButton(
child: Text('Open Alert'),
onPressed: () {
RemoveAlert();
visible = true;
Get.defaultDialog(
title: 'Alert',
middleText: 'Alert will go in ${seconds} seconds').then((value){
visible = false;
});
},
)),
);
}
void RemoveAlert() {
Future.delayed(Duration(seconds: seconds)).then((value){
if(visible){
Get.back();
print('removed');
}else{
print('removed already');
}
});
}
}
Note: I have used get package here.
Yes I got the solution after a long time effort.
I called Navigator.pop(context) two time. Then it worked for me.
I have added following code to my main screen in order to exit app after 2 times back button taps
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
final timeGap = DateTime.now().difference(preBackpress);
final cantExit = timeGap >= const Duration(seconds: 2);
preBackpress = DateTime.now();
if(cantExit){
const snack = SnackBar(
content: Text('Press Back button again to Exit'),
duration: Duration(seconds: 2),
);
ScaffoldMessenger.of(context).showSnackBar(snack);
return false;
}else{
return true;
}
},
child: Scaffold(....)
);
}
But what I get instead is that when I hit back button it goes all the steps back to every single screen user has been visited.
The reason I added WillPopScope above was to avoid going back to visited screen and just close the app but it seems still doesn't help.
My question is: How do I exit app (when in main screen back button is tapped) without going back to visited screens?
Maybe this is not the same as your code. but I have code to close apps as you want
class ExitController extends StatefulWidget {
#override
_ExitControllerState createState() => _ExitControllerState();
}
class _ExitControllerState extends State<ExitController> {
static const snackBarDuration = Duration(milliseconds: 2000);
// set how long the snackbar appears
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
DateTime? backButtonPressTime;
final snackBar = SnackBar(
behavior: SnackBarBehavior.floating,
// margin: EdgeInsets.fromLTRB(75, 0, 75, 250),
// add margin so that the snackbar is not at the bottom
duration: snackBarDuration,
backgroundColor: Colors.black87,
content: Text('Press Again to Exit',
textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12)),
);
Future<bool> onWillPop() async {
DateTime currentTime = DateTime.now();
bool backButtonCondition =
backButtonPressTime == null ||
currentTime.difference(backButtonPressTime!) > snackBarDuration;
if (backButtonCondition) {
backButtonPressTime = currentTime;
ScaffoldMessenger.of(context).showSnackBar(snackBar);
return false;
}
return true;
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: scaffoldKey,
body: WillPopScope(
onWillPop: onWillPop,
child: yourPageClass(),
),
);
}
}
The snackbar will appear for 2000 milliseconds and during that time if the user press the back button again, the application will exit
Note to call ExitController class in main.dart, and wrapping your all structure(navigation class/page class/etc) with this ExitController class.
Example:
main.dart
import 'package:flutter/material.dart';
import 'package:app_name/structure.dart';
void main(){
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: ExitController(),
);
}
}
WillPopScope(
onWillPop: () async {
return await showDialog(context: ctx, builder: (_) => exitAlertDialog(_));
},
child: GestureDetector(onTap: () => FocusScope.of(ctx).unfocus(), child: widget));
Widget exitAlertDialog(BuildContext context) {
return AlertDialog(
backgroundColor: plColor2,
content: Text('Are you sure you want to exit?'),
actions: <Widget>[
TextButton(child: Text('No'), onPressed: () => Navigator.of(context).pop(false)),
TextButton(child: Text('Yes, exit'), onPressed: () => SystemNavigator.pop())
],
);
}
or you can use showsnackbar as your need
I am making an app which loads the CSV and show the table on the screen but the load function is being called infinitely in the build state can anyone know how to fix it I wanted to call only once but my code called it many times.
Here is the console screenshot:
Here is the code:
import 'package:flutter/material.dart';
import 'package:csv/csv.dart';
import 'dart:async' show Future;
import 'package:flutter/services.dart' show rootBundle;
class TableLayout extends StatefulWidget {
#override
_TableLayoutState createState() => _TableLayoutState();
}
class _TableLayoutState extends State<TableLayout> {
List<List<dynamic>> data = [];
loadAsset() async {
final myData = await rootBundle.loadString("asset/dreamss.csv");
List<List<dynamic>> csvTable = CsvToListConverter().convert(myData);
return csvTable;
}
void load() async{
var newdata = await loadAsset();
setState(() {
data = newdata;
});
print("am i still being called called ");
}
#override
Widget build(BuildContext context) {
load();
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Apps"),),
//floatingActionButton: FloatingActionButton( onPressed: load,child: Icon(Icons.refresh),),
body: ListView(
children: <Widget>[
Container(margin: EdgeInsets.only(top: 20.0),),
Table(
border: TableBorder.all(width: 1.0,color: Colors.black),
children: data.map((item){
return TableRow(
children: item.map((row){
return Text(row.toString(),style: TextStyle(fontSize: 20.0,fontWeight: FontWeight.w900),);
}).toList(),
);
}).toList(),
),
]),
));
}
}
Here is the solution.
#override
void initState() {
super.initState();
load(); // use it here
}
#override
Widget build(BuildContext context) {
return MaterialApp(...); // no need to call initState() here
}