I'm calling async function in initState(),
but the system actually waits the result of async function.
Could anyone tell me why?
Here's my code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: MyHomePage());
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Future<int> _f;
#override
void initState() {
super.initState();
Stopwatch stopwatch = new Stopwatch()..start();
print('executed in ${stopwatch.elapsed}');
_f = getFuture();
print('executed in ${stopwatch.elapsed}');
}
Future<int> getFuture() async {
int i = 0;
while(i < 1000000000) {
i++;
}
return i;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("test")),
body: FutureBuilder<int>(
future: _f,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
return Center(child: Text("snapshot: ${snapshot.data}"));
break;
default:
return Center(child: CircularProgressIndicator());
}
}
),
);
}
}
And here's the output:
I/flutter (22058): executed in 0:00:00.000384
I/flutter (22058): executed in 0:00:04.536278
A Future function is made to return a Future object asynchronously. Which means executing a statement of code once that will take a much longer time, for example getting json object from an api, than the usual time required for a normal statement, for example a declaration of a variable.
However, you are executing a normal statement, that requires milliseconds to run, a 1000000000 times. That will result in a longer execution time which was built up by the milliseconds of each execution.
Even though it is a Future function, it is not returning a Future object or variable, so it is not asynchronous.
Related
I am trying to fetch data from API as soon as the flutter app loads but I am unable to achieve so
class MarketBloc extends Bloc<MarketListEvent, MarketListState> {
MarketBloc() : super(MarketLoading()) {
on<MarketSelectEvent>((event, emit) async {
emit(MarketLoading());
final data = await ApiCall().getData(event.value!);
globalData = data;
emit(MarketDataFetched(marDat: globalData.data, dealType: event.value));
});
}
}
I have called MarketLoading state as the initial state and I want to call MarketSelectEvent just after that but in the current code, action is required to do so and i want to achieve it without any action.
You have 2 options:
add an event from the UI as soon you instantiate the MarketBloc
MarketBloc()..add(MarketSelectEvent())
add an event in the initialization code
MarketBloc() : super(MarketLoading()) {
add(MarketSelectEvent());
}
You could do this with in the initState of whatever the first page is that your app loads.
class TestPage extends StatefulWidget {
#override
State<TestPage> createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
late MarketBloc marketBloc;
#override
void initState() {
super.initState();
marketBloc = BlocProvider.of<MarketBloc>(context);
marketBloc.add(MarketSelectEvent());
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: BlocBuilder<MarketBloc, MarketListState>(
builder: (context, state) {
if (state is MarketLoading) {
return Text('loading...');
}
if (state is MarketDataFetched) {
return ...your UI that contains data from API call
}
},
),
),
);
}
}
1.this is the main entry
void main() {
WidgetsFlutterBinding.ensureInitialized();
StorageUtil.getInstance();
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Wrapper(),
);
}
}
This is the Wrapper. The log-in form or the home page do not show unless I manually hot-reload the app.
I've tried everything but i am stuck. Please help.
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
User _user = User();
#override
Widget build(BuildContext context) {
_user.uId = StorageUtil.getString('access_token');
if(_user.uId != null && _user.uId != ""){
print('filled ${_user.uId}');
return Home();
}else{
print('empty ${_user.uId}');
return Authenticate();
}
}
}
I think your StorageUtil is giving you promise for get data back to you but you are not waiting for it when app loads at first time.You can try await StorageUtil.getInstance(); in main block.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await StorageUtil.getInstance();
runApp(MaterialApp(home: MyApp()));
}
You need to watch the instance. Right now you are grabbing the instance to get the value but you are not subscribing to the value itself, which means that when the value changes nothing will happen until you refresh the page. I recommend subscribing to the value (access_token) that is determining the login screen vs the home screen.
Flutter has some built in features that makes this a bit easier such as streams and or quicker widgets like the ValueListenerBuilder. Let's see if we can do that with StorageUtil.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await StorageUtil.getInstance();
runApp(MaterialApp(home: MyApp()));
}
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
User _user = User();
#override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: StorageUtil.getString('access_token');,
builder: (BuildContext context, String value, _) {
if(value != null && value != ""){
print('filled ${_user.uId}');
return Home();
} else {
print('empty ${_user.uId}');
return Authenticate();
}
},
),
}
}
It is rough but it should get the job done! I recommend probably finding a more streamlined way to store your state than just the StorageUtil that'll better scale as your application grows.
There is a variable pinEnable which tells the app whether the user has set up a pin for the app. This is stored in SharedPreferences. My first page that comes in my app depends on it. Since the fetching operation is async, it just returns null.
the relevant code I used is given:-
PinData is just a class containing functions to set and get pin and pinEnable
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool pinEnable;
PinData pinData = PinData();
updatePinEnable() async {
pinEnable = await pinData.getPinEnable();
print(pinEnable);
}
#override
void initState() {
super.initState();
updatePinEnable();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(...),
home: pinEnable == false ? MyTabbedHome() : PinCodePage());
}
}
In the last code statement pinEnable is not false but it's null, therefore it returns PinCodePage()
Is there any way to fix this, or any ideas to get around this. Thanks!!
You don't need stateful widget
,and this is a better solution using a FutureBuilder to return the correct widget only when the async process is completed:
Edit: edited the code to address fact that you are not setting initial value in shared prefs
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
PinData pinData = PinData();
Future<bool> isPinEnabled() async => pinData.getPinEnable();
#override
Widget build(BuildContext context) {
return FutureBuilder<bool>(
future: isPinEnabled(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
else if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
return snapshot.data ?
PinScreen() //if true returned from shared prefs go to pin screen
:
HomeScreen(); //if false returned from shared prefs go to home screen
}
else {
return HomeScreen(); //if null returned from shared prefs go to home screen
}
}
}
);
}
}
I am building an app which uses an api and I am using the future builder to fetch the data but the problem is when the state changes it rebuilds and I want to prevent this from happen.
Thanks,
try using this :
class Example extends StatefulWidget {
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
Future<response> future;
#override
void initState() {
future = _asyncmethodCall();
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: future,
builder: (context, snapshot) {
// create some layout here
},
);
}
Future<someResponse> _asyncmethodCall() async {
// async code here
}
}
similar question: How to deal with unwanted widget build?
I am trying to build a countdown app for special days for example 11 d 2 h 30 m 23s to the new year but I can't reload my state every second so it just shows me the second that I loaded the page I don't know how to dynamically reload the page.
import 'package:flutter/material.dart';
class RopSayac extends StatefulWidget {
_RopSayacState createState() => _RopSayacState();
}
class _RopSayacState extends State<RopSayac> {
var now = DateTime.now().second.toString();
String asd(){
setState(() {
now = DateTime.now().second.toString();
});
return now;
}
#override
Widget build(BuildContext context) {
return Container(
child: new Text(asd()),
);
}
}
This is what I got and it doesn't reload time. I am pretty new on the flutter.
As pskink and Günter mentioned, use a Timer. You can even use the periodic constructor that would fit well your scenario.
Note you don't need the asd() function. When you call setState(), the build method will be called automatically passing the new now property value.
If you want, use initState to set an initial value and, as in this example, setup the Timer.
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(title: 'Timer Periodic Demo', home: RopSayac());
}
}
class RopSayac extends StatefulWidget {
_RopSayacState createState() => _RopSayacState();
}
class _RopSayacState extends State<RopSayac> {
String _now;
Timer _everySecond;
#override
void initState() {
super.initState();
// sets first value
_now = DateTime.now().second.toString();
// defines a timer
_everySecond = Timer.periodic(Duration(seconds: 1), (Timer t) {
setState(() {
_now = DateTime.now().second.toString();
});
});
}
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: new Text(_now),
),
);
}
}
This recursive method should be enough for what you want. The seconds set as 1 will keep triggering setState each second, thus refreshing your widget tree.
void _timer() {
Future.delayed(Duration(seconds: 1)).then((_) {
setState(() {
print("1 second closer to NYE!");
// Anything else you want
});
_timer();
});
}
There's this excellent library called timer_builder. I think it'll help you out.
Example from the pub page:
import 'package:timer_builder/timer_builder.dart';
class ClockWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return TimerBuilder.periodic(Duration(seconds: 1), //updates every second
builder: (context) {
return Text("${DateTime.now()}");
}
);
}
}
Here's what I do. In my case I'm polling a webservice in _liveUpdate()
void startUpdates(AppState appState) async {
await new Future.delayed(const Duration(milliseconds: 100));
while (true) {
_liveUpdate();
appState.setState(() {});
await new Future.delayed(const Duration(seconds : 15));
}