setState deosn't show changes correctly on UI - flutter

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!!
});
}

Related

How to call and API endpoint on initialize. Value is still null

I am trying to get user information from an API. For this, I created a user object. I want to call the function and store it in a user. But the problem is, that I cannot use await and wait till all the data is there.
So instead of async and await, I tried to use .then and fill userInfo with the data. But now the email value is not showing. It is showing 'loading...'.
If I use Future I cannot do user.email.
Is it better to use FutureBuilder? Or try and use Async and Await (the call takes 2.5 seconds)
Here is the code
class Addressbook extends StatefulWidget {
const Addressbook({Key? key}) : super(key: key);
#override
State<Addressbook> createState() => _AddressbookState();
}
class _AddressbookState extends State<Addressbook> {
User? userInfo;
Future<User> getUserInformation() async {
User user = await UserService().getUserById(12345);
return user;
}
#override
void initState() {
// TODO: implement initState
getUserInformation().then((response) {
userInfo = response;
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Mijn adresboek'),
centerTitle: true,
elevation: 0.5,
titleTextStyle: const TextStyle(
fontSize: 21,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
body: Text(userInfo?.email ?? "loading..."),
);
}
}
What do I want to archive?
I want to display user data on the screen. In this case I want to show the email.
What is the problem?
User is not filled with data on init (probably because it is still loading to get the data).
My question
How can I solve this problem? Is async await a solution or should I use FutureBuilder? Can you give me a working sample?
Thanks for helping!
You can use futurebuilder or you can use in initstate to fetch api
#override
void initState() {
// TODO: implement initState
Future.delayed(Duration(seconds: 10), () {
setState(() {
userinfo = "response";
});
// userinfo = "response";
});
super.initState();
}
Inside the widget
Text(userinfo != null ? userinfo.toString() : "loading..."),
SAmple Code
import 'package:flutter/material.dart';
//import 'package:pucon/home.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String? userinfo = null;
String? userinfo2 = null;
#override
void initState() {
// TODO: implement initState
Future.delayed(Duration(seconds: 10), () {
setState(() {
userinfo = "response";
});
// userinfo = "response";
});
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: ListView(
shrinkWrap: true,
children: [
Text(""),
Text(""),
Text(userinfo != null ? userinfo.toString() : "loading..."),
Row(
children: [
FutureBuilder(
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Container(
child: Text(userinfo2.toString()), );
} else {
return SizedBox(
child: CircularProgressIndicator(),
height: 45,
width: 45,
);
// else
// return Container(
// child: CircularProgressIndicator(),
// height: 45,
// width: 45,
// );
}
},
future: _future(),
),
],
)
// InsertData(),
],
),
);
}
_future() async {
await Future.delayed(Duration(seconds: 5), () {
userinfo2 = "Completed";
// setState(() {
//
// });
// userinfo = "response";
});
}
}

Is it possible to have separate BuildContext for two dialogs in Flutter?

