Future.wait function - dart

class Tickets extends StatefulWidget {
int groupid;
int event_id;
List returnTickets = [];
Map<String, dynamic> hey;
Tickets([this.groupid,this.event_id,this.hey,this.returnTickets]);
#override
_TicketsState createState() => new _TicketsState();
}
class _TicketsState extends State<Tickets> {
List returnTickets = [];
List ticketType = [];
Map<String, dynamic> hey;
int i = 0;
#override
void initState() {
super.initState();
// _getTicketType();
}
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
widget.groupid;
widget.event_id;
var futureBuilder = new FutureBuilder(
future: Future.wait([hey, returnTickets]).then(
(response) => new _TicketsState(hey: response[0], returnTickets: response[1]),
),
builder: (BuildContext context, AsyncSnapshot snapshot) {
print(snapshot.connectionState);
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return new Text('...');
default:
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
else
return createListTickets(context, snapshot);
}
},
);
Hi everyone, I am trying to implement the Future.wait function so then I can hopefully use more than a method within a building widget, but as I am trying to do so, I get this long error, any help please??
Thank you in advance, much appreciated.

It's not recommended to use what is called "raw" types in Dart2 (List). In this particular case, Future.wait expects a List<Future>, but you've (implicitly) created a List<Object> here:
List returnTickets = [];
This worked in Dart1, because List<dynamic> could pretend to be List<T>.
Try writing:
List<Future> returnTickets = [];
Or, better yet:
final returnTickets = <Future>[];

Related

How to prevent FutureBuilder's rebuilds even its underlying future does not change

