how to move buttons to a new row automatically when space is finished in flutter? - flutter

I am building an app where user is asked a question and an audio is player then the user selects the option he thinks is the correct one. i am having two problems here
i want to for the user when he taps the play button the icon changes to play and when ever the audio stops the play button come back to normal like in messanger apps.
the options button i will be having are too many that cannot fit in a single row so what i want is for the options to move to a new row after showing 5 or 6 button in each row. Any ideas for these two Questions.
this is my Question files:
// ignore_for_file: prefer_const_constructors, file_names, non_constant_identifier_names
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_tts/flutter_tts.dart';
import '../../utilities/button.dart';
import '../../utilities/buttonTapped.dart';
import '../../utilities/playbutton.dart';
import '../../utilities/playbutton.dart';
class Q7sound extends StatefulWidget {
const Q7sound({Key? key}) : super(key: key);
#override
State<Q7sound> createState() => _Q7soundState();
}
class _Q7soundState extends State<Q7sound> {
String question =
"Listen to this letter sound and then choose the letter whose sound you hear";
var changeButton = false;
var score = 0;
final FlutterTts flutterTts = FlutterTts();
speak(word) async {
await flutterTts.setLanguage("en-US");
await flutterTts.setPitch(1);
await flutterTts.setVolume(1.0);
await flutterTts.speak(word);
}
var word = "b";
var tap_count = 0;
bool isPlayingMsg = false;
bool showImage = false;
bool showoptions = false;
bool showquestion = true;
var letter = 'B';
bool buttonPressed1 = false;
void _letsPress1() {
setState(() {
buttonPressed1 = true;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
backgroundColor: Colors.cyan,
title: Text("AD&DY"),
),
body: Padding(
padding: const EdgeInsets.all(14.0),
child: Column(children: [
Container(
alignment: Alignment.center,
child: Text(
"Q1: $question",
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(
height: 20,
),
ListTile(
onTap: () async {
speak(word);
print(word);
},
leading: const Icon(Icons.play_arrow),
title: const Text("Play",
textScaleFactor: 1.2,
style: TextStyle(
color: Colors.black,
)),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TxtOption("A"),
TxtOption("B"),
TxtOption("F"),
TxtOption("D"),
TxtOption("E"),
TxtOption("E"),
TxtOption("E"),
TxtOption("E"),
TxtOption("E"),
TxtOption("E"),
TxtOption("E"),
],
)
]),
),
);
}
Material TxtOption(word) {
return Material(
elevation: 12,
shadowColor: Colors.grey,
color: Colors.cyan,
borderRadius: BorderRadius.circular(changeButton ? 50 : 8),
child: InkWell(
onTap: () {
Colors.red;
},
child: Container(
height: 50,
width: 50,
alignment: Alignment.center,
child: Text(word)),
),
);
}
}

for first issue you can do this:
ListTile(
onTap: () async {
speak(word);
print(word);
},
leading: Icon(isPlayingMsg ? Icons.stop :Icons.play_arrow),
title: const Text(isPlayingMsg ?"Stop" :"Play",
textScaleFactor: 1.2,
style: TextStyle(
color: Colors.black,
)),
),
for second problem you can use wrap instead of row:
Wrap(
spacing: 16,
runSpacing: 16,
children: [
TxtOption("A"),
TxtOption("B"),
TxtOption("F"),
TxtOption("D"),
TxtOption("E"),
TxtOption("E"),
TxtOption("E"),
TxtOption("E"),
TxtOption("E"),
TxtOption("E"),
TxtOption("E"),
],
)
and for space between children use spacing and between rows use runSpacing.

Use Wrap instead of row.
Wrap(
spacing: 10,
runSpacing: 10,
children: [
TxtOption("A"),
TxtOption("B"),
TxtOption("F"),
TxtOption("D"),
TxtOption("E"),
TxtOption("E"),
TxtOption("E"),
TxtOption("E"),
TxtOption("E"),
TxtOption("E"),
TxtOption("E"),
],
)

Related

Flutter Method to Display opening hours using optimized code

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.

Flutter: image messing up Flexible/Expanded sizes

Im trying to make something in flutter that looks like a twitter clone. The Tweet, called a wave, can be seen in the following:
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:timeago/timeago.dart' as timeago;
import '../../../../../../blocs/vote/vote_bloc.dart' as vote;
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:like_button/like_button.dart';
import '../../../../../../blocs/profile/profile_bloc.dart';
import '../../../../../../blocs/wave_liking/wave_liking_bloc.dart';
import '../../../../../../models/user_model.dart';
import '../../../../../../models/wave_model.dart';
import '../../../../../../widgets/text_splitter.dart';
import '../../generic_view.dart';
import '../../photo_view/photo_view.dart';
class WaveTile extends StatelessWidget {
const WaveTile({
Key? key,
required this.poster,
required this.wave,
this.extendBelow = false,
}) : super(key: key);
final User poster;
final Wave wave;
final bool extendBelow;
#override
Widget build(BuildContext context) {
User user =
(BlocProvider.of<ProfileBloc>(context).state as ProfileLoaded).user;
return IntrinsicHeight(
child: Row(
children: [
waveColumn(context),
Expanded(
flex: 5,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
waveHeader(poster, wave, context, user),
waveText(wave, context),
if (wave.imageUrl != null) waveImage(wave, context),
waveButtons(wave),
],
))
],
),
);
}
Expanded waveColumn(BuildContext context) {
return Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 40,
height: 40,
child: InkWell(
child: Hero(
tag: 'wave${wave.id}',
child: CircleAvatar(
backgroundImage:
CachedNetworkImageProvider(poster.imageUrls[0])),
),
onTap: () {
BlocProvider.of<vote.VoteBloc>(context)
.add(vote.LoadUserEvent(user: poster));
Navigator.pushNamed(
context,
'/votes',
);
},
),
),
if (extendBelow)
Expanded(
child: VerticalDivider(
color: Color.fromARGB(255, 207, 207, 207),
thickness: 2,
width: 10,
),
),
//add a grey line
]),
);
}
}
Widget waveHeader(User poster, Wave wave, BuildContext context, User user) {
return Row(
children: [
Container(
margin: const EdgeInsets.only(right: 5.0),
child: Text(poster.name,
style: Theme.of(context)
.textTheme
.headline4!
.copyWith(fontWeight: FontWeight.bold)),
),
Text(
'${poster.handle} · ${timeago.format(wave.createdAt)}',
style: Theme.of(context).textTheme.subtitle1,
),
Spacer(),
WaveTilePopup(poster: poster, wave: wave, user: user),
],
);
}
Widget waveText(Wave wave, BuildContext context) {
return Flexible(
child: TextSplitter(
wave.message,
context,
Theme.of(context).textTheme.subtitle2!,
));
}
Widget waveImage(Wave wave, BuildContext context) {
return Flexible(
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: InkWell(
child: CachedNetworkImage(
imageUrl: wave.imageUrl!,
fit: BoxFit.fill,
),
onTap: () {
Navigator.pushNamed(
context,
MyPhotoView.routeName,
arguments: {
'imageUrls': [wave.imageUrl!],
'index': 0
},
);
},
),
),
);
}
Widget waveButtons(Wave wave) {
return BlocBuilder<ProfileBloc, ProfileState>(
builder: (context, profileState) {
if (profileState is ProfileLoading) {
return Container();
}
if (profileState is ProfileLoaded) {
User user = profileState.user;
return Container(
margin: const EdgeInsets.only(top: 10.0, right: 20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Row(
children: [
//font awesome comment icon
Icon(
FontAwesomeIcons.comment,
size: 14.0,
color: Colors.grey,
),
SizedBox(width: 5),
Text(wave.comments.toString()),
],
),
BlocBuilder<WaveLikingBloc, WaveLikingState>(
builder: (context, waveLikingState) {
//set waveLikingState to be WaveLikingLoaded
waveLikingState = waveLikingState as WaveLikingLoaded;
bool inShortTermLikes =
waveLikingState.shortTermLikes.contains(wave.id);
bool inShortTermDislikes =
waveLikingState.shortTermDislikes.contains(wave.id);
bool inLikes = waveLikingState.likes.contains(wave.id);
bool inDislikes = waveLikingState.dislikes.contains(wave.id);
bool likedBy = wave.likedBy.contains(user.id);
bool isLiked = ((likedBy || inLikes || inShortTermLikes) &&
!(inDislikes || inShortTermDislikes));
int likeCount = (inLikes || inShortTermLikes)
? wave.likes + 1
: (inDislikes || inShortTermDislikes)
? wave.likes - 1
: wave.likes;
return LikeButton(
isLiked: isLiked,
size: 30,
circleColor: CircleColor(
start: Colors.red[300]!, end: Colors.red[900]!),
bubblesColor: BubblesColor(
dotPrimaryColor: Colors.red[300]!,
dotSecondaryColor: Colors.red[900]!,
),
likeBuilder: (bool isLiked) {
return Icon(
Icons.favorite,
color: isLiked ? Colors.red[900] : Colors.grey,
size: 20,
);
},
likeCount: likeCount,
countBuilder: (int? count, bool isLiked, String text) {
var color = isLiked ? Colors.red[900] : Colors.grey;
Widget result;
result = Text(
text,
style: TextStyle(color: color),
);
return result;
},
onTap: (bool isLiked) async {
(isLiked)
? BlocProvider.of<WaveLikingBloc>(context).add(
DislikeWave(waveId: wave.id, userId: user.id!))
: BlocProvider.of<WaveLikingBloc>(context)
.add(LikeWave(waveId: wave.id, userId: user.id!));
return !isLiked;
},
);
},
),
],
),
);
}
return Container();
},
);
}
When creating a wave with just text, it will end up looking like this:
This is the ideal situation right now. However, when an image is added, it looks like this:
Going through widget inspector does not seem to help me much, and the only way I can change the size of the wave is by deleting the image, leading me to believe the image is causing some weird interaction with the Flexible/Expanded widgets near it. Anyone got any ideas?
Thanks!
As you have 2 Flexible in a column , it will take up all the space. You can try removing Flexible from Text.