I want to control how I close specific dialogs in Flutter. I know if I call Navigator.of(context).pop() it will close the latest dialog.
However my situation is that I can have two dialogs opened at the same time in different order (a -> b or b -> a) and I want to explicitly close one of them.
I know that showDialog builder method provides a BuildContext that I can reference and do Navigator.of(storedDialogContext).pop() but that actually doesn't really help since this context shares same navigation stack.
Update: Vandan has provided useful answer. One solution is to use Overlay widget but it has its downsides, see this answer
My example is on dartpad.dev, example code:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Completer<BuildContext>? _dialog1Completer;
Completer<BuildContext>? _dialog2Completer;
bool _opened1 = false;
bool _opened2 = false;
#override
void initState() {
super.initState();
Timer(const Duration(seconds: 3), () {
_openDialog1();
debugPrint('Opened dialog 1. Dialog should read: "Dialog 1"');
Timer(const Duration(seconds: 2), () {
_openDialog2();
debugPrint('Opened dialog 2. Dialog should read: "Dialog 2"');
Timer(const Duration(seconds: 3), () {
_closeDialog1();
debugPrint('Closed dialog 1. Dialog should read: "Dialog 2"');
Timer(const Duration(seconds: 5), () {
_closeDialog2();
debugPrint('Closed dialog 2. You should not see any dialog at all.');
});
});
});
});
}
Future<void> _openDialog1() async {
setState(() {
_opened1 = true;
});
_dialog1Completer = Completer<BuildContext>();
await showDialog(
barrierDismissible: false,
context: context,
routeSettings: const RouteSettings(name: 'dialog1'),
builder: (dialogContext) {
if (_dialog1Completer?.isCompleted == false) {
_dialog1Completer?.complete(dialogContext);
}
return CustomDialog(title: 'Dialog 1', timeout: false, onClose: _closeDialog1);
});
}
Future<void> _openDialog2() async {
setState(() {
_opened2 = true;
});
_dialog2Completer = Completer<BuildContext>();
await showDialog(
barrierDismissible: false,
context: context,
routeSettings: const RouteSettings(name: 'dialog1'),
builder: (dialogContext) {
if (_dialog2Completer?.isCompleted == false) {
_dialog2Completer?.complete(dialogContext);
}
return CustomDialog(title: 'Dialog 2', timeout: false, onClose: _closeDialog2);
});
}
Future<void> _closeDialog1() async {
final ctx = await _dialog1Completer?.future;
if (ctx == null) {
debugPrint('Could not closed dialog 1, no context.');
return;
}
Navigator.of(ctx, rootNavigator: true).pop();
setState(() {
_dialog1Completer = null;
_opened1 = false;
});
}
Future<void> _closeDialog2() async {
final ctx = await _dialog2Completer?.future;
if (ctx == null) {
debugPrint('Could not closed dialog 2, no context.');
return;
}
Navigator.of(ctx, rootNavigator: true).pop();
setState(() {
_dialog2Completer = null;
_opened2 = false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
TextButton(onPressed: _openDialog1, child: const Text('Open 1')),
TextButton(onPressed: _openDialog2, child: const Text('Open 2')),
const Spacer(),
Align(
alignment: Alignment.bottomCenter,
child: Text('Opened 1? $_opened1\nOpened 2? $_opened2'),
),
],
),
),
);
}
}
class CustomDialog extends StatefulWidget {
const CustomDialog({
Key? key,
required this.timeout,
required this.title,
required this.onClose,
}) : super(key: key);
final bool timeout;
final String title;
final void Function() onClose;
#override
createState() => _CustomDialogState();
}
class _CustomDialogState extends State<CustomDialog>
with SingleTickerProviderStateMixin {
late final Ticker _ticker;
Duration? _elapsed;
final Duration _closeIn = const Duration(seconds: 5);
late final Timer? _timer;
#override
void initState() {
super.initState();
_timer = widget.timeout ? Timer(_closeIn, widget.onClose) : null;
_ticker = createTicker((elapsed) {
setState(() {
_elapsed = elapsed;
});
});
_ticker.start();
}
#override
void dispose() {
_ticker.dispose();
_timer?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(widget.title),
content: SizedBox(
height: MediaQuery.of(context).size.height / 3,
child: Center(
child: Text([
'${_elapsed?.inMilliseconds ?? 0.0}',
if (widget.timeout) ' / ${_closeIn.inMilliseconds}',
].join('')))),
actions: [
TextButton(onPressed: widget.onClose, child: const Text('Close'))
],
);
}
}
If you were to run this code and observe console you can see steps being printed, on step #3 you can observe unwanted behaviour:
opened dialog 1 - OK
opened dialog 2 - OK
closed dialog 1 - not OK
I think I understand the problem, Navigator.of(dialogContext, rootNavigator: true) searches for nearest navigator and then calls .pop() method on it, removing the latest route/dialog on its stack.
I would need to remove specific dialog.
What would be the solution here? Multiple Navigator objects?
I highly suggest that in this case you use Overlay in Flutter. Overlays are rendered independently of widgets on the screen and have their own lifetimes. They appear when you ask them to and you can control when and which one of them should disappear at which time.

How to pause and resume StreamProvider in Flutter

