When the user comes on this page, the function async get_ticket() need to be called, it returns a ticket id and the revenue user wins (it is scratch game). The first problem with this code is if i want display the value of "gain" and "id_ticket" because at the screen it says "null". When i have finish to scratch the ticket there is a setstate made and the value are updated from null to their real value.
Why this 2 values are not updated ??? The widget seems to be displayed before the function so widget can't display real values (which is updated by the async function). How i can resolve that ? For my game the better solution would be to called the async function and if player can have a ticket we display the ticket and if not we display a message for that "You can't scratch again today" for example.
import 'package:flutter/material.dart';
import 'package:flutter_app/menu_member.dart';
import 'package:scratcher/scratcher.dart';
import 'package:flutter_app/globals.dart' as globals;
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'package:flutter_app/liste_grattage.dart';
class Affiche_Ticket_Grattage extends StatefulWidget {
#override
_Affiche_Ticket_Grattage_State createState() {
return _Affiche_Ticket_Grattage_State();
}
}
// Create a corresponding State class.
// This class holds data related to the form.
class _Affiche_Ticket_Grattage_State extends State<Affiche_Ticket_Grattage> {
#override
void initState() {
Get_Ticket();
super.initState();
}
bool vis=false;
var gain;
var id_ticket;
Future Get_Ticket() async {
// SERVER LOGIN API URL
var url = 'https://www.easytrafic.fr/game_app/get_ticket.php';
// Store all data with Param Name.
var data = {'id_membre':globals.id_membre};
var ticket_encode=jsonEncode(data);
// Starting Web API Call.
var response = await http.post(url, body: ticket_encode,headers: {'content-type': 'application/json','accept': 'application/json','authorization': globals.token});
// Getting Server response into variable.
Map <String,dynamic> map = json.decode(response.body);
gain=map["gain"];
print(gain.toString());
id_ticket=map["id"];
print(id_ticket.toString());
}
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('GRATTAGE')),
drawer: new DrawerOnly(),
body:
Center(
child:
Column(
children: <Widget>[
Container(
width:400,
height:30,
margin: const EdgeInsets.only(top: 10.0),
child : new Text("Grattez votre ticket N °"+id_ticket.toString(),textAlign: TextAlign.center,style: TextStyle(fontSize: 30.0),),
),
Container(
padding: const EdgeInsets.all(30.0),
child:
Scratcher(
accuracy: ScratchAccuracy.medium,
brushSize: 35,
color: Colors.lightBlueAccent,
threshold: 80,
onThreshold: () {
setState(() {
vis = true;
});
print ("Ticket gratté !!!");
},
child:
Container(
width: 300,
height:300,
color: Colors.black,
alignment: Alignment.center,
child:
Text(gain.toString()+" €",style: TextStyle(fontWeight: FontWeight.bold, fontSize: 50, color: Colors.red),
)
)
)
),
Visibility(
visible: vis,
child: Container(
width:300,
height:45,
margin: const EdgeInsets.only(top: 20.0),
child:
RaisedButton(
color: Colors.green,
textColor: Colors.white,
padding: EdgeInsets.fromLTRB(9, 9, 9, 9),
child: Text('VALIDER VOTRE TICKET'),
onPressed: () {
Valide_Ticket();
},
),
),
),
]
)
)
),
);
}
Future Valide_Ticket() async{
// SERVER LOGIN API URL
var url2 = 'https://www.easytrafic.fr/game_app/valide_ticket.php';
// Store all data with Param Name.
var data2 = {'id_membre':globals.id_membre,'id_ticket':id_ticket,'gain':gain};
var ticket_info=jsonEncode(data2);
// Starting Web API Call.
var response = await http.post(url2, body: ticket_info,headers: {'content-type': 'application/json','accept': 'application/json','authorization': globals.token});
// Getting Server response into variable.
Map <String,dynamic> map2 = json.decode(response.body);
if(map2["status"] == 1)
{
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: new Text(map2["message"]),
actions: <Widget>[
FlatButton(
child: new Text("OK"),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Affiche_Liste_Grattage()),
);
},
),
],
);
},
);
}
}
}
You can use FutureBuilder for this issue. It wait for the future function to perform its task and then renders your page.
Another way would be to show an animation while the data is fetched. then use setState() to hide the animation and show the Widgets(based on the condition)
Related
I am trying to write a widget test for a screen, not the main app. It's my first time writing a widget test and I couldn't find a proper solution for the issue.
I don't know how to write a proper test for this one. I tried to write a simple widget test and it end up giving me an error as below
"Warning: At least one test in this suite creates an HttpClient. When
running a test suite that uses TestWidgetsFlutterBinding, all HTTP
requests will return status code 400, and no network request will
actually be made. Any test expecting a real network connection and
status code will fail.
To test code that needs an HttpClient, provide your own HttpClient
implementation to the code under test, so that your test can
consistently provide a testable response to the code under test."
I have just started learning it please help me.
NOTE: my test was just writing a basic test for finding Text widgets.
class BookingDetails extends StatefulWidget {
final booking;
BookingDetails(this.booking);
#override
_BookingDetailsState createState() => _BookingDetailsState();
}
class _BookingDetailsState extends State<BookingDetails>
with AutomaticKeepAliveClientMixin {
Row _buildTeacherInfo(Map<String, dynamic> teacherData) {
return teacherData != null
? Row(
children: <Widget>[
CircleAvatar(
radius: 53,
backgroundColor: MyColors.primary,
child: CircleAvatar(
radius: 50.0,
backgroundImage: teacherData['user']['img_url'] == null ||
teacherData['user']['img_url'] == ''
? AssetImage('assets/images/placeholder_avatar.png')
: NetworkImage(teacherData['user']['img_url']),
backgroundColor: Colors.transparent,
),
),
SizedBox(width: 20.0),
Column(
children: <Widget>[
Container(
child: Column(
children: <Widget>[
Text(
'${teacherData['user']['first_name']} ',
style: AppStyles.textHeader1Style,
),
Text(
'${teacherData['user']['last_name']}',
style: AppStyles.textHeader1Style,
),
],
),
),
ElevatedButton(
onPressed: () {
//View Profile method
},
style: ElevatedButton.styleFrom(
primary: MyColors.primary,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(25))),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(Icons.next_plan_outlined),
SizedBox(width: 10.0),
Text('VIEW PROFILE'),
],
),
),
],
),
],
)
: Row(
children: <Widget>[
CircleAvatar(
radius: 48,
backgroundColor: MyColors.primary,
child: CircleAvatar(
radius: 45.0,
backgroundImage:
AssetImage('assets/images/placeholder_avatar.png'),
backgroundColor: Colors.transparent,
),
),
SizedBox(width: 20.0),
Expanded(
child: Text(
'Teacher allocation in progress',
style: AppStyles.textHeader1Style,
),
)
],
);
}
Widget _buildBookingDetails(
Map<String, dynamic> booking,
List<dynamic> campusData, // one campus' data is an array for some reason.
Map<String, dynamic> instData,
) {
return Expanded(
child: Scrollbar(
child: ListView(
children: [
ListTile(
leading: Icon(Icons.location_on),
title: Text(
'${campusData[0]['address_line1']},'
' ${campusData[0]['suburb']}, '
'${campusData[0]['state']} ${campusData[0]['postcode']} ',
style: AppStyles.textHeader3Style,
),
),
}
#override
Widget build(BuildContext context) {
super.build(context);
return FutureBuilder(
future: Future.wait([_teacherData, _campusData, _classData, _instData]),
builder: (context, snapshot) => snapshot.connectionState ==
ConnectionState.waiting
? MyLoadingScreen(message: 'Loading booking data, please wait...')
: snapshot.hasData
? SafeArea(
child: Container(
margin: const EdgeInsets.only(top: 30.0),
child: Padding(
padding: const EdgeInsets.all(30),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_buildTeacherInfo(snapshot.data[0]),
Divider(color: MyColors.dividerColor),
SizedBox(height: 10),
const SizedBox(height: 10),
Divider(
color: MyColors.primary,
thickness: 1,
),
const SizedBox(height: 10),
_buildBookingDetails(
widget.booking,
snapshot.data[1],
snapshot.data[3],
),
SizedBox(height: 10),
Divider(
color: MyColors.primary,
thickness: 1,
),
SizedBox(height: 10),
Center(
child: widget.booking['cancelled_by_inst'] == true
? Text(
'Canceled',
style: AppStyles.textHeader3StyleBold,
)
: widget.booking['teacher_id'] == null
? Center(
child: Text(
'Teacher Allocation in Progress',
style: AppStyles.textHeader3StyleBold,
),
)
: null,
),
}
I have reduced your code to the following minimal version, to be able to execute it:
snippet.dart:
import 'package:flutter/material.dart';
import 'dart:convert';
import 'api.dart';
class BookingDetails extends StatefulWidget {
final Map<String, String> booking;
BookingDetails(this.booking);
#override
_BookingDetailsState createState() => _BookingDetailsState();
}
class _BookingDetailsState extends State<BookingDetails> {
late Future _campusData;
Future<dynamic> _fetchCampusData() async {
var campusID = widget.booking['campus_id'];
if (campusID != null) {
var response = await api.getCampusByID(campusID);
return json.decode(response.body);
}
}
#override
void initState() {
_campusData = _fetchCampusData();
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _campusData,
builder: (context, snapshot) {
if (snapshot.hasData) {
return const Text('Displaying data');
} else if (snapshot.hasError) {
return const Text('An error occurred.');
} else {
return const Text('Loading...');
}
}
);
}
}
api.dart:
import 'package:http/http.dart' as http;
final _ApiClient api = _ApiClient();
class _ApiClient {
Future<http.Response> getCampusByID(String id) async {
var url = Uri.parse('https://run.mocky.io/v3/49c23ebc-c107-4dae-b1c6-5d325b8f8b58');
var response = await http.get(url);
if (response.statusCode >= 400) {
throw "An error occurred";
}
return response;
}
}
Here is a widget test which reproduces the error which you described:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:widget_test/snippet.dart';
void main() {
testWidgets('Should test widget with http call', (WidgetTester tester) async {
var booking = <String, String>{
'campus_id': '2f4fccd2-e199-4989-bad3-d8c48e66a15e'
};
await tester.pumpWidget(TestApp(BookingDetails(booking)));
expect(find.text('Loading...'), findsOneWidget);
await tester.pump();
expect(find.text('Displaying data'), findsOneWidget);
});
}
class TestApp extends StatelessWidget {
final Widget child;
TestApp(this.child);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: child,
);
}
}
Here is the error message, for a reference:
Test failed. See exception logs above.
The test description was: Should test widget with http call
Warning: At least one test in this suite creates an HttpClient. When
running a test suite that uses TestWidgetsFlutterBinding, all HTTP
requests will return status code 400, and no network request will
actually be made. Any test expecting a real network connection and
status code will fail.
To test code that needs an HttpClient, provide your own HttpClient
implementation to the code under test, so that your test can
consistently provide a testable response to the code under test.
Solution
The error tells you what the problem is: you must not execute HTTP calls in the widget tests. So you need to mock that HTTP call out, so that the mock is called instead of the real HTTP call. There are many options with which you can do that, e.g. using the mockito package.
Here a possible solution using the nock package which simulates an HTTP response at the HTTP level.
pubspec.yaml:
dev_dependencies:
nock: ^1.1.2
Widget test:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:nock/nock.dart';
import 'package:widget_test/snippet.dart';
void main() {
setUpAll(nock.init);
setUp(() {
nock.cleanAll();
});
testWidgets('Should test widget with http call', (WidgetTester tester) async {
nock('https://run.mocky.io')
.get('/v3/49c23ebc-c107-4dae-b1c6-5d325b8f8b58')
.reply(200, json.encode('{"id": "49c23ebc-c107-4dae-b1c6-5d325b8f8b58", "name": "Example campus" }'));
var booking = <String, String>{
'campus_id': '2f4fccd2-e199-4989-bad3-d8c48e66a15e'
};
await tester.pumpWidget(TestApp(BookingDetails(booking)));
expect(find.text('Loading...'), findsOneWidget);
await tester.pump();
expect(find.text('Displaying data'), findsOneWidget);
});
}
class TestApp extends StatelessWidget {
final Widget child;
TestApp(this.child);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: child,
);
}
}
Here is how I am trying to read data from a single document in Firestore.
final CollectionReference myCollection =
FirebaseFirestore.instance.collection('collection');
//
Future getScore(String level) async {
var document = await myCollection.doc(uid).get();
for (int i = 0; i < document.data().length; i++) {
print(document.data().keys.elementAt(i));
print(document.data().values.elementAt(i));
}
}
I call this code on press of a button like this:
Expanded(
flex: 1,
child: Container(
alignment: Alignment.centerLeft,
child: ElevatedButton(
onPressed: () {
//////////////////////////////////////////////////////////////////////
// This is where I call the getScore function in database dart file //
//////////////////////////////////////////////////////////////////////
DatabaseService(uid: globals.uid).getScore(
'level11',
);
/////////////////////////////////////////////////////////////////////////////////////////////////
// I would like the circular indicator until data is fetched and before the new view is pushed //
/////////////////////////////////////////////////////////////////////////////////////////////////
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) => GameHome()));
},
child: Text(
'Play',
style: Theme.of(context).textTheme.headline2,
),
style: OutlinedButton.styleFrom(
shape: StadiumBorder(),
padding: EdgeInsets.symmetric(
horizontal: 5.25 * SizeConfig.textMultiplier,
vertical: 1.125 * SizeConfig.textMultiplier),
side: BorderSide(width: 1, color: Colors.black26),
backgroundColor: Colors.black,
),
),
),
),
I was wondering how can I show the circular progress indicator until the data is fetched and the view switch code is executed.
Thanks for any pointers.
Make your Widget a StatefullWidget and set a boolean value isLoading to false.
Make sure that your DatabaseService is asynchronous and await it.
If the value of isLoading is true show a CircularProgressIndicator.
Here is a basic example:
import 'package:flutter/material.dart';
class YourWidget extends StatefulWidget {
#override
_YourWidgetState createState() => _YourWidgetState();
}
class _YourWidgetState extends State<YourWidget> {
bool isLoading = false;
Future getScore() async {
setState(() {
isLoading = true;
});
//////////////////////////////////////////////////////////////////////
// This is where I call the getScore function in database dart file //
//////////////////////////////////////////////////////////////////////
await DatabaseService(uid: globals.uid).getScore(
'level11',
);
/////////////////////////////////////////////////////////////////////////////////////////////////
// I would like the circular indicator until data is fetched and before the new view is pushed //
/////////////////////////////////////////////////////////////////////////////////////////////////
setState(() {
isLoading = false;
});
Navigator.push(
context, CupertinoPageRoute(builder: (context) => GameHome()));
}
#override
Widget build(BuildContext context) {
return Expanded(
flex: 1,
child: Container(
alignment: Alignment.centerLeft,
child: Column(
children: [
ElevatedButton(
onPressed: getScore,
child: Text(
'Play',
style: Theme.of(context).textTheme.headline2,
),
style: OutlinedButton.styleFrom(
shape: StadiumBorder(),
padding: EdgeInsets.symmetric(
horizontal: 5.25 * SizeConfig.textMultiplier,
vertical: 1.125 * SizeConfig.textMultiplier),
side: BorderSide(width: 1, color: Colors.black26),
backgroundColor: Colors.black,
),
),
Visibility(visible: isLoading, child: CircularProgressIndicator())
],
),
),
);
}
}
Make your Widget a StatefullWidget and set a boolean value isLoading to false.
Make sure that your DatabaseService is asynchronous and await it.
If the value of isLoading is true show a CircularProgressIndicator.
Edit: You don't need to setState(){isLoading=false} because once you push to a new screen the state will be updated. Thus avoids building the screen everytime
Here is a basic example:
import 'package:flutter/material.dart';
class YourWidget extends StatefulWidget {
#override
_YourWidgetState createState() => _YourWidgetState();
}
class _YourWidgetState extends State<YourWidget> {
bool isLoading = false;
Future getScore() async {
setState(() {
isLoading = true;
});
//////////////////////////////////////////////////////////////////////
// This is where I call the getScore function in database dart file //
//////////////////////////////////////////////////////////////////////
await DatabaseService(uid: globals.uid).getScore(
'level11',
);
/////////////////////////////////////////////////////////////////////////////////////////////////
// I would like the circular indicator until data is fetched and before the new view is pushed //
/////////////////////////////////////////////////////////////////////////////////////////////////
Navigator.push(
context, CupertinoPageRoute(builder: (context) => GameHome()));
}
#override
Widget build(BuildContext context) {
return Expanded(
flex: 1,
child: Container(
alignment: Alignment.centerLeft,
child: Column(
children: [
ElevatedButton(
onPressed: getScore,
child: Text(
'Play',
style: Theme.of(context).textTheme.headline2,
),
style: OutlinedButton.styleFrom(
shape: StadiumBorder(),
padding: EdgeInsets.symmetric(
horizontal: 5.25 * SizeConfig.textMultiplier,
vertical: 1.125 * SizeConfig.textMultiplier),
side: BorderSide(width: 1, color: Colors.black26),
backgroundColor: Colors.black,
),
),
Visibility(visible: isLoading, child: CircularProgressIndicator())
],
),
),
);
}
}
I'm currently developing a Fingerspelling learning app. In the fingerspelling_screen.dart file, user will be able to choose which category that he/she would like to learn first. When the user chooses either one of the button, the app will query Firebase Firestore to get a list of signs object which contains the name of the sign, the category to which the sign belongs to and the video URL of each sign before navigating user to sign_video.dart which is a video player. From there, user can press next to switch to the next sign in the category. When the user presses on the check sign button, the user will be navigating to the check sign_checker.dart to check if they are performing a sign correctly.
I've been passing the 'category' variable from one screen to another using constructor and I do not think it is very effective. Is there any way I could solve this?
I wish to initilize vidlist variable (a variable that stores a list of Sign objects) in the initState because the video player controller needs to be initialized first. I've tried using StreamProvider, but for some reason, I couldn't initialize the vidlist variable in initState. The vidlist variable would always be null.
Thank you.
fingerspelling.dart
import 'package:slem_proto/screens/sign_video.dart';
import 'package:slem_proto/shared/constants.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:undraw/undraw.dart';
class FingerspellingScreen extends StatelessWidget {
static String routeName = 'fingerspelling_screen';
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.only(left: 20, right: 20, top: 50),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Fingerspelling',
style: kHeadingTextStyle,
),
SizedBox(height: 30),
FingerspellingTab(
title: 'Alphabets',
illustration: UnDrawIllustration.learning,
onTap: () {
print('Alphabet tab tapped');
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SignVideoScreen(
category: 'alphabets',
),
),
);
},
),
SizedBox(
height: 15,
),
FingerspellingTab(
title: 'Numbers',
illustration: UnDrawIllustration.calculator,
onTap: () {
print('Number tab tapped');
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SignVideoScreen(
category: 'numbers',
),
),
);
},
),
],
),
),
);
}
}
class FingerspellingTab extends StatelessWidget {
final String title;
final UnDrawIllustration illustration;
final Function onTap;
const FingerspellingTab(
{#required this.title,
#required this.illustration,
#required this.onTap});
#override
Widget build(BuildContext context) {
return InkWell(
child: Container(
width: double.infinity,
height: 250,
decoration: BoxDecoration(
color: Color.fromRGBO(58, 139, 238, 0.2),
borderRadius: BorderRadius.circular(20),
),
child: Padding(
padding: const EdgeInsets.only(left: 20, top: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: kHeadingTextStyle.copyWith(
color: Color.fromRGBO(80, 80, 80, 0.8),
fontSize: 25,
),
),
SizedBox(
height: 15,
),
Container(
height: 150,
child: UnDraw(
color: Color(0xFF6C63FF),
illustration: illustration,
placeholder: Text(
"Illustration is loading..."), //optional, default is the CircularProgressIndicator().
),
),
],
),
),
),
onTap: onTap,
);
}
}
database.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:slem_proto/models/sign.dart';
class DatabaseService {
// collection reference
final CollectionReference signCollection =
FirebaseFirestore.instance.collection('signs');
// Sign list from snapshot
List<Sign> _signListFromSnapshot(QuerySnapshot snapshot) {
return snapshot.docs.map((doc) {
return Sign(
category: doc.data()['category'] ?? '',
sign_name: doc.data()['sign_name'] ?? '',
sign_url: doc.data()['sign_url'] ?? '',
);
}).toList();
}
// get signs stream
Stream<List<Sign>> get signs {
return signCollection.snapshots().map(_signListFromSnapshot);
}
// get signs stream
Stream<List<Sign>> getSignFromCategory({String category}) {
return signCollection
.where('category', isEqualTo: category)
.snapshots()
.map(_signListFromSnapshot);
}
}
sign_checker.dart
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:image_picker/image_picker.dart';
class SignChecker extends StatefulWidget {
final String category;
SignChecker({this.category});
#override
_SignCheckerState createState() => _SignCheckerState(category: this.category);
}
class _SignCheckerState extends State<SignChecker> {
final String category;
_SignCheckerState({this.category});
File _image;
bool predictionStarted = false;
bool predictionComplete = false;
var predictionResult = 'Please wait...';
Future getImage() async {
setState(() {
predictionStarted = false;
predictionComplete = false;
});
// Get image from camera
// var image = await ImagePicker.pickImage(source: ImageSource.camera);
// Get image from gallery
var image = await ImagePicker.pickImage(source: ImageSource.gallery);
setState(() {
_image = image;
predictionStarted = true;
});
// Base64 Encode the image
List<int> imageBytes = image.readAsBytesSync();
String base64Image = base64.encode(imageBytes);
// Print the base64 encoded string in console
print(base64Image);
// Send the encoded image with POST request
Map<String, String> headers = {"Accept": "application/json"};
Map body = {"image": base64Image};
// var response = await http.post('http://XX.XXX.XXX.X/automl.php',
// body: body, headers: headers);
var response = await http.post('http://XX.XXX.XXX.X/automl_alphabet.php',
body: body, headers: headers);
// Print the status code returned by server
print('Status code');
print(response.statusCode);
// Get prediction Result
setState(() {
predictionResult = response.body;
predictionComplete = true;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Sign Checker'),
),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
SizedBox(
height: 20,
),
Text(
'Push the camera button',
textAlign: TextAlign.center,
),
RaisedButton(
onPressed: getImage,
child: Text('Camera'),
),
(_image != null)
? Image.file(
_image,
scale: 50,
)
: Text('No Image Picked'),
predictionBody()
],
),
),
);
}
Widget predictionBody() {
var predictionText = (predictionComplete) ? 'Result' : 'Prediction started';
if (predictionStarted) {
return Column(
children: <Widget>[
Divider(),
Text(predictionText),
Text(predictionResult)
],
);
} else {
return Container();
}
}
}
sign.dart
class Sign {
final String category;
final String sign_name;
final String sign_url;
Sign({this.category, this.sign_name, this.sign_url});
}
Firstly, using constructors to pass variables may not be inefficient, since Flutter only pass on references. Say,
var a = List(...huge list...);
var b = a;
Then the second line is not costly.
Secondly, if you ask about state management, you may try Mobx, Bloc, Redux, etc. There are many ways to do so.
I want use this loading widget : https://pub.dev/packages/flutter_spinkit
But i would like to know how i can center horizontally and vertically this widget whatever the content displayed (simple container it is simple but when there is futurebuilder and a lot of widget displayed in column it is more complicated).
EDIT : My code is Under :
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'menu_member.dart';
import 'globals.dart' as globals;
import 'appbar_draw.dart';
import 'package:awesome_dialog/awesome_dialog.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:firebase_admob/firebase_admob.dart';
import 'package:intl/intl.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
const testDevices = "Your_DEVICE_ID";
// Create a Form widget.
class Affiche_Liste_Tombola extends StatefulWidget {
#override
_Affiche_Liste_Tombola_State createState() {
return _Affiche_Liste_Tombola_State();
}
}
// Create a corresponding State class.
// This class holds data related to the form.
class _Affiche_Liste_Tombola_State extends State<Affiche_Liste_Tombola> {
#override
static const MobileAdTargetingInfo targetingInfo = MobileAdTargetingInfo(testDevices: testDevices!=null ? <String> ['tessDevices'] : null,keywords: <String> ['game','bet'],nonPersonalizedAds: true);
RewardedVideoAd VideoAd = RewardedVideoAd.instance;
bool load=false;
bool visible=false;
String tombola_select="";
Future<List<Tombola>> ListTombola;
Future <List<Tombola>> Tombola_display() async {
// SERVER LOGIN API URL
var url = 'https://www.easytrafic.fr/game_app/display_list_tombola.php';
var data = {
'id_membre': globals.id_membre,
};
var data_encode = jsonEncode(data);
// Starting Web API Call.
var response = await http.post(url,body: data_encode,headers: {'content-type': 'application/json','accept': 'application/json','authorization': globals.token});
// Getting Server response into variable.
var jsondata = json.decode(response.body);
List<Tombola> Tombolas = [];
for (var u in jsondata) {
Tombola tombola = Tombola(u["id"],u["libelle"],u["date_debut_validation"],u["gain"],u["nb_tickets_achat"],u["nb_tickets_total"],u["nb"]);
Tombolas.add(tombola);
}
return Tombolas;
}
void initState() {
super.initState();
//FirebaseAdMob.instance.initialize(appId: "ca-app-pub-8677431175756102~8892789953");
FirebaseAdMob.instance.initialize(appId: FirebaseAdMob.testAppId);
VideoAd.listener=(RewardedVideoAdEvent event,{String rewardType, int rewardAmount}) {
print('REWARDED VIDEO ADS $event');
if (event == RewardedVideoAdEvent.rewarded) {
setState(() {
load=false;
});
Valide_tombola(tombola_select);
}
if (event == RewardedVideoAdEvent.loaded) {
print('VIDEO LOADED !!!');
load=true;
visible=false;
VideoAd.show();
}
};
ListTombola = Tombola_display();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: drawappbar(true),
drawer: new DrawerOnly(className: Affiche_Liste_Tombola()),
body:
Center(
child:
Column(
children: <Widget>[
Visibility(
maintainSize: true,
maintainAnimation: true,
maintainState: true,
visible: visible,
child: Container(
child: SpinKitChasingDots(color: Colors.blueAccent,size:200)
)
),
Container(
height: MediaQuery.of(context).size.height*0.8,
width: MediaQuery.of(context).size.width*0.8,
child:
FutureBuilder(
future: ListTombola,
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
setState(() {
visible=true;
});
break;
default:
if (snapshot.hasError) {
return new Center(
child: new Text('Error: ${snapshot.error}'),);
}
else {
List<Tombola> values = snapshot.data;
if (values.isEmpty) {
return Container(
child: Center(
child: Text("Aucune tombola disponible !!!")
)
);
}
else {
return ListView.builder(itemCount: values.length,
itemBuilder: (_, index) {
var mt_jackpot=double.parse(values[index].gain);
var f = new NumberFormat.currency(locale: "fr-FR",symbol: "");
var mt_jackpot_f=f.format(mt_jackpot);
return Column(
children: [
Container(
height: 30,
width: MediaQuery.of(context).size.width*90/100,
margin : EdgeInsets.only(top:20),
decoration: BoxDecoration(
border: Border.all(
color: Colors.blue[700],
width: 2,
),
borderRadius: BorderRadius.only(topLeft : Radius.circular(25),topRight: Radius.circular(25)),
color: Colors.blue[700]
),
child : new Text(values[index].libelle,textAlign: TextAlign.center,style: TextStyle(fontSize: 18.0,fontWeight: FontWeight.w500,color: Colors.white),),
),
Center(
child : Container(
height:MediaQuery.of(context).size.height/4,
width: MediaQuery.of(context).size.width*90/100,
margin: EdgeInsets.only(bottom:20),
decoration: BoxDecoration(
border: Border.all(
color: Colors.grey[500],
width: 2,
),
borderRadius: BorderRadius.only(bottomLeft : Radius.circular(25),bottomRight: Radius.circular(25)),
color:Colors.white,
),
child :
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
margin : EdgeInsets.only(bottom:10),
child : RichText(
text: TextSpan(
children: [
TextSpan(
text: mt_jackpot_f,
style: TextStyle(fontSize: 20,fontWeight: FontWeight.w800,color: Colors.black)
),
WidgetSpan(
child: Icon(FontAwesomeIcons.euroSign,color: Colors.amber[900],size:20)
),
],
),
),
),
Container(
margin : EdgeInsets.only(bottom:10),
child :
Text("Vos tickets : "+values[index].nb_tickets_membre)
),
Container(
child : RaisedButton(
color: Colors.green,
textColor: Colors.white,
padding: EdgeInsets.fromLTRB(
9, 9, 9, 9),
child: Text(
'PARTICIPEZ'),
onPressed: () {
tombola_select=values[index].id;
setState(() {
visible=true;
});
VideoAd.load(
adUnitId: RewardedVideoAd.testAdUnitId,
targetingInfo: targetingInfo);
},
),
)
]
),
),
)
]
);
}
);
}
}
}
}
)
)
]
)
)
);
}
Future Valide_tombola(String id_tombola) async{
// For CircularProgressIndicator.
bool visible = false ;
// Showing CircularProgressIndicator.
setState(() {
visible = true ;
});
// SERVER LOGIN API URL
var url = 'https://www.easytrafic.fr/game_app/valide_tombola.php';
// Store all data with Param Name.
var data = {'id_membre':globals.id_membre, 'id_tombola':id_tombola};
var tombola_encode=jsonEncode(data);
// Starting Web API Call.
var response = await http.post(url, body: tombola_encode,headers: {'content-type': 'application/json','accept': 'application/json','authorization': globals.token});
// Getting Server response into variable.
Map <String,dynamic> map2 = json.decode(response.body);
// If the Response Message is Matched.
if(map2["status"] == 1)
{
setState(() {
ListTombola=Tombola_display();
});
AwesomeDialog(context: context,
useRootNavigator: true,
dialogType: DialogType.SUCCES,
animType: AnimType.BOTTOMSLIDE,
tittle: 'VALIDATION',
desc: map2["message"],
btnOkOnPress: () {
}).show();
}else{
// Showing Alert Dialog with Response JSON Message.
AwesomeDialog(context: context,
useRootNavigator: true,
dialogType: DialogType.ERROR,
animType: AnimType.BOTTOMSLIDE,
tittle: 'ERREUR',
desc: map2["message"],
btnOkOnPress: () {
}).show();
}
}
}
class Tombola {
final String id;
final String libelle;
final String datedebut;
final String gain;
final String nb_tickets_achat;
final String nb_tickets_total;
final String nb_tickets_membre;
const Tombola(this.id, this.libelle, this.datedebut,this.gain,this.nb_tickets_achat,this.nb_tickets_total,this.nb_tickets_membre);
}
As you can see the loader is here :
Visibility(
maintainSize: true,
maintainAnimation: true,
maintainState: true,
visible: visible,
child: Container(
child: SpinKitChasingDots(color: Colors.blueAccent,size:200)
)
),
problem : it doesn't work because this code make it is overflowed by 123 pixel and because it is not centered horizontally and vertically
Sorry I'm new to flutter and trying to learn it. I have an issue with RefreshIndicator.
When I try pulling it I got an error like below :
════════ Exception caught by material library ══════════════════════════════════════════════════════
The following assertion was thrown when calling onRefresh:
The onRefresh callback returned null.
The RefreshIndicator onRefresh callback must return a Future.
════════════════════════════════════════════════════════════════════════════════════════════════════
Currently I am using flutter_bloc. Here is my sample code
TableList_bloc.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:pos_project_bloc/modal/api.dart';
class ModalTableList {
final String Table_Number;
final String Name_Table;
ModalTableList(
this.Table_Number,
this.Name_Table,
);
}
class tablelistbloc extends Bloc<bool, List<ModalTableList>>{
#override
// TODO: implement initialState
List<ModalTableList> get initialState => [];
#override
Stream<List<ModalTableList>> mapEventToState(bool event) async* {
// TODO: implement mapEventToState
List<ModalTableList> tablelist =[];
try {
final response = await http.get(BaseUrl.GetTableList);
final data = jsonDecode(response.body);
if (data.length != 0) {
data.forEach((api) {
tablelist.add(
ModalTableList(
api['Table_Number'],
api['Name_Table'],
)
);
print("test"+api['Table_Number'].toString());
});
print("Get Table List : sukses");
} else {
print('data kosong');
}
} catch (e) {
print("Error GetTableList :");
print(e);
}
yield tablelist;
}
}
TabeList.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pos_project_bloc/bloc/TableList_bloc.dart';
import 'package:pos_project_bloc/modal/SharedPreferences.dart';
class TableList extends StatefulWidget {
#override
_TableListState createState() => _TableListState();
}
class _TableListState extends State<TableList> {
#override
void initState() {
super.initState();
BlocProvider.of<tablelistbloc>(context).add(true);
}
Widget build(BuildContext context) {
final GlobalKey<RefreshIndicatorState> refresh = GlobalKey<RefreshIndicatorState>();
Future<Null> _refresh() async{
BlocProvider.of<tablelistbloc>(context).add(true);
}
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.lightBlueAccent,
title: new Center(
child: new Text('Available Table',
style: new TextStyle(color: Colors.white, fontSize: 15.0)),
)
),
floatingActionButton: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Padding(
padding: EdgeInsets.all(5.0),
child: FloatingActionButton.extended(
heroTag: 1,
icon: Icon(Icons.refresh),
label: Text('Refresh Table List'),
onPressed: () {
BlocProvider.of<tablelistbloc>(context).add(true);
},
),
)
],
),
),
body: RefreshIndicator(
onRefresh: (){
_refresh();
},
key: refresh,
child: BlocBuilder<tablelistbloc,List<ModalTableList>>(
builder: (context,tablelist)=> ListView.builder(
itemCount: tablelist.length,
itemBuilder: (context,index){
final x = tablelist[index];
return GestureDetector(
onTap: (){
print("Selected Available Table On Tap : " + x.Table_Number);
Shared_Preferences().SaveTableNumber(
x.Table_Number
);
Navigator.pushReplacementNamed(context, '/POS');
},
child: Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 2.0,
color: Colors.grey.withOpacity(0.4),
))),
padding: EdgeInsets.all(10.0),
child: Row(
children: <Widget>[
Icon(
Icons.table_chart,
size: 100.0,
color: Colors.lightBlueAccent,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'Table Number : ' + x.Table_Number,
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
color: Colors.lightBlueAccent),
),
Text(
'Name : ' + x.Name_Table,
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
color: Colors.lightBlueAccent),
),
],
)
],
),
),
);
},
),
),
)
);
}
}
Just add async.
onRefresh: () async {
GetTableList.add(true);
},
onRefresh is a RefreshCallback, and RefreshCallback is a Future Function().
So if GetTableList.add(true) not return Future, must to add async.
it is says onRefresh callback must return a Future.
and it is seems like your are not returning Future from onRefresh
onRefresh: _refresh
Future<Null> _refresh() async{
GetTableList.add(true);
}
hope it helps..
You can problably use the following:
Future<bool> refresh() async {
//your refresh code
return true;
}