How can I update the style of an element inside a list dynamically in Flutter?

I am making a Fast Reading exercise app with flutter. I tried run foreach loop on my list to update TextSpans opacity but it didn't work. After that, I tried to replace the element with a new TextSpan and a new opacity, element changed inside the list but it didn't render on screen. Here is my code.
var textList = <TextSpan>[];
#override
void initState() {
super.initState();
text.split(' ').forEach((element) {
textList.add(TextSpan(text:element));
});
}
Widget _textArea() {
return FractionallySizedBox(
widthFactor: 0.9,
heightFactor: 0.9,
child: DecoratedBox(
decoration: BoxDecoration(
border: Border.all(
color: Colors.grey,
width: 1,
),
),
child: RichText(
text: TextSpan(
children: textList
),
)
));
}
I need to update the opacities of each element inside textList every second and the update function has to start with ``ònPress```event.
I am stuck on that issue.
FULL CODE
import 'package:flutter/material.dart';
class HomePeageReaderWidget extends StatefulWidget {
const HomePeageReaderWidget({Key? key}) : super(key: key);
#override
_HomePageReaderWidgetState createState() => _HomePageReaderWidgetState();
}
class _HomePageReaderWidgetState extends State<HomePeageReaderWidget> {
var text =
"""Gaziantep şehri Türkiye'nin güneydoğusundadır. Çok güzel bir şehirdir. Belki İstanbul ve Ankara kadar büyük değildir, ama yine de burada bir milyonun üstünde insan yaşıyor. Gaziantep'te iş alanları çok geniştir, herkes çalışıyor. Birçok fabrikalar ve atölyeler vardır. Ayrıca bir de Gaziantep Üniversitesi vardır. Üniversite şehirden biraz uzak, ama yurt da üniversitenin yanında. Bunun için ulaşım sorunu yok.Gaziantep'te yazın hava çok sıcak oluyor, insanlar evlerinden ve iş yerlerinden dışarıya çıkmıyorlar. Gece balkonlarda yatıyorlar. Kışın ise havalar sert, sokaklar ise her zaman ıslaktır.""";
var textList = <TextSpan>[];
#override
void initState() {
super.initState();
text.split(' ').forEach((element) {
textList.add(TextSpan(text: element));
});
}
#override
Widget build(BuildContext context) {
Widget _controlRow() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
children: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.skip_previous),
iconSize: 50,
color: const Color(0xFFffcd05),
)
],
),
Column(
children: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.pause_circle),
iconSize: 50,
color: const Color(0xFF91268e),
)
],
),
Column(
children: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.skip_next),
iconSize: 50,
color: const Color(0xFFffcd05),
)
],
),
],
);
}
Widget _textArea() {
return FractionallySizedBox(
widthFactor: 0.9,
heightFactor: 0.9,
child: DecoratedBox(
decoration: BoxDecoration(
border: Border.all(
color: Colors.grey,
width: 1,
),
),
child: RichText(
text: TextSpan(children: textList),
)));
}
return Column(
children: [
Expanded(
flex: 6,
child: _textArea(),
),
Expanded(
child: _controlRow(),
),
Container(
width: 300,
child: ElevatedButton.icon(
onPressed: () {
// Respond to button press
},
icon: Icon(Icons.play_arrow, size: 18),
label: Text("Hızlı Egzersiz"),
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(const Color(0xFF075098)),
),
),
)
],
);
}
}
You need to use setState( ) so your changes are reflected in the client.
Dont forget that you need to keep a StatefulWidget so that setState works
Code:
onPressed: () {
setState(() {
yourOpacityValue = updatedOpacityValue; });
},

Null check operator used on a null value Carousel Flutter

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...')
),
);
}
}

How to pass data back from a widget?

I have a screen where users can add a location. Here, I have separated all my widgets into there own files as illustrated below;
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:fluttershare/pages/location/location_help_screen.dart';
import 'package:fluttershare/widgets/common_widgets/customDivider.dart';
import 'package:uuid/uuid.dart';
import '../../widgets/camp_type_select.dart';
import '../../widgets/extra_location_notes.dart';
import '../../widgets/location_input.dart';
import '../../widgets/opening_times.dart';
import '../../widgets/post_media.dart';
import '../../widgets/space_avalibility.dart';
import '../../widgets/utility_type_select.dart';
import '../../widgets/width_restriction.dart';
import '../../widgets/height_restriction.dart';
import '../../models/locations.dart';
import '../../models/user.dart';
import '../home.dart';
class AddNewLocation extends StatefulWidget {
static const routeName = '/add-new-location';
final User currentUser;
AddNewLocation({this.currentUser});
_AddNewLocationState createState() => _AddNewLocationState();
}
class _AddNewLocationState extends State<AddNewLocation> {
String postId = Uuid().v4();
final _scaffoldKey = GlobalKey<ScaffoldState>();
PlaceLocation _pickedLocation;
int storyPostCount = 0;
bool isLoading = false;
void _selectPlace(double lat, double lng) {
_pickedLocation = PlaceLocation(lattitude: lat, longitude: lng);
}
getLocationPostCount() async {
setState(() {
isLoading = true;
});
QuerySnapshot snapshot = await locationPostRef
.document(currentUser.id)
.collection('user_location_posts')
.getDocuments();
setState(() {
storyPostCount = snapshot.documents.length;
});
}
createLocationPostInFirestore(
{String mediaUrl,
String description,
double heightRestriction,
double widthRestriction}) {
locationPostRef
.document(currentUser.id)
.collection("user_location_posts")
.document(postId)
.setData({
"postId": postId,
"ownerId": currentUser.id,
"username": currentUser.username,
"description": description,
"timestamp": timestamp,
"lattitude": _pickedLocation.lattitude,
"longitude": _pickedLocation.longitude,
"max_height": heightRestrictionValue.toStringAsFixed(0),
"max_width": widthRestrictionValue.toStringAsFixed(0),
});
}
handlePostSubmit() {
createLocationPostInFirestore(
heightRestriction: heightRestrictionValue,
widthRestriction: widthRestrictionValue,
);
SnackBar snackbar = SnackBar(
content: Text("Profile Updated"),
);
_scaffoldKey.currentState.showSnackBar(snackbar);
setState(() {
postId = Uuid().v4();
});
}
buildUploadUserHeader() {
return Container(
margin: EdgeInsets.only(bottom: 10),
height: 200,
child: Row(
children: <Widget>[
Expanded(
flex: 2,
child: Container(
color: Colors.blue,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ListTile(
leading: CircleAvatar(
backgroundImage:
CachedNetworkImageProvider(currentUser.photoUrl)),
),
],
),
),
),
Expanded(
flex: 6,
child: Container(
color: Colors.pink,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Text(currentUser.displayName),
],
),
),
),
],
),
);
}
buildCampUploadForm() {
return Container(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
//buildUploadUserHeader(), //TODO: This is the profile header that is dissabled for now. Work on possibly a header in the future.
Container(
padding: EdgeInsets.all(15),
child: Column(
children: <Widget>[
CampTypeSelect(),
CustomDivider(),
LocationInput(_selectPlace),
CustomDivider(),
HeightRestriction(),
WidthRestriction(),
SpaceAvalibility(),
OpeningTimes(),
CustomDivider(),
PostMedia(),
CustomDivider(),
UtilityServices(),
CustomDivider(),
ExtraLocationNotes(),
Container(
height: 80,
margin: EdgeInsets.only(top: 10, bottom: 10),
child: Row(
children: <Widget>[
Expanded(
child: FlatButton(
color: Colors.black,
onPressed: () => handlePostSubmit(),
child: Text(
"SUBMIT",
style: Theme.of(context).textTheme.display2,
),
padding: EdgeInsets.all(20),
),
)
],
),
),
],
),
),
],
),
));
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
automaticallyImplyLeading: false,
title: const Text(
'Add New Location',
style: TextStyle(color: Colors.black),
),
actions: <Widget>[
// action button
IconButton(
icon: Icon(Icons.info_outline),
color: Colors.black,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
fullscreenDialog: true,
builder: (context) => LocationSubmitHelpScreen()),
);
},
),
// action button
IconButton(
icon: Icon(Icons.close),
color: Colors.black,
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
body: buildCampUploadForm(),
backgroundColor: Colors.white,
);
}
}
What I am trying to do is pass the data back from the widget ExtraLocationNotes()
to the function createLocationPostInFirestore().
For context, this is what my widget looks like;
import 'package:flutter/material.dart';
import 'common_widgets/custom_form_card.dart';
class ExtraLocationNotes extends StatefulWidget {
_ExtraLocationNotesState createState() => _ExtraLocationNotesState();
}
class _ExtraLocationNotesState extends State<ExtraLocationNotes> {
TextEditingController descriptionController = TextEditingController();
#override
Widget build(BuildContext context) {
return CustomFormCard(
child: Column(
children: <Widget>[
Container(
child: Row(
children: <Widget>[
Text(
"EXTRA INFORMATION",
style: TextStyle(
fontSize: 18.0,
color: Colors.black,
fontWeight: FontWeight.w400,
letterSpacing: 2.0,
),
),
],
),
),
SizedBox(height: 20),
TextFormField(
controller: descriptionController,
maxLines: 6,
maxLength: 250,
maxLengthEnforced: true,
style:
new TextStyle(fontSize: 18.0, height: 1.3, color: Colors.black),
decoration: const InputDecoration(
hintText:
"Please write a description of this location for fellow travellers.",
alignLabelWithHint: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.only(),
borderSide: BorderSide(color: Colors.black),
),
),
),
],
),
);
}
}
How do I pass the data back to the parent widget?
You need a callback, which will be triggered in the child widget then the value will be updated in the parent widget:
// 1- Define a pointers to executable code in memory, which is the callback.
typedef void MyCallback(String val);
class ExtraLocationNotes extends StatefulWidget {
// 2- You will pass it to this widget with the constructor.
final MyCallback cb;
// 3- ..pass it to this widget with the constructor
ExtraLocationNotes({this.cb});
_ExtraLocationNotesState createState() => _ExtraLocationNotesState();
}
class _ExtraLocationNotesState extends State<ExtraLocationNotes> {
//..
//...
RaisedButton(
//..
// 4- in any event inside the child you can call the callback with
// the data you want to send back to the parent widget:
onPressed: () {
widget.cb("Hello from the other side!");
}
),
}
Then inside the parent widget you need to catch the data which sent form the child:
class AddNewLocation extends StatefulWidget {
//...
_AddNewLocationState createState() => _AddNewLocationState();
}
class _AddNewLocationState extends State<AddNewLocation> {
// 1- Global var to store the data that we're waiting for.
String _dataFromMyChild = "";
buildCampUploadForm() {
return Container(
//...
//...
// 2- Pass the callback with the constructor of the child, this
// will update _dataFromMyChild's value:
ExtraLocationNotes(cb: (v) => setState(() => _dataFromMyChild = v)),
//..
}
// then
createLocationPostInFirestore() {
// Use _dataFromMyChild's value here
}
}
You can use the BuildContext object to get the context widget (might no be the parent!) couldn't read it all but as i understand that you need to pass the info from the child to the parent ,and you can do it with some like this :-
(context.widget as MyType).doStuff();
Note.
please check first with
print(context.widget.runtimeType);
but to make a better solution make a mutable data object that is passed from parent to the child so when changes happens it reflect's on the parent so you can separate business logic from ui logic.