Related
I have managed to compile the code below but know that I can display the opening hours using a better method. I believe it can be done using Future Builder but I am not experienced as yet. The code below loads the items and then goes and find out the opening hours. Can anyone assist to optimize this code so It shows on first instance. I believe there are performance issues with the code below.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:intl/intl.dart';
import 'package:rapideats/global/global.dart';
import '../mainScreens/menus_screen.dart';
import '../models/sellers.dart';
class SellersDesignWidget extends StatefulWidget {
Sellers? model;
BuildContext? context;
SellersDesignWidget({this.model,this.context});
String status = "";
#override
State<SellersDesignWidget> createState() => _SellersDesignWidgetState();
}
class _SellersDesignWidgetState extends State<SellersDesignWidget> {
var status = "Closed";
getOpeningHours() async
{
var date = DateTime.now();
var from = "";
var to = "";
var TodaysDate = DateFormat('EEEE').format(date).substring(0,3).toLowerCase();
// prints mon,tue etc.
QuerySnapshot oh = await FirebaseFirestore.instance.collection("openingHours")
.where("sellerUID", isEqualTo:widget.model!.sellerUID!)
.get();
if(oh.docs.isNotEmpty)
{
for (int i=0; i < oh.docs.length; i++)
{
from = oh.docs[i][TodaysDate+"From"].toString();
to = oh.docs[i][TodaysDate+"To"].toString();
}
if(from == "00:00" && to == "00:00")
{
setState(() {
status = "Closed";
} );
}else
{
setState(() {
status = "Open Now: " + TodaysDate.toTitleCase() + " " + from + " - " + to + "Hrs";
} );
}
}
}
void initState(){
super.initState();
getOpeningHours();
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child : Padding(
padding: const EdgeInsets.all(6.0),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(6.0),
child: Image.network(widget.model!.sellerAvatarUrl!,
height: 150,
width: 150,
fit:BoxFit.cover,
),
),
const SizedBox(width: 10.0,),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 20,
),
Row(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: Text(
widget.model!.sellerName!.toTitleCase(),
style: const TextStyle(
color:Colors.black,
fontSize: 24,
fontFamily:"Acme",
),
),
),
const SizedBox(
width: 10,
),
],
),
const SizedBox(
height: 20,
),
Row(
children: [
Text(
status,
style: TextStyle(
color: Colors.grey,
fontSize: 14,
),
),
],
),
const SizedBox(
height: 20,
),
Row(
children: [
Padding(
padding: const EdgeInsets.all(4.0),
child:
status == "Closed"
? const Text(
"",
)
: ElevatedButton(
child: const Text("Order Now"),
style: ElevatedButton.styleFrom(
backgroundColor:Colors.blueAccent,
),
onPressed: ()
{
Navigator.push(context, MaterialPageRoute(builder:(c)=> MenusScreen(model:widget.model)));
},
)
),
]
),
],
),
),
],
),
),
);
}
}
I recommend using a state management package to handle your UI states in an easier and cleaner way.
As an example you can use Bloc or Provider (There's more packages)
But as an answer for your question, and to handle your state using FutureBuilder to optimize your UI Performance you can use this code
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:intl/intl.dart';
import 'package:rapideats/global/global.dart';
import '../mainScreens/menus_screen.dart';
import '../models/sellers.dart';
/// We Changed it to StatelessWidget as we don't need to rebuild the whole screen again
class SellersDesignWidget extends StatelessWidget {
final Sellers? model;
SellersDesignWidget({this.model});
/// We will use the same function with some modification to get the data and return the value to the FutureBuilder
Future<String> getOpeningHours() async
{
String status = "Closed";
DateTime date = DateTime.now();
String from = "";
String to = "";
String todaysDate = DateFormat('EEEE').format(date).substring(0, 3).toLowerCase();
// prints mon,tue etc.
QuerySnapshot oh = await FirebaseFirestore.instance.collection("openingHours")
.where("sellerUID", isEqualTo: model!.sellerUID!)
.get();
if (oh.docs.isNotEmpty) {
for (int i = 0; i < oh.docs.length; i++) {
from = oh.docs[i][todaysDate + "From"].toString();
to = oh.docs[i][todaysDate + "To"].toString();
}
if (from == "00:00" && to == "00:00") {
status = "Closed";
} else {
status = "Open Now: " + todaysDate.toTitleCase() + " " + from + " - " + to + "Hrs";
}
}
return status;
}
#override
Widget build(BuildContext context) {
/// As most of your UI doesn't depend on the data from Firebase so it can be shown directly
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(6.0),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(6.0),
child: Image.network(model!.sellerAvatarUrl!,
height: 150,
width: 150,
fit: BoxFit.cover,
),
),
const SizedBox(width: 10.0,),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 20,
),
Row(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: Text(
model!.sellerName!.toTitleCase(),
style: const TextStyle(
color: Colors.black,
fontSize: 24,
fontFamily: "Acme",
),
),
),
const SizedBox(
width: 10,
),
],
),
const SizedBox(
height: 20,
),
/// For the part needs the remote data we wrap it in future builder
FutureBuilder(
future: getOpeningHours(),
builder: (ctx, AsyncSnapshot<String?> status) {
/// When data is loading(status == null) we display a progress indicator and prevent the user from performing any actions
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
status.data == null ?
//Change SizedBox Size if needed
SizedBox(
width: 30, child: CircularProgressIndicator(),)
: Text(
status.data!,
style: TextStyle(
color: Colors.grey,
fontSize: 14,
),
),
],
),
const SizedBox(
height: 20,
),
Row(
children: [
Padding(
padding: const EdgeInsets.all(4.0),
child:
status.data == "Closed"
? const Text(
"",
)
: ElevatedButton(
child: status.data == null ?
//Change SizedBox Size if needed
SizedBox(
width: 30, child: CircularProgressIndicator(),) : Text("Order Now"),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blueAccent,
),
onPressed: () {
if (status.data != null) {
Navigator.push(context,
MaterialPageRoute(builder: (c) => MenusScreen(model: model)));
}
},
)
),
]
),
],
);
}),
],
),
),
],
),
),
);
}
}
The cleanest way is: 1 - You show the first paint and give a condition to the opening hours block. If hours have been fetched, display them, else, show a progress indicator or a loading text or any kind of indication to the user that something is being loaded.
2- Then, in the init state, don't forget to set haveFetched to false when you call the function, and to true when it finishes, you call the fetch function. You need to handle possible errors as well by giving it some sort of default times or 'error' text. As Hesham Erfan mentioned, if you have a UI heavy design, you should use a state management solution and stateless widgets since it will improve performance. I recommend:1 - getx for small projects (fast to code, also includes navigator, context-less toasts, snackbars, screen width and height and more!), 2 - provider all around good state manager (very popular good docs, also easy to learn), and 3 - bloc for corporate (it has a lot of boiler plate but a very similar structure all around). You would have to move the init state logic to the state managing solution you implement.
I'm new to flutter and graphql
I'm trying to develop an app to consume backend api hosted on heroku
during development and while testing the app on my Samsung Note 5, the app connects successfully to the backend and I can fetch data from query and mutation
but after building the app-release.apk and installing it on the same mobile, the response is always null in both the query and mutation.
Also, I have REST api within the backend and I use Dio lib to send requests but I don't get any response from these requests.
I tried "flutter clean" but in vain
flutter doctor command shows no error:
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 2.0.3, on Microsoft Windows [Version 10.0.18363.1646], locale en-US)
[√] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[√] Chrome - develop for the web
[√] Android Studio
[√] VS Code (version 1.54.3)
[√] Connected device (2 available)
• No issues found!
I'm using android studio 4.2.2
main.dart
bool isAuth;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await initHiveForFlutter();
isAuth = await isAuthenticated();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GraphQLProvider(
client: client,
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter App',
theme: ThemeData(
primarySwatch: Colors.red,
),
initialRoute: isAuth ? 'HomeScreen' : 'LoginScreen',
routes: {
'HomeScreen': (context) => HomeScreen(),
'LoginScreen': (context) => LoginScreen()
}),
);
}
}
authentication.dart
// check if token is set using sharedPreferences lib
Future<bool> isAuthenticated() async {
final accessToken = await getAccessToken();
if (accessToken != null) {
return true;
}
return false;
}
GraphQLConfiguration.dart:
class GraphQLConfiguration {
HttpLink _httpLink;
AuthLink _authLink;
Link _link;
GraphQLConfiguration () {
_httpLink = new HttpLink('https://backendapi.herokuapp.com/graphql');
_authLink = new AuthLink(getToken: () async => 'Bearer ${await getAccessToken()}');
_link = _authLink.concat(_httpLink);
}
GraphQLClient myGQLClient() {
return GraphQLClient(
link: _link,
cache: GraphQLCache(store: HiveStore()),
);
}
}
ValueNotifier<GraphQLClient> client = ValueNotifier(GraphQLConfiguration().myGQLClient());
mutations.dart
String login() {
return '''
mutation login(\$data: LoginInput!){
login(data: \$data){
status
message
accessToken
}
}
''';
}
LoginScreen.dart
class LoginScreen extends StatefulWidget {
#override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
bool _hidePassword = true;
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Stack(
children: [
Mutation(
options: MutationOptions(
document: gql(mutations.login()),
update: (GraphQLDataProxy cache, QueryResult result) {
String cookie;
final String cookieHeader = result.context
.entry<HttpLinkResponseContext>()
.headers['set-cookie'];
if (cookieHeader != null) {
cookie = cookieHeader.split(';')[0];
}
final LoginResponse loginResponse =
LoginResponse.from(result.data['login']);
if (loginResponse.status == true && cookie != null) {
setRefreshToken(cookie);
}
return cache;
},
onCompleted: (resultData) async {
final LoginResponse loginResponse =
LoginResponse.from(resultData['login']);
if (loginResponse.status == true) {
await setAccessToken(loginResponse.accessToken);
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => HomeScreen()));
}
Fluttertoast.showToast(
msg: loginResponse.message,
backgroundColor: Colors.grey,
gravity: ToastGravity.BOTTOM,
fontSize: 16.0,
timeInSecForIosWeb: 1,
textColor: Colors.white,
toastLength: Toast.LENGTH_LONG,
);
}),
builder: (RunMutation runMutation, QueryResult result) {
return Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
margin: EdgeInsets.only(top: 100),
child: Column(
children: [
Text(
'Welcome...',
style: TextStyle(
shadows: [
Shadow(
offset: Offset(0.5, 0.5),
blurRadius: 10.0,
color: Colors.grey,
),
],
fontSize: 45,
letterSpacing: 2,
fontWeight: FontWeight.bold,
),
),
Text(
'welcome,,,',
style: TextStyle(
fontSize: 20,
letterSpacing: 7,
),
)
],
),
),
Container(
child: Column(
children: [
Padding(
padding:
EdgeInsets.symmetric(vertical: 0, horizontal: 20),
child: TextField(
controller: _usernameController,
style: TextStyle(
fontSize: 22.0,
),
decoration: InputDecoration(
labelText: 'Username',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(50.5),
),
prefixIcon:
Icon(Icons.supervised_user_circle_outlined),
),
),
),
Padding(
padding: EdgeInsets.symmetric(
vertical: 10, horizontal: 20),
child: TextField(
controller: _passwordController,
obscureText: _hidePassword,
style: TextStyle(
fontSize: 22.0,
),
decoration: InputDecoration(
labelText: 'Password',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(50.5),
),
prefixIcon: Icon(Icons.lock),
suffixIcon: IconButton(
icon: Icon(_hidePassword
? Icons.visibility
: Icons.visibility_off),
onPressed: () => _togglePasswordVisibility(),
),
),
),
),
Padding(
padding: EdgeInsets.only(top: 30),
child: ElevatedButton(
style: ButtonStyle(
padding: MaterialStateProperty.all<EdgeInsets>(
EdgeInsets.all(15)),
minimumSize:
MaterialStateProperty.all<Size>(Size(370, 0)),
shape: MaterialStateProperty.all<
RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50.0),
side: BorderSide(color: Colors.red),
),
),
backgroundColor:
MaterialStateProperty.all<Color>(Colors.red),
),
onPressed: () => runMutation(
{
'data': {
'username': _usernameController.text,
'password': _passwordController.text
}
},
),
child: Text(
'Login',
style: TextStyle(
fontSize: 30.0,
),
),
),
),
],
),
),
Container(
margin: EdgeInsets.only(top: 100),
padding: EdgeInsets.only(bottom: 20),
child: Center(
child: Text(
'BY XXX',
style: TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold,
fontSize: 10,
),
),
),
),
],
);
},
),
],
),
);
}
void _togglePasswordVisibility() {
setState(() {
_hidePassword = !_hidePassword;
});
}
pubspec.yaml
environment:
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
graphql_flutter: ^5.0.0
shared_preferences: ^2.0.6
http: ^0.13.3
fluttertoast: ^8.0.7
font_awesome_flutter: ^9.1.0
dio: ^4.0.0
flutter_datetime_picker: ^1.5.1
loading_overlay: ^0.3.0
cupertino_icons: ^1.0.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
assets:
- assets/images/male.png
- assets/images/female.png
fonts:
- family: Changa
fonts:
- asset: fonts/Changa-Regular.ttf
- asset: fonts/Changa-Bold.ttf
weight: 700
As I said, I'm new to flutter and mobile app development
and the solution was obtained from this thread
The problem wasn't related to flutter_graphql lib but to the manifest file of the android project.
internet permission should be declared.
I supposed flutter will auto-detect and configure the app permissions based on the used libraries, but I was wrong.
Good Morning,
I'm trying to put a Carousel on the home page looking for Firebase data, but for some reason, the first time I load the application it appears the message below:
════════ Exception caught by widgets library ═════════════════════════════════════ ══════════════════
The following _CastError was thrown building DotsIndicator (animation: PageController # 734f9 (one client, offset 0.0), dirty, state: _AnimatedState # 636ca):
Null check operator used on a null value
and the screen looks like this:
After giving a hot reload the error continues to appear, but the image is loaded successfully, any tips of what I can do?
HomeManager:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provantagens_app/models/section.dart';
class HomeManager extends ChangeNotifier{
HomeManager({this.images}){
_loadSections();
images = images ?? [];
}
void addSection(Section section){
_editingSections.add(section);
notifyListeners();
}
final List<dynamic> _sections = [];
List<String> images;
List<dynamic> newImages;
List<dynamic> _editingSections = [];
bool editing = false;
bool loading = false;
int index, totalItems;
final Firestore firestore = Firestore.instance;
Future<void> _loadSections() async{
loading = true;
firestore.collection('home').snapshots().listen((snapshot){
_sections.clear();
for(final DocumentSnapshot document in snapshot.documents){
_sections.add( Section.fromDocument(document));
images = List<String>.from(document.data['images'] as List<dynamic>);
}
});
loading = false;
notifyListeners();
}
List<dynamic> get sections {
if(editing)
return _editingSections;
else
return _sections;
}
void enterEditing({Section section}){
editing = true;
_editingSections = _sections.map((s) => s.clone()).toList();
defineIndex(section: section);
notifyListeners();
}
void saveEditing() async{
bool valid = true;
for(final section in _editingSections){
if(!section.valid()) valid = false;
}
if(!valid) return;
loading = true;
notifyListeners();
for(final section in _editingSections){
await section.save();
}
for(final section in List.from(_sections)){
if(!_editingSections.any((s) => s.id == section.id)){
await section.delete();
}
}
loading = false;
editing = false;
notifyListeners();
}
void discardEditing(){
editing = false;
notifyListeners();
}
void removeSection(Section section){
_editingSections.remove(section);
notifyListeners();
}
void onMoveUp(Section section){
int index = _editingSections.indexOf(section);
if(index != 0) {
_editingSections.remove(section);
_editingSections.insert(index - 1, section);
index = _editingSections.indexOf(section);
}
notifyListeners();
}
HomeManager clone(){
return HomeManager(
images: List.from(images),
);
}
void onMoveDown(Section section){
index = _editingSections.indexOf(section);
totalItems = _editingSections.length;
if(index < totalItems - 1){
_editingSections.remove(section);
_editingSections.insert(index + 1, section);
index = _editingSections.indexOf(section);
}else{
}
notifyListeners();
}
void defineIndex({Section section}){
index = _editingSections.indexOf(section);
totalItems = _editingSections.length;
notifyListeners();
}
}
HomeScreen:
import 'package:carousel_pro/carousel_pro.dart';
import 'package:flutter/material.dart';
import 'package:provantagens_app/commom/custom_drawer.dart';
import 'package:provantagens_app/commom/custom_icons_icons.dart';
import 'package:provantagens_app/models/home_manager.dart';
import 'package:provantagens_app/models/section.dart';
import 'package:provantagens_app/screens/home/components/home_carousel.dart';
import 'package:provantagens_app/screens/home/components/menu_icon_tile.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
// ignore: must_be_immutable
class HomeScreen extends StatelessWidget {
HomeManager homeManager;
Section section;
List<Widget> get children => null;
String videoUrl = 'https://www.youtube.com/watch?v=VFnDo3JUzjs';
int index;
var _tapPosition;
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(colors: const [
Colors.white,
Colors.white,
], begin: Alignment.topCenter, end: Alignment.bottomCenter)),
child: Scaffold(
backgroundColor: Colors.transparent,
drawer: CustomDrawer(),
appBar: AppBar(
backgroundColor: Colors.transparent,
iconTheme: IconThemeData(color: Colors.black),
title: Text('Página inicial', style: TextStyle(color: Color.fromARGB(255, 30, 158, 8))),
centerTitle: true,
actions: <Widget>[
Divider(),
],
),
body: Consumer<HomeManager>(
builder: (_, homeManager, __){
return ListView(children: <Widget>[
AspectRatio(
aspectRatio: 1,
child:HomeCarousel(homeManager),
),
Column(
children: <Widget>[
Container(
height: 50,
),
Divider(
color: Colors.black,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Padding(
padding: const EdgeInsets.only(left:12.0),
child: MenuIconTile(title: 'Parceiros', iconData: Icons.apartment, page: 1,),
),
Padding(
padding: const EdgeInsets.only(left:7.0),
child: MenuIconTile(title: 'Beneficios', iconData: Icons.card_giftcard, page: 2,),
),
Padding(
padding: const EdgeInsets.only(right:3.0),
child: MenuIconTile(title: 'Suporte', iconData: Icons.help_outline, page: 6,),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
MenuIconTile(iconData: Icons.assignment,
title: 'Dados pessoais',
page: 3)
,
MenuIconTile(iconData: Icons.credit_card_outlined,
title: 'Meu cartão',
page: 4)
,
MenuIconTile(iconData: Icons.account_balance_wallet_outlined,
title: 'Pagamento',
page: 5,)
,
],
),
Divider(
color: Colors.black,
),
Container(
height: 50,
),
Consumer<HomeManager>(
builder: (_, sec, __){
return RaisedButton(
child: Text('Teste'),
onPressed: (){
Navigator.of(context)
.pushReplacementNamed('/teste',
arguments: sec);
},
);
},
),
Text('Saiba onde usar o seu', style: TextStyle(color: Colors.black, fontSize: 20),),
Text('Cartão Pró Vantagens', style: TextStyle(color: Color.fromARGB(255, 30, 158, 8), fontSize: 30),),
AspectRatio(
aspectRatio: 1,
child: Image.network(
'https://static.wixstatic.com/media/d170e1_80b5f6510f5841c19046f1ed5bca71e4~mv2.png/v1/fill/w_745,h_595,al_c,q_90,usm_0.66_1.00_0.01/Arte_Cart%C3%83%C2%B5es.webp',
fit: BoxFit.fill,
),
),
Divider(),
Container(
height: 150,
child: Row(
children: [
AspectRatio(
aspectRatio: 1,
child: Image.network(
'https://static.wixstatic.com/media/d170e1_486dd638987b4ef48d12a4bafee20e80~mv2.png/v1/fill/w_684,h_547,al_c,q_90,usm_0.66_1.00_0.01/Arte_Cart%C3%83%C2%B5es_2.webp',
fit: BoxFit.fill,
),
),
Padding(
padding: const EdgeInsets.only(left:20.0),
child: RichText(
text: TextSpan(children: <TextSpan>[
TextSpan(
text: 'Adquira já o seu',
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
TextSpan(
text: '\n\CARTÃO PRÓ VANTAGENS',
style: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 30, 158, 8)),
),
]),
),
),
],
),
),
Divider(),
tableBeneficios(),
Divider(),
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Text(
'O cartão Pró-Vantagens é sediado na cidade de Hortolândia/SP e já está no mercado há mais de 3 anos. Somos um time de profissionais apaixonados por gestão de benefícios e empenhados em gerar o máximo de valor para os conveniados.'),
FlatButton(
onPressed: () {
launch(
'https://www.youtube.com/watch?v=VFnDo3JUzjs');
},
child: Text('SAIBA MAIS')),
],
),
),
Container(
color: Color.fromARGB(255, 105, 190, 90),
child: Column(
children: <Widget>[
Row(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Text(
'© 2020 todos os direitos reservados a Cartão Pró Vantagens.',
style: TextStyle(fontSize: 10),
),
)
],
),
Divider(),
Row(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Text(
'Rua Luís Camilo de Camargo, 175 -\n\Centro, Hortolândia (piso superior)',
style: TextStyle(fontSize: 10),
),
),
Padding(
padding: const EdgeInsets.only(left: 16),
child: IconButton(
icon: Icon(CustomIcons.facebook),
color: Colors.black,
onPressed: () {
launch(
'https://www.facebook.com/provantagens/');
},
),
),
Padding(
padding: const EdgeInsets.only(left: 16),
child: IconButton(
icon: Icon(CustomIcons.instagram),
color: Colors.black,
onPressed: () {
launch(
'https://www.instagram.com/cartaoprovantagens/');
},
),
),
],
),
],
),
)
],
),
]);
},
)
),
);
}
tableBeneficios() {
return Table(
defaultColumnWidth: FlexColumnWidth(120.0),
border: TableBorder(
horizontalInside: BorderSide(
color: Colors.black,
style: BorderStyle.solid,
width: 1.0,
),
verticalInside: BorderSide(
color: Colors.black,
style: BorderStyle.solid,
width: 1.0,
),
),
children: [
_criarTituloTable(",Plus, Premium"),
_criarLinhaTable("Seguro de vida\n\(Morte Acidental),X,X"),
_criarLinhaTable("Seguro de Vida\n\(Qualquer natureza),,X"),
_criarLinhaTable("Invalidez Total e Parcial,X,X"),
_criarLinhaTable("Assistência Residencial,X,X"),
_criarLinhaTable("Assistência Funeral,X,X"),
_criarLinhaTable("Assistência Pet,X,X"),
_criarLinhaTable("Assistência Natalidade,X,X"),
_criarLinhaTable("Assistência Eletroassist,X,X"),
_criarLinhaTable("Assistência Alimentação,X,X"),
_criarLinhaTable("Descontos em Parceiros,X,X"),
],
);
}
_criarLinhaTable(String listaNomes) {
return TableRow(
children: listaNomes.split(',').map((name) {
return Container(
alignment: Alignment.center,
child: RichText(
text: TextSpan(children: <TextSpan>[
TextSpan(
text: name != "X" ? '' : 'X',
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
TextSpan(
text: name != 'X' ? name : '',
style: TextStyle(
fontSize: 10.0,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 30, 158, 8)),
),
]),
),
padding: EdgeInsets.all(8.0),
);
}).toList(),
);
}
_criarTituloTable(String listaNomes) {
return TableRow(
children: listaNomes.split(',').map((name) {
return Container(
alignment: Alignment.center,
child: RichText(
text: TextSpan(children: <TextSpan>[
TextSpan(
text: name == "" ? '' : 'Plano ',
style: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
TextSpan(
text: name,
style: TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 30, 158, 8)),
),
]),
),
padding: EdgeInsets.all(8.0),
);
}).toList(),
);
}
void _storePosition(TapDownDetails details) {
_tapPosition = details.globalPosition;
}
}
I forked the library to create a custom carousel for my company's project, and since we updated flutter to 2.x we had the same problem.
To fix this just update boolean expressions like
if(carouselState.pageController.position.minScrollExtent == null ||
carouselState.pageController.position.maxScrollExtent == null){ ... }
to
if(!carouselState.pageController.position.hasContentDimensions){ ... }
Here is flutter's github reference.
This worked for me
So I edited scrollposition.dart package
from line 133
#override
//double get minScrollExtent => _minScrollExtent!;
// double? _minScrollExtent;
double get minScrollExtent {
if (_minScrollExtent == null) {
_minScrollExtent = 0.0;
}
return double.parse(_minScrollExtent.toString());
}
double? _minScrollExtent;
#override
// double get maxScrollExtent => _maxScrollExtent!;
// double? _maxScrollExtent;
double get maxScrollExtent {
if (_maxScrollExtent == null) {
_maxScrollExtent = 0.0;
}
return double.parse(_maxScrollExtent.toString());
}
double? _maxScrollExtent;
Just upgrade to ^3.0.0 Check here https://pub.dev/packages/carousel_slider
I faced the same issue.
This is how I solved it
class PlansPage extends StatefulWidget {
const PlansPage({Key? key}) : super(key: key);
#override
State<PlansPage> createState() => _PlansPageState();
}
class _PlansPageState extends State<PlansPage> {
int _currentPage = 1;
late CarouselController carouselController;
#override
void initState() {
super.initState();
carouselController = CarouselController();
}
}
Then put initialization the carouselController inside the initState method I was able to use the methods jumpToPage(_currentPage ) and animateToPage(_currentPage) etc.
I use animateToPage inside GestureDetector in onTap.
onTap: () {
setState(() {
_currentPage = pageIndex;
});
carouselController.animateToPage(_currentPage);
},
I apologize in advance if this is inappropriate.
I solved the similar problem as follows. You can take advantage of the Boolean variable. I hope, help you.
child: !loading ? HomeCarousel(homeManager) : Center(child:ProgressIndicator()),
or
child: isLoading ? HomeCarousel(homeManager) : SplashScreen(),
class SplashScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('Loading...')
),
);
}
}
What the app does:
Tapping on the FAB will:
Create a single pdf with multiple pages or multiple png files. (the latter is selected as default, can be changed with a boolean in the parameter)
The function will return a list of Files.
What is the problem:
When there are more than 4 pages, the main isolate (app) will start to lag, and if more than 2 digits quantity the app will crash. I am trying to speed up the processing and hopefully prevent crashes.
What I have tried:
Learning how to spawn an isolate but I still get a lot of errors here and there.
Using an Isolate is very low-level programming and there are very few resources for it.
Some things may not make sense it's because this is a stripped-down version of a larger code. But everything should be working, in this example, it can either create pdf or png files, I prefer png files which is the one that consumes the most time.
For complete code https://github.com/fenchai23/pdf_isolate_test.git
This is an example of Isolates from the author of the pdf package for isolates but it is very simple and not what I am trying to do.
This is the complete main.dart file:
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:printing/printing.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(title: 'Pdf Isolate Test'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final List _invoice = [
{
'qty': '1',
'code': '27274-7J125 SET',
'desc': 'A/A NIS PATHFINDER NEW 2015 - 2PC ALMERA 27275-1N605',
'invt': '615',
'codealt': 'AC2503 SET',
'descalt': '',
'group': '10.FILTRO A/A',
'price': '5.53',
'disc1': '4.42',
'globalPrice': '5.53',
'total': '5.53'
},
{
'qty': '1',
'code': '27274-EA000 SET',
'desc':
'A/A NIS FRONTIER VQ40 YD25 PATHFIADER D40 VITARRA J20 27274-EL00A 27277-4JA0A',
'invt': '1018',
'codealt': 'AC2507SET',
'descalt': '27277-4JA0A' 'GRAN VITARRA J20',
'group': '10.FILTRO A/A',
'price': '4.25',
'disc1': '3.40',
'globalPrice': '4.25',
'total': '4.25'
}
];
static Future<List<File>> generateInvoice(Map args) async {
print('creating pdf');
final String _cartSubTotal = '1.00';
final String _cartTotal = '1.01';
final String _cartItbms = '7.00%';
final DateTime _startTime = DateTime.now();
final String dateTimeStamp = DateTime.now().toString();
PdfImage _logo;
List<Product> products = [];
final List customRecordData = args['customRecordData'];
final String customClientName = args['customClientName'];
final String customClientDirection = args['customClientDirection'];
final String customClientSeller = args['customClientSeller'];
final bool isPdf = args['isPdf'];
for (final k in customRecordData) {
Product productsHolder = Product(
k['qty'],
k['code'],
k['desc'],
k['globalPrice'],
k['total'],
);
products.add(productsHolder);
}
final pw.Document pdf = pw.Document();
_logo = PdfImage.file(pdf.document,
bytes: (await rootBundle.load('assets/icons/appIcon.png'))
.buffer
.asUint8List());
pw.Widget _pdfHeader(pw.Context context) {
return pw.Column(
children: [
pw.Row(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Expanded(
child: pw.Column(
children: [
pw.Container(height: 2),
pw.Container(
decoration: pw.BoxDecoration(
borderRadius: 2,
border:
pw.BoxBorder(color: PdfColors.blue, width: 2)),
padding: const pw.EdgeInsets.only(
left: 10, top: 5, bottom: 5, right: 10),
alignment: pw.Alignment.centerLeft,
height: 60,
child: pw.DefaultTextStyle(
style: pw.TextStyle(
color: PdfColors.black,
fontSize: 10,
),
child: pw.Column(
mainAxisSize: pw.MainAxisSize.min,
crossAxisAlignment: pw.CrossAxisAlignment.start,
mainAxisAlignment: pw.MainAxisAlignment.spaceEvenly,
children: [
pw.Text('Cliente: $customClientName', maxLines: 1),
pw.Text('Dir: $customClientDirection',
maxLines: 1),
pw.Text('Vend: $customClientSeller', maxLines: 1),
],
),
),
),
],
),
),
pw.Expanded(
child: pw.Column(
mainAxisSize: pw.MainAxisSize.min,
children: [
pw.Container(
alignment: pw.Alignment.topRight,
padding: const pw.EdgeInsets.only(bottom: 8, left: 30),
height: 72,
child: pw.Image(_logo),
),
],
),
),
],
),
if (context.pageNumber > 1) pw.SizedBox(height: 20),
],
);
}
pw.Widget _contentTable(pw.Context context) {
const tableHeaders = ['CANT', 'CODIGO', 'DESCRIPCION', 'PRECIO', 'TOTAL'];
return pw.Table.fromTextArray(
context: context,
border: null,
cellAlignment: pw.Alignment.centerLeft,
headerDecoration: pw.BoxDecoration(
borderRadius: 2,
color: PdfColors.blue,
),
headerHeight: 25,
cellHeight: 25,
cellAlignments: {
0: pw.Alignment.center,
1: pw.Alignment.centerLeft,
2: pw.Alignment.centerLeft,
3: pw.Alignment.center,
4: pw.Alignment.centerRight,
},
headerStyle: pw.TextStyle(
color: PdfColors.white,
fontSize: 10,
fontWeight: pw.FontWeight.bold,
),
cellStyle: const pw.TextStyle(
color: PdfColors.black,
fontSize: 10,
),
rowDecoration: pw.BoxDecoration(
border: pw.BoxBorder(
bottom: true,
color: PdfColors.grey,
width: .5,
),
),
headers: List<String>.generate(
tableHeaders.length,
(col) => tableHeaders[col],
),
data: List<List<String>>.generate(
products.length,
(row) => List<String>.generate(
tableHeaders.length,
(col) => products[row].getIndex(col),
),
),
);
}
pw.Widget _contentFooter(pw.Context context) {
return pw.Row(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Expanded(
flex: 2,
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Text(
'',
style: pw.TextStyle(
color: PdfColors.black,
fontWeight: pw.FontWeight.bold,
),
),
pw.Container(
margin: const pw.EdgeInsets.only(top: 20, bottom: 8),
child: pw.Text(
'',
style: pw.TextStyle(
color: PdfColors.black,
fontWeight: pw.FontWeight.bold,
),
),
),
pw.Text(
'',
style: const pw.TextStyle(
fontSize: 8,
lineSpacing: 5,
color: PdfColors.black,
),
),
],
),
),
pw.Expanded(
flex: 1,
child: pw.DefaultTextStyle(
style: const pw.TextStyle(
fontSize: 10,
color: PdfColors.black,
),
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
children: [
pw.Text('Sub Total:'),
pw.Text(_cartSubTotal),
],
),
pw.SizedBox(height: 5),
pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
children: [
pw.Text('I.T.B.M.S:'),
pw.Text(_cartItbms),
],
),
pw.Divider(color: PdfColors.black),
pw.DefaultTextStyle(
style: pw.TextStyle(
color: PdfColors.black,
fontSize: 14,
fontWeight: pw.FontWeight.bold,
),
child: pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
children: [
pw.Text('Total:'),
pw.Text(_cartTotal),
],
),
),
],
),
),
),
],
);
}
pw.Widget _pdfFooter(pw.Context context) {
return pw.DefaultTextStyle(
style: pw.Theme.of(context).defaultTextStyle.copyWith(
color: PdfColors.grey,
),
child: pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
children: [
pw.Container(
child: pw.Text(dateTimeStamp),
margin: const pw.EdgeInsets.only(top: 1.0 * PdfPageFormat.cm),
),
pw.Container(
alignment: pw.Alignment.centerRight,
margin: const pw.EdgeInsets.only(top: 1.0 * PdfPageFormat.cm),
child: pw.Text(
'Página ${context.pageNumber} de ${context.pagesCount}',
),
),
],
),
);
}
// Here is where PDF (Invoice) is created
pdf.addPage(
pw.MultiPage(
pageTheme: pw.PageTheme(
buildBackground: (pw.Context context) => pw.FullPage(
ignoreMargins: true,
child: pw.Container(
color: PdfColors.white,
),
),
pageFormat: PdfPageFormat.letter.copyWith(
marginBottom: 1.5 * PdfPageFormat.cm,
marginTop: 1.5 * PdfPageFormat.cm,
marginLeft: 1 * PdfPageFormat.cm,
marginRight: 1 * PdfPageFormat.cm,
),
),
header: _pdfHeader,
footer: _pdfFooter,
build: (pw.Context context) => <pw.Widget>[
pw.Padding(
padding: pw.EdgeInsets.only(bottom: 12.0),
),
_contentTable(context),
pw.Padding(
padding: pw.EdgeInsets.only(bottom: 12.0),
),
_contentFooter(context),
],
),
);
final output = await getTemporaryDirectory();
final timef = dateTimeStamp;
// get a safe name for filenames
final safeClientName =
customClientName.replaceAll(RegExp("[\\\\/:*?\"<>|]"), '');
final String fileName = '$timef--$customClientSeller--$safeClientName';
File file;
var imageList = List<File>();
var pagesToPrint;
pagesToPrint = await Printing.raster(pdf.save()).length;
if (!isPdf) {
int pageIndex = 1;
await for (var page in Printing.raster(pdf.save(), dpi: 300)) {
final image = await page.toPng();
final String pageQty =
(pagesToPrint > 1) ? '$pageIndex-$pagesToPrint' : '1-1';
file = File("${output.path}/$fileName--$pageQty.png");
await file.writeAsBytes(image);
imageList.add(file);
pageIndex++;
}
} else {
file = File("${output.path}/$fileName.pdf");
await file.writeAsBytes(pdf.save());
imageList.add(file);
}
print('done creating pdf');
final int elapsedTime = DateTime.now().difference(_startTime).inSeconds;
print('time elapsed: $elapsedTime seconds');
return imageList;
}
#override
void initState() {
final tempInvoice = [];
for (final Map k in _invoice) {
print(k);
tempInvoice.add(k);
}
for (int i = 1; i <= 100; i++) {
_invoice.addAll(tempInvoice);
}
print(_invoice);
print(_invoice.length);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(
height: 20,
),
Text('If animation lags then we know isolate failed.')
],
))),
floatingActionButton: FloatingActionButton(
onPressed: () async {
await getTemporaryDirectory()
..delete(recursive: true);
var result = await compute(generateInvoice, {
'customRecordData': _invoice,
'customClientName': 'Obama',
'customClientDirection': 'USA',
'customClientSeller': '1',
'isPdf': false,
});
print(result);
},
child: Icon(Icons.picture_as_pdf),
),
);
}
}
class Product {
const Product(
this.qty,
this.code,
this.desc,
this.price,
this.total,
);
final String qty;
final String code;
final String desc;
final String price;
final String total;
String getIndex(int index) {
switch (index) {
case 0:
return qty;
case 1:
return truncateWithEllipsis(25, code.trim());
case 2:
return truncateWithEllipsis(50, desc.trim());
case 3:
return price;
case 4:
return total;
}
return '';
}
}
String truncateWithEllipsis(int cutoff, String myString) {
return (myString.length <= cutoff)
? myString
: '${myString.substring(0, cutoff)}...';
}
While the app runs fine, when I try to run the compute code it gives me this error:
[ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: Exception: ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized.
I have tried giving main() async and also WidgetsFlutterBinding.ensureInitialized();
but it continues to call the same error.
btw this is the part that takes the most time.
if (!isPdf) {
int pageIndex = 1;
await for (var page in Printing.raster(pdf.save(), dpi: 300)) {
final image = await page.toPng();
final String pageQty =
(pagesToPrint > 1) ? '$pageIndex-$pagesToPrint' : '1-1';
file = File("${output.path}/$fileName--$pageQty.png");
await file.writeAsBytes(image);
imageList.add(file);
pageIndex++;
}
} else {
file = File("${output.path}/$fileName.pdf");
await file.writeAsBytes(pdf.save());
imageList.add(file);
}
Maybe I haven't seen it but where do you spawn your Isolate?
I could be a little bit tricky to generate your on Isolate, but Flutter have a great workaround and it's called the compute function. Give it a try! Compute Flutter API
Or try this page it helped me to get a good view over this function. Compute with Flutter
Maybe good to know is that Dart is a single threaded language, that means you can run your code in background on a second thread and in the main thread you can show a loading indicator. Workaround is to generate more threads or to vectorize your code.Dart vectorize
Tom
I am trying the code from this video: https://www.youtube.com/watch?v=4AUuhhSakro (or the github: https://github.com/khaliqdadmohmand/flutter_dynamic_dropdownLists/blob/master/lib/main.dart)
The issue is that when we (the viewers of the video) tries to "go back" to change the initial state (not app state, like county or province), the app crashes with this error:
There should be exactly one item with [DropdownButton]'s value: 14.
Either zero or 2 or more [DropdownMenuItem]s were detected with the same value
'package:flutter/src/material/dropdown.dart':
Failed assertion: line 827 pos 15: 'items == null || items.isEmpty || value == null ||
items.where((DropdownMenuItem<T> item) {
return item.value == value;
}).length == 1'
I believe the problem is when the dropdown is built the first time, it can have a null string as the value parameter of the dropdown, but the second time around it crashes on the assert (if you set it to null you crash at value==null and if you don't reset the variable you are using for value then in the new dropdownlist this value is not in the items. (where the count has to be == 1)
This has been racking my brain in my own project too, I thought I had it working, but apparently it's still very much broken.
Flutter : I have error when work with tow dropdown button load one from another
This is a similar problem and that solution has an async in it (but this is simply not working for me).
You can copy paste run full code below
You can in onChanged add _myCity = null;
code snippet
onChanged: (String Value) {
setState(() {
_myState = Value;
_myCity = null;
_getCitiesList();
print(_myState);
});
},
working demo
full code
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
_getStateList();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Dynamic DropDownList REST API'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
alignment: Alignment.topCenter,
margin: EdgeInsets.only(bottom: 100, top: 100),
child: Text(
'KDTechs',
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 20),
),
),
//======================================================== State
Container(
padding: EdgeInsets.only(left: 15, right: 15, top: 5),
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: DropdownButtonHideUnderline(
child: ButtonTheme(
alignedDropdown: true,
child: DropdownButton<String>(
value: _myState,
iconSize: 30,
icon: (null),
style: TextStyle(
color: Colors.black54,
fontSize: 16,
),
hint: Text('Select State'),
onChanged: (String Value) {
setState(() {
_myState = Value;
_myCity = null;
_getCitiesList();
print(_myState);
});
},
items: statesList?.map((item) {
return DropdownMenuItem(
child: Text(item['name']),
value: item['id'].toString(),
);
})?.toList() ??
[],
),
),
),
),
],
),
),
SizedBox(
height: 30,
),
//======================================================== City
Container(
padding: EdgeInsets.only(left: 15, right: 15, top: 5),
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: DropdownButtonHideUnderline(
child: ButtonTheme(
alignedDropdown: true,
child: DropdownButton<String>(
value: _myCity,
iconSize: 30,
icon: (null),
style: TextStyle(
color: Colors.black54,
fontSize: 16,
),
hint: Text('Select City'),
onChanged: (String Value) {
setState(() {
_myCity = Value;
print(_myCity);
});
},
items: citiesList?.map((item) {
return DropdownMenuItem(
child: Text(item['name']),
value: item['id'].toString(),
);
})?.toList() ??
[],
),
),
),
),
],
),
),
],
),
);
}
//=============================================================================== Api Calling here
//CALLING STATE API HERE
// Get State information by API
List statesList;
String _myState;
String stateInfoUrl = 'http://cleanions.bestweb.my/api/location/get_state';
Future<String> _getStateList() async {
await http.post(stateInfoUrl, headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}, body: {
"api_key": '25d55ad283aa400af464c76d713c07ad',
}).then((response) {
var data = json.decode(response.body);
// print(data);
setState(() {
statesList = data['state'];
});
});
}
// Get State information by API
List citiesList;
String _myCity;
String cityInfoUrl =
'http://cleanions.bestweb.my/api/location/get_city_by_state_id';
Future<String> _getCitiesList() async {
await http.post(cityInfoUrl, headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}, body: {
"api_key": '25d55ad283aa400af464c76d713c07ad',
"state_id": _myState,
}).then((response) {
var data = json.decode(response.body);
setState(() {
citiesList = data['cities'];
});
});
}
}