In Flutter, I am getting unwanted rebuilds. In my case I use FutureBuilder to show a list by fetching db result, which is a future and have a dependency on query parameter. I tried to make it that FutureBuilder's future does not change if the query parameter does not change, But still the FutureBuilder's builder block is called every time. How can I make the FutureBuilder does not rebuild itself where its future does not change.
Below is my codes, every time the MusicList2's parent widget build, MusicList2 get rebuild, its FutureBuilder get rebuild.
class MusicList2 extends StatefulWidget {
final MusicRowActionCallback onTapItem;
final MusicRowActionCallback onDoubleTap;
final MusicRowActionCallback onLongPressed;
final String facetName;
final String facetValue;
const MusicList2(
{Key key,
this.onTapItem,
this.onDoubleTap,
this.onLongPressed,
this.facetName,
this.facetValue}) : super(key: key);
#override
State<StatefulWidget> createState() {
return _MusicList2State();
}
}
class _MusicList2State extends State<MusicList2> {
Future<List<Music>> loadMusicByFacet;
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Music>>(
future:
loadMusicByFacet,
builder: (context, snapshot) {
if (snapshot.data == null)
return Center(child: CircularProgressIndicator(),);
return ListView.builder(
shrinkWrap: true,
key: const ValueKey<String>('music-list'),
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
final random = Random();
var i = random.nextInt(5);
return MusicRow(
avatarBgColor: colors[i],
music: snapshot.data[index],
onTap: widget.onTapItem,
onDoubleTap: widget.onDoubleTap,
onLongPressed: widget.onLongPressed,
);
},
);
},
);
}
#override
void initState() {
super.initState();
loadMusicByFacet = MusicsDatabaseRepository.get.getMusicsByFacet(widget.facetName, widget.facetValue);
}
#override
void didUpdateWidget(MusicList2 oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.facetValue != widget.facetValue || oldWidget.facetName != widget.facetName) {
loadMusicByFacet = MusicsDatabaseRepository.get.getMusicsByFacet(widget.facetName, widget.facetValue);
}
}
}
For anyone else having this error, I solved it using AbdulRahmanAlHamali's solution on GitHub. This is his answer:
Hello, I believe the problem here is that didUpdateWidget of the
FutureBuilder state is being called every time a rebuild is issued.
This function checks if the old future object is different from the
new one, and if so, refires the FutureBuilder.
To get past this, we can call the Future somewhere other than in the
build function. For example, in the initState, and save it in a member
variable, and pass this variable to the FutureBuilder.
So instead of having:
FutureBuilder(
future: someFunction(),
....
We should have:
initState() {
super.initState();
_future = SomeFunction();
}
and then
FutureBuilder(
future: _future,
....
Find the original thread here.

How to fix FutureBuilder open multiple times error?

these my two classes(two pages). these two classes open multiple times.
I put debug point in futurebuilder in two classes.
debug point running,
MainCategory page and got to the next page
SubCategory page and again running MainCategory page(previous page) futurebuilder and again running MainCategory page futurebuilder
navigate subcategory page to third page running subcategory page and main category page
I upload my two classes to GitHub and please let me know what the issue is.
MainCategory code: https://github.com/bhanuka96/ios_login/blob/master/MainCategory.dart
SubCategory code: https://github.com/bhanuka96/ios_login/blob/master/subCategory.dart
As stated in the documentation, you should not fetch the Future for the Futurebuilder during the widget's build event.
https://docs.flutter.io/flutter/widgets/FutureBuilder-class.html
The future must have been obtained earlier, e.g. during
State.initState, State.didUpdateConfig, or
State.didChangeDependencies. It must not be created during the
State.build or StatelessWidget.build method call when constructing the
FutureBuilder. If the future is created at the same time as the
FutureBuilder, then every time the FutureBuilder's parent is rebuilt,
the asynchronous task will be restarted.
So, try to move your call to getRegister method outside the build method and replace it with the returned Future value.
For example, below I have a class that returns a Future value which will be consumed with the help of FutureBuilder.
class MyApiHelper{
static Future<List<String>> getMyList() async {
// your implementation to make server calls
return List<String>();
}
}
Now, inside your widget, you will have something like this:
class _MyHomePageState extends State<MyHomePage> {
Future<List<String>> _myList;
#override
void initState() {
super.initState();
_myList = MyApiHelper.getMyList();
}
#override
Widget build(BuildContext context) {
return Scaffold(body: FutureBuilder(
future: _myList,
builder: (_, AsyncSnapshot<List<String>> snapLs) {
if(!snapLs.hasData) return CircularProgressIndicator();
return ListView.builder(
itemCount: snapLs.data.length,
itemBuilder: (_, index) {
//show your list item row here...
},
);
},
));
}
}
As shown above, the Future is fetched in the initState function and used inside the build method and used by FutureBuilder.
I hope this was helpful.
Thanks.
If you happen to use Provider, here's (in my opinion) a clearer alternative based on your question:
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureProvider<List<String>>(
create: (_) => MyApiHelper.getMyList(),
child: Consumer<List<String>>(
builder: (_, list, __) {
if (list == null) return CircularProgressIndicator();
return ListView.builder(
itemCount: list.length,
itemBuilder: (_, index) {
//show your list item row here...
},
);
};
),
);
}
}
This can also be achieved of course as a StatefulWidget as suggested by the other answer, or even with flutter_hooks as explained in Why is my Future/Async Called Multiple Times?
You can create new Widget and pass Function to
returnFuture as
() {
return YourFuture;
}
import 'dart:developer';
import 'package:flutter/material.dart';
class MyFutureBuilder<T> extends StatefulWidget {
final Future<T> Function() returnFuture;
final AsyncWidgetBuilder<T> builder;
final T initialData;
MyFutureBuilder({
this.returnFuture,
#required this.builder,
this.initialData,
Key key,
}) : super(key: key);
#override
_MyFutureBuilderState<T> createState() => _MyFutureBuilderState<T>();
}
class _MyFutureBuilderState<T> extends State<MyFutureBuilder<T>> {
bool isLoading = false;
Future<T> future;
#override
void initState() {
super.initState();
future = widget.returnFuture();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
builder: widget.builder,
initialData: widget.initialData,
future: future,
);
}
}
Example
MyFutureBuilder<List<User>>(
returnFuture: () {
return moderatorUserProvider
.getExecutorsAsModeratorByIds(val.users,
save: true);
},
builder: (cont, asyncData) {
if (asyncData.connectionState !=
ConnectionState.done) {
return Center(
child: MyCircularProgressIndicator(
color: ModeratorColor.executors.color,
),
);
}
return Column(
children: asyncData.data
.map(
(singlExecutor) =>
ChooseInfoButton(
title:
'${singlExecutor.firstName} ${singlExecutor.secondName}',
subTitle: 'Business analyst',
middleText: '4.000 NOK',
subMiddleText: 'full time',
label: 'test period',
subLabel: '1.5 month',
imageUrl:
assetsUrl + 'download.jpeg',
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) =>
ModeratorExecutorEditPage(),
),
);
},
),
)
.toList());
},
)
```

Flutter set startup page based on Shared Preference

I've been trying without success to load different pages according to my Shared Preference settings.
Based on several posts found in stackoverflow, i end up with the following solution:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:testing/screens/login.dart';
import 'package:testing/screens/home.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Widget page = Login();
Future getSharedPrefs() async {
String user = Preferences.local.getString('user');
if (user != null) {
print(user);
this.page = Home();
}
}
#override
void initState() {
super.initState();
this.getSharedPrefs();
}
#override
Widget build(BuildContext context) {
return MaterialApp(home: this.page);
}
}
class Preferences {
static SharedPreferences local;
/// Initializes the Shared Preferences and sets the info towards a global variable
static Future init() async {
local = await SharedPreferences.getInstance();
}
}
The variable user is not null because the print(user) returns a value as expected, but the login screen is always being opened.
Your problem is that your build method returns before your getSharedPrefs future is complete. The getSharedPrefs returns instantly as soon as it's called because it's async and you're treating it as a "Fire and Forget" by not awaiting. Seeing that you can't await in your initState function that makes sense.
This is where you want to use the FutureBuilder widget. Create a Future that returns a boolean (or enum if you want more states) and use a future builder as your home child to return the correct widget.
Create your future
Future<bool> showLoginPage() async {
var sharedPreferences = await SharedPreferences.getInstance();
// sharedPreferences.setString('user', 'hasuser');
String user = sharedPreferences.getString('user');
return user == null;
}
When user is null this will return true. Use this future in a Future builder to listen to the value changes and respond accordingly.
#override
Widget build(BuildContext context) {
return MaterialApp(home: FutureBuilder<bool>(
future: showLoginPage(),
builder: (buildContext, snapshot) {
if(snapshot.hasData) {
if(snapshot.data){
// Return your login here
return Container(color: Colors.blue);
}
// Return your home here
return Container(color: Colors.red);
} else {
// Return loading screen while reading preferences
return Center(child: CircularProgressIndicator());
}
},
));
}
I ran this code and it works fine. You should see a blue screen when login is required and a red screen when there's a user present. Uncomment the line in showLoginPage to test.
There is a much pretty way of doing this.
Assuming that you have some routes and a boolean SharedPreference key called initialized.
You need to use the WidgetsFlutterBinding.ensureInitialized() function before calling runApp() method.
void main() async {
var mapp;
var routes = <String, WidgetBuilder>{
'/initialize': (BuildContext context) => Initialize(),
'/register': (BuildContext context) => Register(),
'/home': (BuildContext context) => Home(),
};
print("Initializing.");
WidgetsFlutterBinding.ensureInitialized();
await SharedPreferencesClass.restore("initialized").then((value) {
if (value) {
mapp = MaterialApp(
debugShowCheckedModeBanner: false,
title: 'AppName',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: routes,
home: Home(),
);
} else {
mapp = MaterialApp(
debugShowCheckedModeBanner: false,
title: 'AppName',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: routes,
home: Initialize(),
);
}
});
print("Done.");
runApp(mapp);
}
The SharedPreference Class Code :
class SharedPreferencesClass {
static Future restore(String key) async {
final SharedPreferences sharedPrefs = await SharedPreferences.getInstance();
return (sharedPrefs.get(key) ?? false);
}
static save(String key, dynamic value) async {
final SharedPreferences sharedPrefs = await SharedPreferences.getInstance();
if (value is bool) {
sharedPrefs.setBool(key, value);
} else if (value is String) {
sharedPrefs.setString(key, value);
} else if (value is int) {
sharedPrefs.setInt(key, value);
} else if (value is double) {
sharedPrefs.setDouble(key, value);
} else if (value is List<String>) {
sharedPrefs.setStringList(key, value);
}
}
}

Reset State of children widget

Here is the summary of the code I'm having a problem with:
Parent widget
class HomePage extends StatefulWidget {
#override
State<HomePage> createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
final GlobalKey<AsyncLoaderState> _asyncLoaderState = GlobalKey<AsyncLoaderState>();
List<DateTime> rounds;
List<PickupModel> pickups;
DateTime currentDate;
Widget build(BuildContext context) {
var _asyncLoader = AsyncLoader(
key: _asyncLoaderState,
initState: () async => await _getData(),
renderLoad: () => Scaffold(body: Center(child: CircularProgressIndicator())),
renderError: ([error]) => Text('Sorry, there was an error loading'),
renderSuccess: ({data}) => _buildScreen(context),
);
return _asyncLoader;
}
Widget _buildScreen(context) {
return Scaffold(
body: PickupList(pickups),
);
}
Future<Null> _selectDate(BuildContext context) async {
final DateTime picked = await showDatePicker(
context: context,
);
if (picked != null && picked != currentDate) {
currentDate = picked;
pickups = await api.fetchPickupList(currentDate);
setState(() {
});
}
}
_getData() async {
rounds = await api.fetchRoundsList();
currentDate = _getNextRound(rounds);
pickups = await api.fetchPickupList(currentDate);
}
}
Children Widget
(Listview builds tiles)
class PickupTile extends StatefulWidget{
final PickupModel pickup;
PickupTile(this.pickup);
#override
State<StatefulWidget> createState() {
return PickupTileState();
}
}
class PickupTileState extends State<PickupTile> {
Duration duration;
Timer timer;
bool _isUnavailable;
bool _isRunning = false;
bool _isDone = false;
#override
void initState() {
duration = widget.pickup.duration;
_isUnavailable = widget.pickup.customerUnavailable;
super.initState();
}
#override
Widget build(BuildContext context) {
return Row(
children: [
// UI widgets
]
}
So I have a parent widget an initial list of pickups which are displayed in the children PickupTile. One can change the date of the pickups displayed using _selectDate. This fetches a new list of Pickups which are stored in the parent State, and the children are rebuilt with their correct attributes. However, the State of the children widget (duration, isRunning, isDone...) is not reset so they stay on screen when changing the date.
If feel like I'm missing something obvious but I can't figure out how to reset the State of the children Widget or create new PickupTiles so that when changing the date I get new separate States.

Using SharedPreferences to set login state and retrieving it at App launch - Flutter

I have an flutter app in which I have to check the login status when the app is launched and call the relevant screen accordingly.
The code used to launch the app:
class MyApp extends StatefulWidget {
#override
MyAppState createState() {
return new MyAppState();
}
}
class MyAppState extends State<MyApp> {
bool isLoggedIn;
Future<bool> getLoginState() async{
SharedPreferences pf = await SharedPreferences.getInstance();
bool loginState = pf.getBool('loginState');
return loginState;
// return pf.commit();
}
#override
Widget build(BuildContext context) {
getLoginState().then((isAuth){
this.isLoggedIn = isAuth;
});
if(this.isLoggedIn) {return Container(child: Text('Logged In'));}
else {return Container(child: Text('Not Logged In));}
}
}
I am able to save the SharedPreference and retrieve it here, the issue is that as getLoginState() is an async function, this.isLoggedIn is null by the time the if condition is executed. The boolean assertion fails in the if statement and the app crashes.
How do I ensure that the bool variable isLoggedIn used in the if condition has a value when the if statement is executed?
Any help would be appreciated.
You can use FutureBuilder to solve this problem.
#override
Widget build(BuildContext context) {
new FutureBuilder<String>(
future: getLoginState(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.active:
case ConnectionState.waiting:
return new Text('Loading...');
case ConnectionState.done:
if (snapshot.hasData) {
loginState = snapshot.data;
if(loginState) {
return Container(child: Text('Logged In'));
}
else {
return Container(child: Text('Not Logged In));
}
} else {
return Container(child: Text('Error..));
}
}
},
)
}
Note: we don't need isLoggedIn state variable.

Resources