Almost a day of figuring way how to do it and I need your help.
I've created an app that would have a state with an instance of audioplayers and using flutter_bloc.
Problems:
The state player plays but the Widget MusicPlayer doesn't rebuild the widget with the BlocBuilder
I'm trying also to get the currentPosition and duration of the music I'm playing and displaying it using LinearPercentIndicator from linear_percent_indicator package but can't seem to find a solution because rebuild doesn't work.
What am I missing?
Here's what I have so far:
Bloc
class AudioPlayerBloc extends Bloc<AudioPlayerEvents, MusicPlayerState> {
#override
MusicPlayerState get initialState => MusicPlayerState(
player: AudioPlayer(),
episode: Episode(),
);
#override
Stream<MusicPlayerState> mapEventToState(AudioPlayerEvents event) async* {
if (event is InitializePlayer) {
this.currentState.episode = event.episode;
this.dispatch(PlayPlayer());
yield this.currentState;
}
if (event is PlayPlayer) {
this.play(this.currentState.episode.source);
}
if (event is PlayRemote) {
this.currentState.player.stop();
this.currentState.player.play(event.remoteURL);
yield this.currentState;
}
if (event is ShowPlayer) {
yield this.currentState;
}
if (event is HidePlayer) {
yield this.currentState;
}
}
void play(String remoteURL) {
this.dispatch(PlayRemote(remoteURL));
}
void stop() async {
await this.currentState.player.stop();
}
void pause() async{
await this.currentState.player.pause();
}
void resume(){
this.currentState.player.resume();
}
#override
void dispose() {
super.dispose();
}
}
Event
abstract class AudioPlayerEvents {}
class InitializePlayer extends AudioPlayerEvents {
Episode episode;
InitializePlayer(this.episode);
}
class PlayRemote extends AudioPlayerEvents {
final String remoteURL;
PlayRemote(this.remoteURL);
}
class PlayPlayer extends AudioPlayerEvents {}
class ShowPlayer extends AudioPlayerEvents {}
class HidePlayer extends AudioPlayerEvents {}
State
import 'package:audioplayers/audioplayers.dart';
class MusicPlayerState {
AudioPlayer player;
Episode episode; // My Custom Class
MusicPlayerState({
this.player,
this.episode,
});
}
main.dart
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<AudioPlayerBloc>(
builder: (_) => AudioPlayerBloc(),
)
],
child: MaterialApp(
navigatorObservers: [],
initialRoute: HomeScreen.id,
routes: {
HomeScreen.id: (context) => HomeScreen(app: widget.app),
},
),
);
}
}
MusicPlayer.dart <-- my Player Widget
class MusicPlayer extends StatelessWidget {
#override
Widget build(BuildContext context) {
final AudioPlayerBloc audioPlayerBloc =
BlocProvider.of<AudioPlayerBloc>(context);
return BlocBuilder<AudioPlayerBloc, MusicPlayerState>(
bloc: audioPlayerBloc,
builder: (context, state) {
return Container(
height: 200.0,
color: Colors.cyan[200],
child: Padding(
padding: const EdgeInsets.only(top: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Column(
children: <Widget>[
Text("${state.episode.name}"),
Row(
children: <Widget>[
Expanded(
flex: 1,
child: FutureBuilder<int>(
future: audioPlayerBloc.currentState.player.getCurrentPosition(),
builder: (context, AsyncSnapshot<int> snapshot) {
double seconds = snapshot.data / 1000;
if (snapshot.hasData) {
return Text("${printDuration(Duration(seconds: seconds.toInt()), abbreviated: true)}");
} else {
return Text('Calculating..');
}
},
),
),
Expanded(
flex: 3,
child: LinearPercentIndicator(
lineHeight: 7.0,
// percent: this.currentPosition / this.fileDuration,
percent: 0.3,
backgroundColor: Colors.grey,
progressColor: Colors.blue,
),
),
Expanded(
flex: 1,
child: FutureBuilder<int>(
future: audioPlayerBloc.currentState.player.getDuration(),
builder: (context, AsyncSnapshot<int> snapshot) {
double seconds = snapshot.data / 1000;
if (snapshot.hasData) {
return Text("${printDuration(Duration(seconds: seconds.toInt()), abbreviated: true)}");
} else {
return Text('Calculating..');
}
},
),
),
],
),
Text(state.player.state.toString()),
FlatButton(
onPressed: () {
print('close here');
Navigator.of(context).pop();
},
child: Icon(
Icons.close,
color: Colors.black.withOpacity(0.5),
),
),
Row(
children: <Widget>[
FlatButton(
onPressed: () {
audioPlayerBloc.pause();
},
child: Text('Pause Player'),
),
FlatButton(
onPressed: () {
audioPlayerBloc.resume();
},
child: Text('Resume Player'),
),
FlatButton(
onPressed: () {
audioPlayerBloc.stop();
},
child: Text('Stop Player'),
),
],
)
],
)
],
),
),
);
},
);
}
}
HomeScreen.dart <-- my first screen
#override
Widget build(BuildContext context) {
final AudioPlayerBloc audioPlayerBloc = BlocProvider.of<AudioPlayerBloc>(context);
return MultiBlocProvider(
providers: [
BlocProvider<AudioPlayerBloc>(
builder: (_) => AudioPlayerBloc(),
)
],
child: Scaffold(
appBar: AppBar(
title: Text('Global Audio Player'),
),
body: Container(
child: BlocBuilder<AudioPlayerBloc, MusicPlayerState>(
builder: (context, state) {
return Column(
children: <Widget>[
Flexible(
child: getListView(context)
),
displayPlayer(state.player), // Here I'm trying to display the player when the AudioPlayerState is PLAYING
],
);
},
),
),
),
);
}
Global function
Widget displayPlayer(AudioPlayer player){
return MusicPlayer();
if(player.state == AudioPlayerState.PLAYING) {
return MusicPlayer();
}
return Container();
}
EpisodesScreen.dart <-- ListView of the Episodes
class _EpisodesScreenState extends State<EpisodesScreen> {
#override
void initState() {
super.initState();
print(widget.series);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.series.name)),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
flex: 0,
child: Image.network(widget.series.image.original),
),
Expanded(
child: getListView(context),
),
Expanded(
child: BlocBuilder<AudioPlayerBloc, MusicPlayerState>(
builder: (context, state) {
return displayPlayer(state.player);
},
),
)
],
));
}
Widget getListView(BuildContext context) {
List<Episode> episodesList = widget.series.episodes;
final AudioPlayerBloc audioPlayerBloc =
BlocProvider.of<AudioPlayerBloc>(context);
var listView = ListView.separated(
itemCount: episodesList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(episodesList[index].name),
trailing: BlocBuilder<AudioPlayerBloc, MusicPlayerState>(
builder: (context, state) {
return FlatButton(
onPressed: () {
audioPlayerBloc.dispatch(InitializePlayer(episodesList[index]));
},
child: Icon(
Icons.play_arrow,
color: Colors.black87,
),
);
},
),
);
},
separatorBuilder: (BuildContext context, int index) {
return Divider(
height: 1.0,
color: Colors.black12,
);
},
);
return listView;
}
}
I missed the yield * part of it and it is now working.
Stream<Duration> currentPosition() async* {
yield* this.currentState.audioPlayer.onAudioPositionChanged;
}
Stream<Duration> fileDuration() async* {
yield* this.currentState.audioPlayer.onDurationChanged;
}
Related
Hello I would like to have a loading page before all the list is created. To display this list, I get the request info in php and I create cards one by one. The problem is that when the user come on the application, there is a lot of loading round because I put "return CircularProgressIndicator()", I would like that we are on a waiting page until all the cards are not loaded
I tried to set variables and pass them to false until all the cards are loaded but I can't get on this loading page
Here is the code of my page
Future<String> getDataSearch(text, index) async {
if (index != "ff") {
var theUrl =
Uri.parse("https://xf8s7auoez.preview.infomaniak.website/getdata.php");
var res = await http.post(theUrl, body: {
"index": index.toString(),
"post": "index",
"file": "get_data",
});
Map<String, dynamic> data = jsonDecode(res.body);
if (text == 1) {
return data["type"];
} else if (text == 2) {
return data["taille"];
} else if (text == 3) {
return data["prix"];
} else if (text == 5) {
return data["image"];
} else if (text == 6) {
return data["description"];
}
}
var theUrl =
Uri.parse("https://xf8s7auoez.preview.infomaniak.website/getdata.php");
var res = await http.post(theUrl, body: {
"post": "rows",
"file": "get_data",
});
print(res.body);
return res.body;
}
class Type extends StatelessWidget {
Type(this.index);
final int index;
#override
Widget build(context) {
return FutureBuilder<String>(
// future: getUserType(index),
future: getDataSearch(1, index),
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!);
} else {
return SizedBox(width: 10, height: 10, child: Container());
}
});
}
}
class Description extends StatelessWidget {
Description(this.index);
final int index;
#override
Widget build(context) {
return FutureBuilder<String>(
// future: getUserType(index),
future: getDataSearch(6, index),
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!);
} else {
return SizedBox(width: 10, height: 10, child: Container());
}
});
}
}
class Taille extends StatelessWidget {
Taille(this.index);
final int index;
#override
Widget build(context) {
return FutureBuilder<String>(
// future: getUserType(index),
future: getDataSearch(2, index),
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!);
} else {
return SizedBox(width: 10, height: 10, child: Container());
}
});
}
}
class Prix extends StatelessWidget {
Prix(this.index);
final int index;
#override
Widget build(context) {
return FutureBuilder<String>(
//future: getAge(index),
future: getDataSearch(3, index),
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
String bio = snapshot.data.toString();
return Text(bio);
} else {
return SizedBox(width: 10, height: 10, child: Container());
}
});
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Mediester',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: RefreshIndicatorView(),
);
}
}
class RefreshIndicatorView extends StatefulWidget {
const RefreshIndicatorView({Key? key}) : super(key: key);
#override
_RefreshIndicatorViewState createState() => _RefreshIndicatorViewState();
}
class _RefreshIndicatorViewState extends State<RefreshIndicatorView> {
int initNumber = 1;
int selectedValue = 1;
final GlobalKey<ScaffoldState> _key = GlobalKey();
#override
initState() {
getDataSearch(4, "ff").then((String result) {
if (initNumber != int.parse(result)) {
setState(() {
initNumber = int.parse(result);
});
}
});
}
#override
Widget build(BuildContext context) {
return RefreshIndicator(
displacement: 250,
backgroundColor: Color.fromARGB(255, 110, 155, 191),
color: Colors.white,
strokeWidth: 3,
triggerMode: RefreshIndicatorTriggerMode.onEdge,
onRefresh: () async {
await Future.delayed(Duration(milliseconds: 1500));
setState(() {
int debug = 0;
});
},
child: Scaffold(
key: _key,
backgroundColor: Color.fromARGB(255, 110, 155, 191),
appBar: BaseAppBar(),
drawer: createDrawer(context),
body: _buildCardDesign(),
),
);
}
Widget _buildCardDesign() {
Future.delayed(const Duration(milliseconds: 1500), () {
if (debugg != 1) {
setState(() {});
debugg = 1;
}
});
return Container(
margin: EdgeInsets.all(5),
child: ListView.builder(
itemCount: initNumber,
shrinkWrap: true,
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics()),
scrollDirection: Axis.vertical,
prototypeItem: _buildCardView(1),
itemBuilder: (BuildContext context, int index) {
return _buildCardView(index + 1);
}),
);
}
Widget _buildPhoto(index) {
return Container(
child: FutureBuilder<String>(
future: getDataSearch(5, index),
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return ClipRRect(
borderRadius: BorderRadius.circular(5.0),
child: Image.network(
snapshot.data!,
height: 65.0,
width: 65.0,
fit: BoxFit.cover,
),
);
} else {
return CircularProgressIndicator.adaptive();
}
}));
}
Widget _buildCardView(index) {
return Container(
width: MediaQuery.of(context).size.width,
child: Container(
margin: EdgeInsets.all(10),
height: 100,
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(10))),
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ArticlePage(
index, Type(index), Prix(index), Description(index)),
));
},
child: Container(
padding: EdgeInsets.fromLTRB(15, 15, 30, 15),
child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildPhoto(index),
//SizedBox(width: 20,),
DefaultTextStyle(
style: TextStyle(
fontSize: 19,
fontWeight: FontWeight.w800,
color: Color.fromARGB(255, 110, 155, 191)),
child: Container(
padding: EdgeInsets.only(left: 40),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DefaultTextStyle(
child: Container(child: Type(index)),
style: TextStyle(
color: Color.fromARGB(255, 110, 155, 191),
fontSize: 17)),
DefaultTextStyle(
child: Container(
padding: EdgeInsets.only(top: 8),
child: Taille(index)),
style: TextStyle(
color: Color.fromARGB(255, 168, 168, 168)),
),
Container(
padding: EdgeInsets.only(top: 3),
child: Row(
children: [
Prix(index),
Text(" €"),
],
)),
],
),
),
),
],
),
),
),
),
);
}
}
This can be accomplished using a FutureBuilder. In your RefreshIndicatorView class, the _buildCardDesign widget is currently calling your future, and then building the ListView. Instead, try creating one async function that holds the logic for retrieving the data, and a separate class for the widget that is only responsible for building the widget. Then, you can call the future in the FutureBuilder and show your loading page until it has completed.
In the FutureBuilder, pass your function as the argument to future. Then you can use the variable snapshot to check the status of the future. In your builder function, simply check if(snapshot.hasData), and show the loading page or the listView widget accordingly.
Edit: Here's a simple example that retrieves a List<String> and puts one String in each card. Your new function and widget class would look like this:
List<String> _getStrings() async {
//Async logic here
...
}
Widget MyCard extends StatelessWidget {
final String displayText;
myCard(this.displayText);
#override
Widget build(BuildContext context) {
return Text(displayText);
}
}
And in the home page of your MyApp, you can use something like this for the build method:
FutureBuilder<List<String>>(
future: _getStrings,
builder: (BuildContext context, AsyncSnapshot<List<String>> snapshot) {
if(snapshot.hasData) {
return ListView.builder(
itemBuilder: (BuildContext context, int index) => MyCard(snapshot.data[index])
...
);
} else {
return MyLoadingScreen();
}
});
I have an alert dialog that displays a series of check boxes.
I am trying to ensure that if at least one of the checkboxes is selected, the confirm button is enabled, otherwise, if no checkbox is selected, it appears as inactive.
I have a parent and a child widget, both statefull. In one of them I have the button that should be enabled / disabled, and in the other one I have the content of the alert dialog.
The challenge for me has been to notify the parent widget from the child widget, that the flag variable with which I determine whether the button should be active or not, has been updated.
I have tried sending a function to the child widget that it executes, also with ValueSetter and ValueChanged, but so far without success.
If after activating or inactivating one of the checkboxes, I do a hot reload, the button is also updated. So I think it may be something with setState that I am not taking into account.
This is what I have done so far, ready to copy and paste into dartPad.
Thanks for your help.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: FrequencySelectionPage(),
),
),
);
}
}
class FrequencySelectionPage extends StatefulWidget {
FrequencySelectionPage();
#override
_FrequencySelectionPageState createState() => _FrequencySelectionPageState();
}
class _FrequencySelectionPageState extends State<FrequencySelectionPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ListTile(
leading: Icon(Icons.calendar_today),
title: Text('Some days of the week'),
trailing: Icon(Icons.keyboard_arrow_right_rounded),
onTap: () {
_showDialog(context);
}
),),
);
}
void _showDialog(BuildContext context) {
final double screenSize = MediaQuery.of(context).size.height;
bool? canConfirm;
void setCanConfirm(bool value) {
setState(() {
canConfirm = value;
});
}
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Choose days"),
content: Container(
width: 200,
height: screenSize * 0.60,
child: ShowAlertContent(
setCanConfirm: setCanConfirm),
),
actions: <Widget>[
SizedBox(
width: screenSize * 0.50,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('Cancel'),
),
SizedBox(
height: 20.0,
width: 20.0,
),
ElevatedButton(
child: Text('Confirm'),
onPressed: (canConfirm == false)
? null
: () {
Navigator.of(context).pop();
},
),
],
),
)
],
);
},
);
}
}
class ShowAlertContent extends StatelessWidget {
final ValueSetter<bool> setCanConfirm;
const ShowAlertContent(
{required this.setCanConfirm});
#override
Widget build(BuildContext context) {
return ShowSomeWeekDaysOptionContent(setCanConfirm: setCanConfirm);
}
}
class ShowSomeWeekDaysOptionContent extends StatefulWidget {
final ValueChanged<bool> setCanConfirm;
const ShowSomeWeekDaysOptionContent({required this.setCanConfirm});
#override
_ShowSomeWeekDaysOptionContentState createState() =>
_ShowSomeWeekDaysOptionContentState();
}
class _ShowSomeWeekDaysOptionContentState
extends State<ShowSomeWeekDaysOptionContent> {
Map<String, bool> days = {
'Day1': false,
'Day2': false,
'Day3': false,
'Day4': false,
'Day5': false,
'Day6': false,
'Day7': false
};
#override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: ListView(
padding: EdgeInsets.all(8.0),
children: days.keys.map(
(day) {
return StatefulBuilder(builder:
(BuildContext context, StateSetter setCheckboxState) {
return CheckboxListTile(
title: Text(day),
value: days[day],
onChanged: (bool? value) {
setState(() {});
setCheckboxState(() {
days[day] = value!;
if (days.containsValue(true)) {
widget.setCanConfirm(true);
} else {
widget.setCanConfirm(false);
}
});
},
);
});
},
).toList(),
),
),
],
);
}
}
In your example you need something which rebuilds the button, when the value of canConfirm changes. You could use a ValueListenableBuilder. Therefore you have to make canConfirm a ValueNotifier.
Here is the working example:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: FrequencySelectionPage(),
),
),
);
}
}
class FrequencySelectionPage extends StatefulWidget {
FrequencySelectionPage();
#override
_FrequencySelectionPageState createState() => _FrequencySelectionPageState();
}
class _FrequencySelectionPageState extends State<FrequencySelectionPage> {
late ValueNotifier<bool> canConfirm;
#override
void initState() {
canConfirm = ValueNotifier(false);
super.initState();
}
void setCanConfirm(bool value) {
canConfirm.value = value;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ListTile(
leading: Icon(Icons.calendar_today),
title: Text('Some days of the week'),
trailing: Icon(Icons.keyboard_arrow_right_rounded),
onTap: () {
_showDialog(context);
}),
),
);
}
void _showDialog(BuildContext context) {
final double screenSize = MediaQuery.of(context).size.height;
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Choose days"),
content: Container(
width: 200,
height: screenSize * 0.60,
child: ShowAlertContent(setCanConfirm: setCanConfirm),
),
actions: <Widget>[
SizedBox(
width: screenSize * 0.50,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('Cancel'),
),
SizedBox(
height: 20.0,
width: 20.0,
),
ValueListenableBuilder<bool>(
valueListenable: canConfirm,
builder: (context, value, child) {
return ElevatedButton(
child: Text('Confirm'),
onPressed: (value == false)
? null
: () {
Navigator.of(context).pop();
},
);
},
),
],
),
)
],
);
},
);
}
}
class ShowAlertContent extends StatelessWidget {
final ValueSetter<bool> setCanConfirm;
const ShowAlertContent({required this.setCanConfirm});
#override
Widget build(BuildContext context) {
return ShowSomeWeekDaysOptionContent(setCanConfirm: setCanConfirm);
}
}
class ShowSomeWeekDaysOptionContent extends StatefulWidget {
final ValueChanged<bool> setCanConfirm;
const ShowSomeWeekDaysOptionContent({required this.setCanConfirm});
#override
_ShowSomeWeekDaysOptionContentState createState() =>
_ShowSomeWeekDaysOptionContentState();
}
class _ShowSomeWeekDaysOptionContentState
extends State<ShowSomeWeekDaysOptionContent> {
Map<String, bool> days = {
'Day1': false,
'Day2': false,
'Day3': false,
'Day4': false,
'Day5': false,
'Day6': false,
'Day7': false
};
#override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: ListView(
padding: EdgeInsets.all(8.0),
children: days.keys.map(
(day) {
return StatefulBuilder(builder:
(BuildContext context, StateSetter setCheckboxState) {
return CheckboxListTile(
title: Text(day),
value: days[day],
onChanged: (bool? value) {
setCheckboxState(() {
days[day] = value!;
if (days.containsValue(true)) {
widget.setCanConfirm(true);
} else {
widget.setCanConfirm(false);
}
});
},
);
});
},
).toList(),
),
),
],
);
}
}
I don’t know about your case but ummmm.. I think this one maybe a lot easier
`bool _isChecked = false;
#override
Widget build(BuildContext context) {
return Column(
children: [
Checkbox(
value: _isChecked,
onChanged: (value) {
setState(() {
_isChecked = value;
});
},
),
ElevatedButton(
child: Text('Button'),
onPressed: (){
if(_isChecked){
print('CHeckbox is checked');
}else{
print('CHeckbox is not checked');
}
},
),
],
);
}`
I have the following code in a Stateful Widget and the future: in Futurebuilder correctly calls the function _getCategory()
class Categories extends StatefulWidget {
#override
_CategoriesState createState() => _CategoriesState();
}
class _CategoriesState extends State<Categories> {
List<AllAccounts> _allaccounts;
DatabaseHelper _dbHelper;
int _count = 0;
#override
void initState() {
super.initState();
setState(() {
_dbHelper = DatabaseHelper.instance;
});
_getCategory();
}
Future _getCategory() async {
List<AllAccounts> x = await _dbHelper.getCategory();
setState(() {
_allaccounts = x;
});
_count = _allaccounts.length;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Add/Edit Categories'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(
height: 20.0,
),
Text('Select a Category', style: TextStyle(fontSize: 18.0),),
SizedBox(
height: 20.0,
),
_list(),
],
),
),
);
}
_list() =>
Expanded(
child: ListView.builder(
itemCount: _count,
itemBuilder: (context, position) {
return Card(
color: Colors.grey[600],
elevation: 2.0,
child: ListTile(
leading: Icon(Icons.group),
title: Text(_allaccounts[position].category,
style: TextStyle(fontSize: 17.0),),
trailing: Icon(Icons.arrow_forward),
onTap: (){},
),
);
},
),
);
}
However, when I try the same thing in a StatelessWidget, the future: in Futurebuilder is not called.
class Test extends StatelessWidget {
List<AllAccounts> _allaccounts;
DatabaseHelper _dbHelper;
int _count = 0;
Future<int> _getCat() async {
List<AllAccounts> x = await _dbHelper.getCategory();
_allaccounts = x;
_count = x.length;
print(_count);
return _count;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Test'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
_list(),
],
),
),);
}
_list() => Expanded(
child: FutureBuilder<int>(
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.none &&
snapshot.hasData == null) {
return Center(
child: CircularProgressIndicator(),
);
}
return Container(
child: Text('Printing $_count'),
);
},
future: _getCat(),
),
);
}
Text('Printing $_count) returns 'Printing 0'. Also the print in the _getCat() function does not show-up in the console.
Is there a problem in the way Futurebuilder is used in the Stateless Widget?
Instead of printing _count, you need to use snapshot.data to get the value returned from _getCat.
return Container(
child: Text('Printing ${snapshot.data}'),
);
I want to create a TaskData instance for each Group in GroupData and will show it in a new Screen, but I don't get it the right way. Actually I am having the same list of Tasks in all available Groups.
Task functionality is working fine. If I tap on the checkmark, the Task is checked, but it will happen in all available groups. How to get a new Tasklist in every group?
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<GroupData>(
create: (context) => GroupData(),
),
ChangeNotifierProvider<TaskData>(
create: (context) => TaskData(),
),
],
child: MaterialApp(
theme: ThemeData.dark().copyWith(
// scaffoldBackgroundColor: Color(0xFF3C3F40),
),
home: GroupsScreen(),
),
);
}
}
class GroupsScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
IconButton(
icon: Icon(
FontAwesomeIcons.plusCircle,
size: 30.0,
),
onPressed: () {
// Add group functionality
},
),
Expanded(
child: Consumer<GroupData>(
builder: (context, groupData, child) {
return ListView.builder(
itemBuilder: (context, index) {
final group = groupData.groups[index];
return GroupTile(
group: group,
);
},
itemCount: groupData.groupCount,
);
},
),
)
],
),
),
);
}
}
class GroupTile extends StatelessWidget {
final Group group;
GroupTile({
#required this.group,
});
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) {
return TasksScreen(
group: group,
);
},
),
);
},
child: Container(
margin: EdgeInsets.all(10.0),
child: Material(
borderRadius: BorderRadius.circular(10.0),
color: Color(0xFF626931),
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 10.0,
vertical: 5.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
group.title,
style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.w700),
),
Row(
children: <Widget>[
Text(
'12 Tasks',
style: TextStyle(
fontSize: 10.0,
fontWeight: FontWeight.bold,
color: Colors.white70,
),
),
],
),
],
),
),
),
),
);
}
}
class TasksScreen extends StatelessWidget {
final Group group;
TasksScreen({this.group});
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider.value(
value: group,
child: Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(
icon: Icon(
FontAwesomeIcons.angleLeft,
size: 30.0,
),
onPressed: () {
Navigator.pop(context);
},
),
Text(
group.title,
style: TextStyle(
fontSize: 30.0,
fontWeight: FontWeight.bold,
),
),
IconButton(
icon: Icon(
FontAwesomeIcons.plusCircle,
size: 30.0,
),
onPressed: () {
// add task to group functionality
},
),
],
),
Expanded(
child: Consumer<TaskData>(
builder: (context, taskData, child) {
print(taskData);
return ListView.builder(
itemBuilder: (context, index) {
final task = taskData.tasks[index];
return TaskTile(
name: task.name,
isChecked: task.isDone,
onLongPressCallback: () {
taskData.deleteTask(task);
},
checkboxCallback: (checkboxState) {
taskData.updateTask(task);
},
);
},
itemCount: taskData.taskCount,
);
},
),
),
],
),
),
),
);
}
}
class TaskTile extends StatelessWidget {
final bool isChecked;
final String name;
final Function checkboxCallback;
final Function onLongPressCallback;
TaskTile(
{this.name,
this.isChecked,
this.checkboxCallback,
this.onLongPressCallback});
#override
Widget build(BuildContext context) {
return ListTile(
onLongPress: onLongPressCallback,
title: Text(
name,
style: TextStyle(
decoration: isChecked ? TextDecoration.lineThrough : null,
),
),
trailing: Checkbox(
value: isChecked,
onChanged: checkboxCallback,
),
);
}
}
class GroupData extends ChangeNotifier {
List<Group> _groups = [
Group(
title: 'Group 1',
),
Group(
title: 'Group 2',
),
];
get groups {
return _groups;
}
void updateGroupData() {
notifyListeners();
}
void addGroup(Group group) {
_groups.add(group);
notifyListeners();
}
get groupCount {
return groups.length;
}
}
class TaskData extends ChangeNotifier {
List<Task> _tasks = [
Task(name: 'Buy milk'),
Task(name: 'watch a movie'),
Task(name: 'have a coffee'),
];
get tasks {
return _tasks;
}
int get taskCount {
return _tasks.length;
}
void addTask(Task newTask) {
_tasks.add(newTask);
notifyListeners();
}
void updateTask(Task task) {
task.toggleIsDone();
notifyListeners();
}
void deleteTask(Task task) {
_tasks.remove(task);
notifyListeners();
}
}
class Group extends ChangeNotifier {
String title;
TaskData taskData;
Group({this.taskData, this.title});
}
class Task {
String name;
bool isDone;
Task({this.isDone = false, this.name});
void toggleIsDone() {
isDone = !isDone;
}
}
From what I see from your code is you are passing data from taskData variable to task variable and then calling your TaskTile.
For handling separate states for each you need to update values in your array list i.e, taskData variable,
try passing taskData.task[index] directly to your `TaskTile it shall handle theie individual state then
ListView.builder(
itemBuilder: (context, index) {
return TaskTile(
name: taskData.tasks[index].name,
isChecked: taskData.tasks[index].isDone,
onLongPressCallback: () {
taskData.deleteTask(taskData.tasks[index]);
},
checkboxCallback: (checkboxState) {
taskData.updateTask(taskData.tasks[index]);
},
);
,
itemCount: taskData.taskCount,
);
follow same for GroupTile
Thanks #Vicky Salunkhe for your Answer, but what you have mentioned is already there.
After a 2 days Brainstorm I got a solution.
Instead of this:
class GroupTile extends StatelessWidget {
final Group group;
GroupTile({
#required this.group,
});
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) {
return TasksScreen(
group: group,
);
},
),
);
},
);
}
}
The working solution is this:
create a new TaskData instance with final TaskData taskData = TaskData();
and use this new TaskData instance to create a provider with existing instance:
ChangeNotifierProvider.value(
value: taskData,
child: TasksScreen(
group: group,
),
);
full code example:
class GroupTile extends StatelessWidget {
#override
Widget build(BuildContext context) {
final group = Provider.of<Group>(context);
final TaskData taskData = TaskData();
return GestureDetector(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) {
return ChangeNotifierProvider.value(
value: taskData,
child: TasksScreen(
group: group,
),
);
},
),
);
},
);
}
}
am making a shopping cart app where I want to calculate the total price of the products present in the cart I made a function for it and tried executing in init state but it's not working
import 'package:flutter/material.dart';
import 'package:shop/Models/Database.dart';
class Cart extends StatefulWidget {
#override
_CartState createState() => _CartState();
}
class _CartState extends State<Cart> {
int sum = 0;
total() {
studlist.forEach((element) {
sum = sum + element.price;
});
}
#override
void initState() {
total();
super.initState();
}
final DbStudentManager dbmanager = new DbStudentManager();
Student cart;
List<Cart> cartList;
int updateIndex;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
title: Text(
'Cart',
style: TextStyle(color: Colors.black),
),
),
body: FutureBuilder(
future: dbmanager.getStudentList(),
builder: (context, snapshot) {
if (snapshot.hasData) {
studlist = snapshot.data;
}
return ListView.builder(
itemCount: studlist == null ? 0 : studlist.length,
itemBuilder: (BuildContext context, int index) {
Student ct = studlist[index];
return Card(
child: ListTile(
title: Text(ct.name),
subtitle: Text('${ct.price}'),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
dbmanager.deleteStudent(ct.id);
setState(() {
studlist.remove(index);
});
}),
),
);
});
}),
bottomNavigationBar: Row(
children: <Widget>[
Container(
padding: EdgeInsets.all(10),
child: Row(
children: <Widget>[
Text('$sum'),
SizedBox(
width: 10,
),
Text('Check Out'),
],
),
),
],
),
);
}
}
the error I get:
The method 'forEach' was called on null.
Receiver: null
Tried calling: forEach(Closure: (Student) => Null)
Try this:
import 'package:flutter/material.dart';
import 'package:shop/Models/Database.dart';
class Cart extends StatefulWidget {
#override
_CartState createState() => _CartState();
}
class _CartState extends State<Cart> {
int sum = 0;
#override
void initState() {
super.initState();
}
final DbStudentManager dbmanager = new DbStudentManager();
Student cart;
List<Cart> studList=[];
int updateIndex;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
title: Text(
'Cart',
style: TextStyle(color: Colors.black),
),
),
body: FutureBuilder(
future: dbmanager.getStudentList(),
builder: (context, snapshot) {
if (snapshot.hasData) {
studlist = snapshot.data;
studlist.forEach((element) {
setState((){
sum = sum + element.price;
});
});
}
return ListView.builder(
itemCount: studlist == null ? 0 : studlist.length,
itemBuilder: (BuildContext context, int index) {
Student ct = studlist[index];
return Card(
child: ListTile(
title: Text(ct.name),
subtitle: Text('${ct.price}'),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
dbmanager.deleteStudent(ct.id);
setState(() {
studlist.remove(index);
});
}),
),
);
});
}),
bottomNavigationBar: Row(
children: <Widget>[
Container(
padding: EdgeInsets.all(10),
child: Row(
children: <Widget>[
Text('$sum'),
SizedBox(
width: 10,
),
Text('Check Out'),
],
),
),
],
),
);
}
}
Try running total() function if the data when the data is loaded, not in init. As data is initially empty, running it inside init will cause this error.
if (snapshot.hasData) {
studlist = snapshot.data;
total();
}
Full Code:
import 'package:flutter/material.dart';
import 'package:shop/Models/Database.dart';
class Cart extends StatefulWidget {
#override
_CartState createState() => _CartState();
}
class _CartState extends State<Cart> {
int sum = 0;
total() {
studlist.forEach((element) {
sum = sum + element.price;
});
}
final DbStudentManager dbmanager = new DbStudentManager();
Student cart;
List<Cart> cartList;
int updateIndex;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
title: Text(
'Cart',
style: TextStyle(color: Colors.black),
),
),
body: FutureBuilder(
future: dbmanager.getStudentList(),
builder: (context, snapshot) {
if (snapshot.hasData) {
studlist = snapshot.data;
total(); //Run the total() function here
}
return ListView.builder(
itemCount: studlist == null ? 0 : studlist.length,
itemBuilder: (BuildContext context, int index) {
Student ct = studlist[index];
return Card(
child: ListTile(
title: Text(ct.name),
subtitle: Text('${ct.price}'),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
dbmanager.deleteStudent(ct.id);
setState(() {
studlist.remove(index);
});
}),
),
);
});
}),
bottomNavigationBar: Row(
children: <Widget>[
Container(
padding: EdgeInsets.all(10),
child: Row(
children: <Widget>[
Text('$sum'),
SizedBox(
width: 10,
),
Text('Check Out'),
],
),
),
],
),
);
}
}
try this
If I added Quantity increased totalQty but I have one issue if I removing Item in list
did not decrease. sorry my english not good
`total() {
studlist.forEach((element) {
if(element.qty!=null){
totalQty=totalQty+element.qty;
print(totalQty);
}
});
}
`