I am experimenting on forex live update using StreamProvider.
The demo will auto-update the exchange rate by fetching latest data from external API periodically. (every 60 seconds in this example)
Below is the diagram of implementation.
Diagram
API call (Future event) --> Put data in stream
^ |
| V
Wait for 60 seconds <-- StreamProvider listens for
new event and rebuild widget
Problem
The stream continues even when navigating to main view.
If we use StreamBuilder, we may be able to call listen() method, which
will return StreamSubscription. Then, either cancel(), pause(), or resume() method can be called on demand.
I wonder if there is similar method to pause and resume while using StreamProvider?
Expected
pause when leaving dashboard view and resume when return to dashboard view.
Codes
Model
class Currency {
String? base;
String? quote;
double? rate;
// constructor, factory constructor, etc.
// ...
}
Controller
class CurrencyService {
Currency? _currency;
Stream<Currency?> get currencyStream async* {
yield* Stream.periodic(Duration(seconds: 60), (_) {
return getCurrencyData();
}).asyncMap((event) async => await event);
}
Future<Currency?> getCurrencyData() async {
try {
// Perform API call and
// update Currency object
// ...
} catch (e) {
print('Error: $e');
}
return _currency;
}
}
View
void main() async {
runApp(
MultiProvider(
providers: [
// some providers,
// another one,
// ...
StreamProvider<Currency?>(
create: (_) => CurrencyService().currencyStream,
initialData: await CurrencyService().getCurrencyData(),
),
],
child: TestApp(),
),
);
}
class TestApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Live Update Demo',
initialRoute: '/',
routes: routes,
);
}
}
Main view (page 1)
class MainView extends StatefulWidget {
const MainView({Key? key}) : super(key: key);
#override
_MainViewState createState() => _MainViewState();
}
class _MainViewState extends State<MainView> {
// ...
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
// ...
ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, '/dashboard');
},
child: Text('Dashboard')),
],
),
);
}
}
Dashboard view (page 2)
class DashboardView extends StatelessWidget {
const DashboardView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(body: Consumer<Currency?>(
builder: (context, currency, child) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child: Text('${currency?.base ?? ''}${currency?.quote ?? ''}'),
),
Container(
child: Text('${currency?.rate ?? ''}'),
),
],
),
);
},
));
}
}
Thank you.
Pausing and resuming StreamProvider on Stream.periodic don't seem possible. Instead, the implementation can still be achieved using Timer.periodic and StreamController, as suggested by #Abion47
We can simulate the pause and resume by controlling when to start and stop adding new data to stream. One of the ways is to start the Timer.periodic when navigating to dashboard view (after a button is pressed) and cancel the timer when returning to main view (dashboard view is popped up).
ElevatedButton(
onPressed: () {
// start timer
// ...
Navigator.pushNamed(...).then((_) {
// stop timer
// this section is triggered when returning from dashboard to main view
});
}
Revised codes
// Controller
class CurrencyService {
Currency? _currency;
Timer? _pollingTimer;
StreamController<Currency?> _currencyController = StreamController.broadcast();
Future<void> addCurrencyData() async {
await getCurrencyData()
.then((currency) => _currencyController.add(currency));
}
void closeStream() {
_currencyController.close();
}
void startPolling() {
addCurrencyData();
_pollingTimer = Timer.periodic(Duration(seconds: 60), (_) => addCurrencyData());
}
void stopPolling() {
_pollingTimer?.cancel();
}
Stream<Currency?> get currencyStream => _currencyController.stream;
Future<Currency?> getCurrencyData() async {
try {
// Perform API call and
// update Currency object
// ...
} catch (e) {
print('Error: $e');
}
return _currency;
}
}
// Main
void main() async {
runApp(
MultiProvider(
providers: [
// some providers,
// another one,
// ...
Provider(create: (_) => CurrencyService()),
],
child: TestApp(),
),
);
}
class TestApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Live Update Demo',
initialRoute: '/',
routes: routes,
);
}
}
// Main view (page 1)
class MainView extends StatelessWidget {
const MainView({Key? key}) : super(key: key);
// ...
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
// ...
ElevatedButton(
onPressed: () {
Provider.of<CurrencyService>(context, listen: false)
.startPolling();
Navigator.pushNamed(
context,
'/dashboard',
).then((_) => Provider.of<CurrencyService>(context, listen: false).stopPolling());
},
child: Text('Dashboard')),
],
),
);
}
}
// Dashboard view (page 2)
class DashboardView extends StatelessWidget {
const DashboardView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final currencyService = Provider.of<CurrencyService>(context);
return Scaffold(
body: StreamProvider<Currency?>.value(
initialData: null,
value: currencyService.currencyStream,
child: CurrencyRate(),
),
);
}
}
class CurrencyRate extends StatelessWidget {
const CurrencyRate({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final currency = context.watch<Currency?>();
return Center(
child: currency == null
? CircularProgressIndicator()
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child: Text('${currency?.base ?? ''}${currency?.quote ?? ''}'),
),
Container(
child: Text('${currency?.rate ?? ''}'),
),
],
),
);
}
}

