Hello any flutter experts is here who can help solving little issue i want implement flutter voice recognition. flutter speech_to_text: ^5.4.3. plugin which is work fine but problem is that i want implement like native voice recognition on top bar when user click on mic button its appears alert dialog showing in picture below and when user speak its shows text like image below anyone know about here is picture click on it
I also used custom dialog builder for this purpose but my text is not update in dialog text check code below
import 'dart:math';
import 'package:bibleapp/Dbhelper.dart';
import 'package:bibleapp/chapters.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:speech_to_text/speech_recognition_result.dart';
import 'package:speech_to_text/speech_to_text.dart' as stt;
import 'package:speech_to_text/speech_to_text.dart';
import 'package:avatar_glow/avatar_glow.dart';
class Booknames extends StatefulWidget {
const Booknames({Key? key}) : super(key: key);
#override
_BooknamesState createState() => _BooknamesState();
}
class _BooknamesState extends State<Booknames> {
var booknames=['Genesis','Exodus','Leviticus','Numbers',
'Deuteronomy','Joshua',
'Judges','Ruth','1 Samuel','2 Samuel','1 Kings','2 Kings',
'1 Chronicles','2 Chronicles','Ezra','Nehemiah','Esther',
'Job','Psalms','Proverbs','Ecclesiastes','Song of Solomon','Isaiah','Jeremiah',
'Lamentations','Ezekiel','Daniel','Hosea','Joel','Amos','Obadiah','Jonah','Micah',
'Nahum','Habakkuk','Zephaniah','Haggai','Zechariah','Malachi','Matthew',
'Mark','Luke','John','Acts','Romans','1 Corinthians','2 Corinthians','Galatians',
'Ephesians','Philippians','Colossians','1 Thessalonians','2 Thessalonians','1 Timothy',
'2 Timothy','Titus','Philemon','Hebrews','James','1 Peter',
'2 Peter','1 John','2 John','3 John','Jude','Revelation'];
Dbhelper dbhelper=new Dbhelper();
stt.SpeechToText speechToText=stt.SpeechToText();
bool islistening=false;
//this text i want change after listening
String text='Example:Gensis Chapter 1 verse 5';
#override
void initState() {
// TODO: implement initState
super.initState();
dbhelper.db;
_initSpeech();
}
/// This has to happen only once per app
void _initSpeech() async {
speechToText.initialize();
}
///this dialog when user press on mic button it show alert alert dialog button
showAlertDialog() {
Widget okButton = TextButton(
child: Text("CANCEL VOICE"),
onPressed: () => Navigator.of(context).pop(false),
);
AlertDialog alert = AlertDialog(
title: Text("Search by voice"),
content: Container(
height: 180,
child: Column(
children: [
AvatarGlow(
glowColor: Colors.blue,
endRadius: 75,
duration: Duration(milliseconds: 2500),
repeat: true,
showTwoGlows: islistening,
repeatPauseDuration: Duration(milliseconds: 150),
child: Material(
elevation: 5,
shape: CircleBorder(),
child: CircleAvatar(
backgroundColor: Colors.white,
child: Icon(Icons.mic, color: Colors.blue, size: 30,),
radius: 50,
),
),
),
Expanded(child: Container(
child: Text(text),
)),
],
),
),
actions: [
okButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
new IconButton(
///when user clickec on mic button dialog and speech rcoginition methods calll
icon: new Icon(islistening?Icons.mic:Icons.mic_none),
highlightColor: Colors.pink,
onPressed:(){
setState(() {
showAlertDialog();
_listen();
});
},
),
],
elevation: 0,
title: Text('The Bible Multiversion', style: TextStyle(
fontSize: 20
),),
centerTitle: true,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: ListView.separated(
shrinkWrap: true,
itemCount: booknames.length,
separatorBuilder: (BuildContext context, int index) =>
Divider(height: 1),
itemBuilder: (context, index) {
return Column(
children: [
GestureDetector(
onTap: () {
int increment = index + 1;
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) =>
chapters(increment, booknames[index]),
transitionDuration: Duration(seconds: 0),
),
);
// Navigator.push(
// context,
// MaterialPageRoute(builder: (context) => chapters(increment)),
// );
},
child: ListTile(
leading: CircleAvatar(
radius: 20,
backgroundColor: Colors.primaries[Random().nextInt(
Colors.primaries.length)],
child: Text(
booknames[index].substring(0, 1), style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.bold,
color: Colors.white
),),
),
title: Text(
booknames[index], style: TextStyle(
color: Colors.black,
fontSize: 20
),
),
),
),
],
);
},
),
),
],
),
);
}
//this voice listener method
void _listen() async {
if (!islistening) {
bool available = await speechToText.initialize(
onStatus: (val) => print('onStatus: $val'),
onError: (val) => print('onError: $val'),
);
if (available) {
setState(() {
islistening=true;
});
speechToText.listen(
onResult: (result)=>setState(() {
//this text is not updating in dialog
text=result.recognizedWords;
//but this print method continues printing spkoen word in console
print('result.recognizedWords')
})
);
}
} else {
setState(() => islistening = false
);
speechToText.stop();
}
}
}
I think this is a common problem, setState does not work for dialog, because Dialog is not the part of the tree.
this is showDialog()'s document:
/// This function takes a `builder` which typically builds a [Dialog] widget.
/// Content below the dialog is dimmed with a [ModalBarrier]. The widget
/// returned by the `builder` does not share a context with the location that
/// `showDialog` is originally called from. Use a [StatefulBuilder] or a
/// custom [StatefulWidget] if the dialog needs to update dynamically.
you can use StatefulBuilder, refer to How to refresh an AlertDialog in Flutter?
Related
Hello friends my problem is that I want perform task there small icon on menu bar mic icon when user click on Mic it show dialog box and when they speak text word appear on dialog box when user finished up there speech it's goes to next screen I have done all thing but problem is that when user finishing there speech i unable to go on next sacreen this only issue any expert can help here is picture below what i actually i want
Here is when user speech
When they complete their speech go next sacreen
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:speech_to_text/speech_recognition_result.dart';
import 'package:speech_to_text/speech_to_text.dart' as stt;
import 'package:speech_to_text/speech_to_text.dart';
import 'package:avatar_glow/avatar_glow.dart';
class Booknames extends StatefulWidget {
const Booknames({Key? key}) : super(key: key);
#override
_BooknamesState createState() => _BooknamesState();
}
class _BooknamesState extends State<Booknames> {
stt.SpeechToText speechToText = stt.SpeechToText();
bool islistening = false;
late String text = 'Example:Gensis chapter verse 5';
bool complete=false;
#override
void initState() {
// TODO: implement initState
super.initState();
_initSpeech();
}
/// This has to happen only once per app
void _initSpeech() async {
speechToText.initialize();
}
final GlobalKey _dialogKey = GlobalKey();
_showDialog() async {
showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return StatefulBuilder(
key: _dialogKey,
builder: (context, setState) {
return Container(
child: Dialog(
child: Padding(
padding: EdgeInsets.all(8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
AvatarGlow(
glowColor: Colors.blue,
endRadius: 80,
duration: Duration( milliseconds: 2500),
repeat: true,
showTwoGlows: true,
repeatPauseDuration: Duration( milliseconds: 150),
child: Material(
elevation: 5,
shape: CircleBorder(),
child: CircleAvatar(
backgroundColor: Colors.white,
child: Icon(Icons.mic, color: Colors.blue, size: 40,),
radius: 40,
),
),
),
Text(text),
SizedBox(height: 10),
TextButton(
onPressed: () => Navigator.pop(context, false), // passing false
child: Text('Cancel Voice'),
),
],
),
),
),
);
},
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
new IconButton(
icon: new Icon(islistening ? Icons.mic : Icons.mic_none),
highlightColor: Colors.pink,
onPressed: () {
setState(() {
_listen();
_showDialog();
});
},
),
],
elevation: 0,
title: Text('The Bible Multiversion', style: TextStyle(
fontSize: 20
),),
centerTitle: true,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: ListView.separated(
shrinkWrap: true,
itemCount: 1,
separatorBuilder: (BuildContext context, int index) =>
Divider(height: 1),
itemBuilder: (context, index) {
return Column(
children: [
],
);
},
),
),
],
),
);
}
void _listen() async {
if (!islistening) {
bool available = await speechToText.initialize(
onStatus: (val) => print('onStatus: $val'),
onError: (val) => print('onError: $val'),
);
if (available) {
setState(() {
islistening = true;
});
speechToText.listen(
onResult: (result) =>
setState(() {
text = result.recognizedWords;
if (_dialogKey.currentState != null && _dialogKey.currentState!.mounted) {
_dialogKey.currentState!.setState(() {
text =result.recognizedWords;
});
}
})
);
}
} else {
setState(() => islistening = false
);
speechToText.stop();
}
}
}
Hi guys I'm trying to build an app with flutter, so I have two screens HomeScreen() and RoutineScreen(). The first one is a Scaffold and in the body has a child Widget (a ListView called RoutinesWidget()) with all the routines. And the second one is to create a routine. The thing is, that when I create the routine, I use a button to pop to the HomeScreen() but it doesn't refresh the ListView (I'm guessing that it's because when I use Navigator.pop() it refreshes the Scaffold but not the child Widget maybe?)
HomeScreen() code here:
import 'package:flutter/material.dart';
import 'package:workout_time/constants.dart';
import 'package:workout_time/Widgets/routines_widget.dart';
import 'package:workout_time/Widgets/statistics_widget.dart';
import 'package:workout_time/Screens/settings_screen.dart';
import 'package:workout_time/Screens/routine_screen.dart';
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _selectedIndex = 0;
List<Widget> _views = [
RoutinesWidget(),
StatisticsWidget(),
];
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: kThirdColor,
appBar: AppBar(
leading: Icon(Icons.adb),
title: Text("Workout Time"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.settings),
onPressed: () => Navigator.push(context,
MaterialPageRoute(builder: (context) => SettingsScreen()))),
],
),
body: _views[_selectedIndex],
floatingActionButton: (_selectedIndex == 1)
? null
: FloatingActionButton(
onPressed: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RoutineScreen(null)));
setState(() {});
},
child: Icon(
Icons.add,
color: kSecondColor,
size: 30.0,
),
elevation: 15.0,
),
bottomNavigationBar: BottomNavigationBar(
items: <BottomNavigationBarItem>[
bottomItems(Icon(Icons.fitness_center_rounded), "Routines"),
bottomItems(Icon(Icons.leaderboard_rounded), "Statistics"),
],
currentIndex: _selectedIndex,
onTap: (int index) => setState(() => _selectedIndex = index),
),
);
}
}
BottomNavigationBarItem bottomItems(Icon icon, String label) {
return BottomNavigationBarItem(
icon: icon,
label: label,
);
}
RoutinesWidget() code here:
import 'package:flutter/material.dart';
import 'package:workout_time/Services/db_crud_service.dart';
import 'package:workout_time/Screens/routine_screen.dart';
import 'package:workout_time/constants.dart';
import 'package:workout_time/Models/routine_model.dart';
class RoutinesWidget extends StatefulWidget {
#override
_RoutinesWidgetState createState() => _RoutinesWidgetState();
}
class _RoutinesWidgetState extends State<RoutinesWidget> {
DBCRUDService helper;
#override
void initState() {
super.initState();
helper = DBCRUDService();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: helper.getRoutines(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
Routine routine = Routine.fromMap(snapshot.data[index]);
return Card(
margin: EdgeInsets.all(1.0),
child: ListTile(
leading: CircleAvatar(
child: Text(
routine.name[0],
style: TextStyle(
color: kThirdOppositeColor,
fontWeight: FontWeight.bold),
),
backgroundColor: kAccentColor,
),
title: Text(routine.name),
subtitle: Text(routine.exercises.join(",")),
trailing: IconButton(
icon: Icon(Icons.delete_rounded),
color: Colors.redAccent,
onPressed: () {
setState(() {
helper.deleteRoutine(routine.id);
});
},
),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RoutineScreen(routine))),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0)),
color: kSecondColor,
);
},
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
);
}
}
RoutineScreen() code here:
import 'package:flutter/material.dart';
import 'package:workout_time/Models/routine_model.dart';
import 'package:workout_time/Widgets/type_card_widget.dart';
import 'package:workout_time/constants.dart';
import 'package:workout_time/Services/db_crud_service.dart';
class RoutineScreen extends StatefulWidget {
final Routine _routine;
RoutineScreen(this._routine);
#override
_RoutineScreenState createState() => _RoutineScreenState();
}
class _RoutineScreenState extends State<RoutineScreen> {
DBCRUDService helper;
final _nameController = TextEditingController();
final _descriptionController = TextEditingController();
bool _type = true;
int _cycles = 1;
int _restBetweenExercises = 15;
int _restBetweenCycles = 60;
#override
void initState() {
super.initState();
helper = DBCRUDService();
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
title: widget._routine != null
? Text(widget._routine.name)
: Text("Create your routine"),
actions: [
IconButton(
icon: Icon(Icons.done_rounded),
onPressed: createRoutine,
)
],
bottom: TabBar(
tabs: [
Tab(
text: "Configuration",
),
Tab(
text: "Exercises",
),
],
),
),
body: TabBarView(children: [
//_routine == null ? ConfigurationNewRoutine() : Text("WIDGET N° 1"),
ListView(
children: [
Container(
padding: EdgeInsets.all(15.0),
child: Row(
children: [
Text(
"Name:",
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
),
SizedBox(
width: 40.0,
),
Expanded(
child: TextField(
textAlign: TextAlign.center,
controller: _nameController,
),
),
],
),
),
SizedBox(
height: 20.0,
),
Card(
margin: EdgeInsets.all(15.0),
color: kSecondColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
child: Container(
padding: EdgeInsets.all(15.0),
child: Column(
children: [
Text(
"Type",
style: TextStyle(fontSize: 25.0),
),
Row(
children: [
Expanded(
child: TypeCard(
Icons.double_arrow_rounded,
_type == true ? kFirstColor : kThirdColor,
() => setState(() => _type = true),
"Straight set",
),
),
Expanded(
child: TypeCard(
Icons.replay_rounded,
_type == false ? kFirstColor : kThirdColor,
() => setState(() => _type = false),
"Cycle",
),
),
],
),
],
),
),
),
SizedBox(
height: 20.0,
),
Container(
padding: EdgeInsets.all(15.0),
child: Row(
children: [
Text(
"N° cycles:",
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
),
SizedBox(
width: 40.0,
),
Expanded(
child: Text("Hello"),
),
],
),
),
SizedBox(
height: 20.0,
),
],
),
Text("WIDGET N° 2"),
]),
),
);
}
void createRoutine() {
List<String> _exercises = ["1", "2"];
List<String> _types = ["t", "r"];
List<String> _quantities = ["30", "20"];
Routine routine = Routine({
'name': _nameController.text,
'description': "_description",
'type': _type.toString(),
'cycles': 1,
'numberExercises': 2,
'restBetweenExercises': 15,
'restBetweenCycles': 60,
'exercises': _exercises,
'types': _types,
'quantities': _quantities,
});
setState(() {
helper.createRoutine(routine);
Navigator.pop(context);
});
}
}
Any idea what can I do to make it work? Thank you
Make it simple
use Navigator.pop() twice
so that the current class and old class in also removed
from the stack
and then use Navigator.push()
When you push a new Route, the old one still stays in the stack. The new route just overlaps the old one and forms like a layer above the old one. Then when you pop the new route, it will just remove the layer(new route) and the old route will be displayed as it was before.
Now you must be aware the Navigator.push() is an asynchronous method and returns a Future. How it works is basically when you perform a Navigator.push(), it will push the new route and will wait for it to be popped out. Then when the new route is popped, it returns a value to the old one and that when the future callback will be executed.
Hence the solution you are looking for is add a future callback like this after your Navigator.push() :
Navigator.push(context,
MaterialPageRoute(builder: (context) => SettingsScreen())
).then((value){setState(() {});}); /// A callback which is executed after the new route will be popped. In that callback, you simply call a setState and refresh the page.
what is the proper way to call Dialog function from another class.
I have been searching this topic for a while but seems none of them are my answer.
my Dialog has a little complicated logic for server communicating and some paginations
so this code is going to be long for just one dart file. so I want to separate them.
and I need the some dialog animations so I picked the showGeneralDialog()
I also saw the example dialog implementaion using StatefulBuilder() which can use setState,
but this problem is it is not able to use initState()
for now, what I did is below
dart1 file
import 'package:aaa/bbb/some_dialog_file.dart'
as someDialog;
GestureDetector(
onTap: () async{
var result =
await someDialog.displayDialogOKCallBack(
context,
);
},
child: Container(
width: 60,
height: 60,
child: Icon(
Icons.comment,
size: 38,
),
),
)
dart2 file
Future<dynamic> displayDialogOKCallBack(BuildContext context) async {
return await showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: true,
// barrierColor: ,
transitionDuration: Duration(milliseconds: 400),
context: context,
pageBuilder: (context, anim1, anim2) {
return StatefulBuilder(builder: (context, setState) {
return Scaffold(
body: SafeArea(
),
);
});
},
transitionBuilder: (context, anim1, anim2, child) {
return SlideTransition(
position:
Tween(begin: Offset(0, 1), end: Offset(0, -0.02)).animate(anim1),
child: child,
);
},
);
}
so my question is I want to build very clean animation dialog
which is logically separated from base class file and it has to have initState(), and setState()
how could I acheive this ? thanks
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Container(
child: RaisedButton(
onPressed: () {
someDialog(context);
},
child: Text("click"),
),
);
}
Future<dynamic> someDialog(BuildContext context) async {
return await showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: true,
context: context,
pageBuilder: (context, anim1, anim2) {
return Scaffold(
backgroundColor: Colors.transparent,
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
// List
AnotherClassDialog(),
],
),
],
),
),
),
);
});
}
}
class AnotherClassDialog extends StatefulWidget {
#override
_AnotherClassDialogState createState() => _AnotherClassDialogState();
}
class _AnotherClassDialogState extends State<AnotherClassDialog> {
Color color;
#override
void initState() {
// TODO: implement initState
super.initState();
color = Colors.black;
}
#override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
RaisedButton(
onPressed: () {
setState(() {
color = Colors.red;
});
},
),
Container(
width: 100,
height: 100,
color: color,
),
RaisedButton(
onPressed: () {
setState(() {
color = Colors.green;
});
},
)
],
),
);
}
}
I use a custom dialog in my app in some classes and had the same problem.
You should define a dialog and pass context and other variables to it and call it everywhere you want.
You can define a dialog like this :
showCustomDialog(BuildContext context, String title, String description) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(
title,
textAlign: TextAlign.right,
),
content: SingleChildScrollView(
child: Text(
description,
style: Theme.of(context).textTheme.bodyText1,
textAlign: TextAlign.right,
),
),
actions: [
FlatButton(
child: Text(
'ok',
style: Theme.of(context).textTheme.bodyText2.copyWith(
color: Theme.of(context).accentColor,
),
),
onPressed: () => Navigator.of(context).pop(),
),
],
actionsPadding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 5,
),
);
});
}
and use it everywhere you want like this :
InkWell(
child: Icon(
Icons.error_outline,
size: 17,
),
onTap: () => showCustomDialog(context,"text1" , "text2") ,
),
I hope my answer will help you.
The Provider package makes use of InheritedWidget. This is a problem when I want to access a provider when I'm in a Dialog. If I load a dialog using
showDialog(... builder: (context) => MyDialog);
I can't access anything using InheritedWidget because my dialog isn't part of the main widget tree. This also means that I can't access my Provider providers, correct?
My question is: How can I access my providers in a dialog if it's not part of the main app widget tree?
final firebaseAuth = Provider.of<FirebaseAuth>(context);
I have the same problem with using BLoCs. If I try to retrieve them in a dialog via InheritedWidget, they fail. I've gotten around this by passing the BLoC in the constructor but this seems to defeat the purpose of InheritedWidgets.
Instead of passing the BLoC in the constructor, you can make use of BlocProvider.value.
https://pub.dev/documentation/flutter_bloc/latest/flutter_bloc/BlocProvider/BlocProvider.value.html
This will allow you to provide your existing BLoC instance to your new route (the dialog). And you still get all the benefits of InheritedWidget
// Get the BLoC using the provider
MyBloc myBloc = BlocProvider.of<MyBloc>(context);
showDialog(
context: context,
builder: (BuildContext context) {
Widget dialog = SimpleDialog(
children: <Widget>[
... // Now you can call BlocProvider.of<MyBloc>(context); and it will work
],
);
// Provide the existing BLoC instance to the new route (the dialog)
return BlocProvider<MyBloc>.value(
value: myBloc, //
child: dialog,
);
},
);
.value() also exists for ChangeNotifierProvider, ListenableProvider, etc.
https://pub.dev/documentation/provider/latest/provider/ChangeNotifierProvider/ChangeNotifierProvider.value.html
https://pub.dev/documentation/provider/latest/provider/ListenableProvider/ListenableProvider.value.html
I got stuck at this part for a while. I honestly didn't want to pass the provider, also unpacking the widget code to grab the parent context is hard when you are dealing with a complex widget (And it doesn't seem like the best approach).
This made more sense
handleFileViewerClicked(context) async {
var reportState = Provider.of<ReportState>(context, listen: false);
/**
*The dialog will live in a new context and requires a new provider to be created for the report state
* For more information read the Provider.Consumer documentation and showDialog function signature.
*/
showDialog(
context: context,
//Notice the use of ChangeNotifierProvider<ReportState>.value
builder: (_) => ChangeNotifierProvider<ReportState>.value(
value: reportState,
child: FileViewer(),
),
);
}
Your child widget which is FileViewer in that case can make use of
class FileViewer extends StatelessWidget {
.
.
Widget build(BuildContext context) {
//you can enable or disable listen if you logic require so
var reportState = Provider.of<ReportState>(context);
return Text('${reportState.files.length}');
}
}
I was able to access Provider data by passing in the data set into the alert dialog. Interestingly, you have to call setState() in the Dialog in order to see the changes in your Dialog.
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
final provider = Provider.of<DataSet>(context);
return Scaffold(
body: Container(
child: RaisedButton(
child: Text('Show Dialog'),
onPressed: () {
showDialog(context: context,
builder: (context) {
return DialogContent(dataSet: provider);
});
},
),
),
);
}
}
class DialogContent extends StatefulWidget {
final DataSet dataSet;
const DialogContent({Key key, this.dataSet}) : super(key: key);
#override
_DialogContentState createState() => _DialogContentState();
}
class _DialogContentState extends State<DialogContent> {
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Dialog with data'),
content: Text('${widget.dataSet.pieceOfData}'),
actions: <Widget>[
FlatButton(
child: Text('Increase Data'),
onPressed: () {
setState(() {
widget.dataSet.increaseData();
});
},
),
],
);
}
}
class DataSet with ChangeNotifier {
int pieceOfData = 1;
increaseData() {
pieceOfData += 1;
notifyListeners();
}
}
Try this. Create a different stateful widget that housed the dialog and return that dialog stateful widget when you call a showDialog() method. Example below
class MainScreen extends StatefulWidget {
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build((BuildContext context) {
MainProvider mainProvider = MainProvider.of(context);
return Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.white,
),
body: Center(
child: Container(
child: RaisedButton(
onPressed: ()=> _openBottomSheet(context, mainProvider),
child: Text("Open Dialog"),
)
)
)
);
}
_openBottomSheet(BuildContext context, MainProvider mainProvider) async {
await showModalBottomSheet<bool>(
context: cntxt,
builder: (_) {
return BottomSheetDialog();
}
);
}
}
class BottomSheetDialog extends StatefulWidget {
#override
_BottomSheetDialogState createState() => _BottomSheetDialogState();
}
class _BottomSheetDialogState extends State<BottomSheetDialog> {
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
MainProvider mainProvider = MainProvider.of(context);
return Container(
width: MediaQuery.of(context).size.width,
height:MediaQuery.of(context).size.height/2.2,
margin: EdgeInsets.fromLTRB(16,16,16,0),
decoration: BoxDecoration(
color: mainProvider.color,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
),
child: RaisedButton(
onPressed: ()=> mainProvider.changeColor(),
child: Text("Open Dialog"),
)
)
}
}
class MainProvider with ChangeNotifier {
static MainProvider of(BuildContext context) {
return Provider.of<MainProvider>(context);
}
Color _color = Colors.white;
bool _isColorChanged = false;
Color get color => _color;
bool get isColorChanged => _isColorChanged;
changeColor() {
if(!isColorChanged) {
_color = Colors.green;
}else{
_color = Colors.white;
}
_isColorChanged = !_isColorChanged;
notifyListeners();
}
}
If that's an option for you, simply lift the provider up above MaterialApp. This might be a good solution for globally unique providers, e.g. user configurations or similar:
You have to pass the thing being provided directly to the dialog constructor to access it in the dialog's new context. You can also give it to a new Provider widget at the top of your dialog tree if you have a very deep widget tree in the dialog and you want to access it from somewhere deeper.
If you are using Bloc, typically you tell Provider to call the Bloc's dispose method when the provider widget is disposed to clean up the streamcontrollers/subscriptions. Obviously, you might not want to do this if you are re-providing the bloc to the dialog, or if this bloc is used outside the dialog.
Using stateful or stateless widgets in the dialog is up to you, as long as you have access to the bloc you can use a streambuilder and listen to some stream as per usual.
an example:
class EditEventDialog extends StatelessWidget {
final GroupBloc groupBloc;
EditEventDialog({this.groupBloc})
: assert(groupBloc != null);
#override
Widget build(BuildContext context) {
return Provider(
builder: (context) => groupBloc,
child: Dialog(
child: Container(
height: 400.0,
width: 200.0,
child: StreamBuilder<StatusStreamType>(
stream: groupBloc.statusStream,
builder: (context, snapshot) {
....
and to call it:
onPressed: () => showDialog(
builder: (newContext) {
GroupBloc groupBloc = Provider.of<GroupBloc>(context);
return EditEventDialog(
groupBloc: groupBloc,
);
},
context: context,
)
I faced the same issue today and I was able to work around it by wrapping the dialog in a Stateful Builder and setting the state in the new widget tree.
context: context,
builder: (context) {
return StatefulBuilder(builder: (context, setState) {
return Dialog(
child: SingleChildScrollView(
child: Container(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(vertical: height * .05),
child: Text('Choose An Avatar'),
),
Stack(
children: <Widget>[
Align(
alignment: Alignment.center,
child: CircleAvatar(
minRadius: width * .09,
maxRadius: width * .09,
backgroundColor: Colors.brown,
backgroundImage: AssetImage(
'assets/profile${appData.avatar}.png'),
),
),
Positioned.fill(
left: width * .04,
child: Align(
alignment: Alignment.centerLeft,
child: Container(
width: width * .18,
child: Material(
color: Colors.transparent,
child: InkWell(
child: Icon(Icons.arrow_left,
size: width * .18),
onTap: () {
setState(() {
appData.changeAvatar();
});
},
),
),
),
),
),
],
),
],
),
),
),
),
);
});
});
I only way I've found to gain access to the Bloc provider from within the dialog is by defining the dialog outside of the showDialog call.
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocConsumer<MyCubit, MyState>(
listener: (context, state) {
if (state.shouldShowDialog == true) {
final dialog = AlertDialog(
content: Text("Info");
actions: <Widget>[
TextButton(
child: const Text('Approve'),
onPressed: () => {
context
.read<MyCubit>()
.handleDialogApproved();
Navigator.of(context, rootNavigator: true).pop();
}
)
],
);
showDialog<void>(
context: context,
builder: (BuildContext context) {
return dialog;
},
);
}
},
builder: (context, state) {
return Container();
},
);
}
}
Widget reviseRatesButton(BuildContext c) {
return Consumer<RideRequestProvider>(
builder: (c, provider, child) {
return OutlinedButton(
onPressed: () async {
alertDialogNew(
c,
content: ChangeNotifierProvider.value(
value: provider,
builder: (context, child) {
return Consumer<RideRequestProvider>(
builder: (context, provider, child) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
const Text(
"Offer your fare",
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
const SizedBox(
height: 5,
),
CustomTextFormField(
hint: "Enter your fair/day",
keyboardType: TextInputType.number,
controller: provider.fareController,
onChanged: (String? val) {
provider.calculateFare();
},
),
const SizedBox(
height: 5,
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'Weekly (5 days)',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
Text.rich(
TextSpan(
text: provider.weeklyFare
.toStringAsFixed(2),
children: [
TextSpan(
text: '/week',
style: TextStyle(
color: Colors.blue.shade700,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
],
),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
],
),
Column(
children: [
const Text(
'Monthly(22 days)',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
Text.rich(
TextSpan(
text: provider.monthlyFare
.toStringAsFixed(2),
children: [
TextSpan(
text: '/month',
style: TextStyle(
fontSize: 12,
color: Colors.blue.shade700,
fontWeight: FontWeight.w600,
),
),
],
),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
],
),
],
),
],
),
);
},
);
}),
);
},
child: const Text(
"Revise Rates",
),
style: OutlinedButton.styleFrom(
side: const BorderSide(width: 1.0, color: Colors.blue),
),
);
},
);}
I've been stuck at this for a few moments, but ChangeNotifierProvider.value works like a charm.
A bit late in finding this, but just had this same challenge and realised a solution: You need to maintain a reference to the context outside of the showDialog call. By default we usually just use "context" as the name of the context both outside and inside the showDialog, thus masking the outside context from use within the showDialog. So, instead, use a different name inside the showDialog (e.g. "c") and then you can still use "final firebaseAuth = Provider.of(context);" inside the showDialog and it will find the FirebaseAuth object from the main tree as you wish.
Here's a short excerpt from some code I am working on which works now:
showDialog(
context: context,
builder: (c) {
final action = Provider.of<ActionType>(context);
final host = Provider.of<String>(context);
return AlertDialog(
title: Text('Action API'),
actions: [
FlatButton(
onPressed: () {
Navigator.pop(c);
},
etc.
I have a timer widget, that when the green button is selected, it calls completeActivity() which opens up an AlertDialog asking the user if they wish to stop their exercise activity. If "yes" is clicked it should call another Dialog for rating pain. This issue is that the 2nd dialog isn't being called at all, and only navigates to another page after the first dialog.
I can only get the Pain Rating dialog to popup if I move the line of code after where the screen navigates to another page AND if that method is in the same class. I am needing the Pain Rating dialog in a separate class. I suspect it is something to do with the return values being a dialog
timer widget:
class TimeScreen extends StatefulWidget {
#override
_TimeScreenState createState() => _TimeScreenState();
}
class _TimeScreenState extends State<TimeScreen> {
var duration;
#override
Widget build(BuildContext context) {
var timerService = TimerService.of(context);
return new Container(
padding: EdgeInsets.all(20.0),
child: new Column(
children: <Widget>[
AnimatedBuilder(
animation: timerService,
builder: (context, child) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('${timerService.currentDuration.toString().substring(0,7)}',style: new TextStyle(fontSize: 25.0)),
SizedBox(height: 20.0),
new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FloatingActionButton(
heroTag: "btn1",
backgroundColor: Colors.red,
onPressed: !timerService.isRunning ? timerService.start : timerService.stop,
child: Icon(!timerService.isRunning ? Icons.play_arrow : Icons.pause)),
SizedBox(width: 20.0),
FloatingActionButton(
heroTag: "btn2",
backgroundColor: Colors.green,
onPressed: () {
timerService.stop();
completeActivity(context, timerService);
},
child: Icon(Icons.check)),
],
)]);
},
),
]),
);
}
completeActivity(BuildContext context, TimerService timerService) {
return showDialog(
context: context,
builder: (context) => new AlertDialog(
title: new Text('Complete Activity?',
style: new TextStyle(color: Colors.black, fontSize: 20.0)),
actions: <Widget>[
new FlatButton(
onPressed: () { User.getCurrentUser().getCurrentActivity().setDuration(timerService.currentDuration);
print("Final Time ${User.getCurrentUser().getCurrentActivity().getDuration()}");
User.getCurrentUser().setCurrentActivity(null);
timerService.reset();
Navigator.push(context, MaterialPageRoute(builder: (context) => FrontPage()));
RatePain();
},
child:
new Text('Yes', style: new TextStyle(fontSize: 18.0)),
),
new FlatButton(
onPressed: () {
Navigator.pop(context);
timerService.start();
}, // this line dismisses the dialog
child: new Text('No', style: new TextStyle(fontSize: 18.0)),
)
],
),
) ??
false;
}
pain rating widget:
class RatePain extends StatefulWidget {
#override
_RatePainState createState() => _RatePainState();
}
class _RatePainState extends State<RatePain> {
#override
Widget build(BuildContext context) {
return showDialog(
context: context,
barrierDismissible: false, // set to false if you want to force a rating
builder: (context) => (
new RatingDialog(
icon: Icon(
Icons.sentiment_satisfied,
color: Colors.grey,
size: 100,
),
title: "How much pain are you in?",
description:
"Tap a star to set your pain rating after this exercise."+
"\n1 = No pain"+
"\n10 = Extreme pain",
submitButton: "SUBMIT",
accentColor: Colors.blueAccent,
onSubmitPressed: (int rating) {
print("onSubmitPressed: rating = $rating");
User.getCurrentUser().getCurrentActivity().getStatistics().setPainRating(rating);
},
)));
}
After "yes" on the first dialog is selected, another popup for rating pain should popup.
You just need to navigate to another page when a user submits the ratings in that way you make sure to navigate to another page after everything has finished.
class RatePain extends StatefulWidget {
#override
_RatePainState createState() => _RatePainState();
}
class _RatePainState extends State<RatePain> {
#override
Widget build(BuildContext context) {
return showDialog(
context: context,
barrierDismissible: false, // set to false if you want to force a rating
builder: (context) => (
new RatingDialog(
icon: Icon(
Icons.sentiment_satisfied,
color: Colors.grey,
size: 100,
),
title: "How much pain are you in?",
description:
"Tap a star to set your pain rating after this exercise."+
"\n1 = No pain"+
"\n10 = Extreme pain",
submitButton: "SUBMIT",
accentColor: Colors.blueAccent,
onSubmitPressed: (int rating) {
print("onSubmitPressed: rating = $rating");
User.getCurrentUser().getCurrentActivity().getStatistics().setPainRating(rating);
Navigator.push(context, MaterialPageRoute(builder: (context) => FrontPage()));
},
)));
}