Delete specific widget | Flutter & Riverpod

As shown in the image, I'm trying to have a list of dice where I can add or delete a die. I've tried StateProvider, ChangeNotifier, and StateNotifier. Each one doesn't seem to work as I expect it to. I'm trying to make a provider that contains a list of dieWidgets, but I can't figure out how to remove a specific die when I longpress on it. The image shows a popup menu to delete it, that's the long-term goal, but just a longpress delete would be good for now. Thoughts on how to approach this?
Code
main.dart
class DiceNotifier extends ChangeNotifier {
List<DieWidget> dice = [];
void add() {
dice.add(DieWidget());
notifyListeners();
}
void removeDie(int id) {
// FIXME: Unable to delete a die based on id
print(id);
notifyListeners();
}
}
final diceProvider = ChangeNotifierProvider((_) {
return DiceNotifier();
});
class MyHomePage extends ConsumerWidget {
#override
Widget build(BuildContext context, ScopedReader watch) {
final dice = watch(diceProvider).dice;
return Scaffold(
appBar: AppBar(
title: Text("Dice"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
...dice,
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
context.read(diceProvider).add();
},
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
die_widget.dart
class DieWidget extends StatefulWidget {
#override
_DieWidgetState createState() => _DieWidgetState();
}
class _DieWidgetState extends State<DieWidget> {
int value = 0;
int id = 0;
#override
Widget build(BuildContext context) {
return FlatButton(
child: Text(
'$value',
),
onPressed: () {
setState(() {
value++;
id++;
});
// context.read(dieProvider).increment();
},
onLongPress: () {
final dice = context.read(diceProvider);
dice.removeDie(id);
// print(this.value);
},
);
}
}
One solution would be to define a parameter value in the DiceWidget class:
class DiceWidget extends StatefulWidget {
const DiceWidget({ Key key, this.value }) : super(key: key);
int value;
#override
_DiceWidgetState createState() => _DiceWidgetState();
}
And access this data from the DiceWidget:
class DiceWidget extends StatefulWidget {
#override
_DiceWidgetState createState() => _DiceWidgetState();
}
class _DiceWidgetState extends State<DiceWidget> {
#override
Widget build(BuildContext context) {
return FlatButton(
child: Text(
widget.value.toString() ?? '',
),
onLongPress: () {
final dice = context.read(diceProvider);
dice.removeDice(widget.value);
// print(widget.value);
},
);
}
}
In the DiceNotifier class, I'd recommend to implement the dices array as a List<int>:
List<int> dices = [];
Therefore, the addDice() and removeDice() functions will be, respectively:
class DiceNotifier extends ChangeNotifier {
List<int> dices = [];
void addDice() {
dices.add(dices.length);
notifyListeners();
}
void removeDice(int id) {
dices.remove(id);
print(id);
notifyListeners();
}
}
To make the example work, we need to modify the MyHomePage Column children as well, to build the list of DiceWidgets:
...dices.map((d) => DiceWidget(value: d)).toList(),
The whole example will then be:
main.dart:
class DiceNotifier extends ChangeNotifier {
List<int> dices = [];
void addDice() {
dices.add(dices.length);
notifyListeners();
}
void removeDice(int id) {
dices.remove(id);
print(id);
notifyListeners();
}
}
final diceProvider = ChangeNotifierProvider((_) {
return DiceNotifier();
});
class MyHomePage extends ConsumerWidget {
#override
Widget build(BuildContext context, ScopedReader watch) {
final dices = watch(diceProvider).dices;
return Scaffold(
appBar: AppBar(
title: Text("Dice"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
...dices.map((d) => DiceWidget(value: d)).toList(),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
context.read(diceProvider).addDice();
},
child: Icon(Icons.add),
),
);
}
}
dice_widget.dart:
class DiceWidget extends StatefulWidget {
#override
_DiceWidgetState createState() => _DiceWidgetState();
}
class _DiceWidgetState extends State<DiceWidget> {
#override
Widget build(BuildContext context) {
return FlatButton(
child: Text(
widget.value.toString() ?? '',
),
onLongPress: () {
final dice = context.read(diceProvider);
dice.removeDice(widget.value);
print(widget.value);
},
);
}
}

How to set text values to change automatically in flutter?

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