Alert Dialog not shown on button press - flutter

Class for Alert Dialog
class AlertWindow extends StatelessWidget {
final String title;
const AlertWindow({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return Builder(
builder:(BuildContext context) {
return AlertDialog(
title: Text(this.title),
actions: <Widget>[
new FlatButton(
onPressed: (){
Navigator.of(context).pop();
},
child: new Text(
"OK"
)
),
],
);
}
);
}
}
Its been called in a aysnc function like this
Future<ParseUser> SignUP(username, pass, email) async {
var user = ParseUser(username, pass, email); // You can add columns to user object adding "..set(key,value)"
var result = await user.create();
if (result.success) {
setState(() {
_parseUser = user; // Keep the user
});
print(user.objectId);
new AlertWindow(
title: "Signup success " + user.objectId,
);
} else {
print(result.error.message);
new AlertWindow(
title: "Signup error " + result.error.message,
);
}
}
on running this, I can see the print statements in the console, but the AlertWindow doesn't show up.
I've got a hunch that its probably got something to do with the parent BuildContext not been passed to the AlertDialog when I'm creating it.

Try to use function instead of using a widget
Create a new function which return a future
Future<dynamic> _showDialog(BuildContext context){
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(this.title),
actions: <Widget>[
new FlatButton(
onPressed: (){
Navigator.of(context).pop();
},
child: new Text(
"OK"
)
),
],
);
}
);
}

You need to call the function showDialog for the AlertDialog to appear:
class AlertWindow {
final String title;
final BuildContext context;
const AlertWindow({Key key, this.title, this.context});
void widget(){
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(this.title),
actions: <Widget>[
new FlatButton(
onPressed: (){
Navigator.of(context).pop();
},
child: new Text(
"OK"
)
),
],
);
}
);
}
}
Working example:
https://dartpad.dev/051f787e1737de84609a390d31c36ee0
https://api.flutter.dev/flutter/material/showDialog.html

Related

Flutter: My notifyListeners() doesn't work, but only in the release apk

I have a page that shows a loading while making my API call, and once the call is done it shows the received data.
On debugger everything works correctly, but when I create the apk with 'flutter build apk', and download it, the loading remains indefinitely.
I also put a showDialog at the end of my Provider function that makes the API call (I put this showDialog just below notifyListeners().
I can't understand why in debug it works and in release it doesn't.
(This notifyListeners thing not working just does it for every API call I make)
This is the code of the provider function that makes the api call:
Future<void> getUserSites(context) async {
_userSites.clear();
isLoading = true;
notifyListeners();
try {
final response = await NetworkService.call(
url: '/api/structure/Sites',
method: Method.Get,
context: context) as List<dynamic>;
for (var i = 0; i < response.length; i++) {
_userSites.add(Sites.fromJson(response.elementAt(i)));
}
if (defaultSite == null) {
if (SimplePreferences.getDefaultSite() == null) {
defaultSite = _userSites.isNotEmpty ? _userSites.first : null;
if (defaultSite != null) {
SimplePreferences.setDefaultSite(defaultSite!.id);
}
} else {
defaultSite = _userSites.firstWhere(
(element) => element.id == SimplePreferences.getDefaultSite()!);
}
}
} catch (e) {
inspect(e);
if (SimplePreferences.getToken() != null) {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('General Error'),
content: Text(e.toString()),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text(
'Ok',
),
)
],
),
);
}
// throw e;
}
isLoading = false;
notifyListeners();
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('getUserSites done!'),
content: Text(_userSites.toString()),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text(
'Ok',
),
)
],
),
);
}
this is the Home page code:
class HomePageScreen extends StatelessWidget { const HomePageScreen({super.key}); static const String routeName = '/';
#override Widget build(BuildContext context) { log('New Page: Home Page'); final provider = Provider.of<MyManager>(context);
return provider.isLoading ? const Center(
child: CircularProgressIndicator(),
)
: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
MainButton(
onTap: () async {
Navigator.of(context)
.pushNamed(ShowPatrolScreen.routeName);
await provider.getPatrol(context);
},
icon: Icons.home,
title: 'ShowPatrol',
),
printSito(provider.defaultSite?.description ?? 'Nessun Sito', context),
PrintRequestZ(
showCompleted: false,
),
],
),
),
);
}
Widget printSito(String name, context) { .... //pass context for Navigator and Theme } } `
this is the main page:
...
final myScreens = [
const HomePageScreen(),
...
];
#override
void initState() {
// TODO: implement initState
super.initState();
print('token: ${SimplePreferences.getToken()}');
if (SimplePreferences.getToken() == null){
Navigator.of(context).pushReplacementNamed('/Auth');
}
var provider = Provider.of<MyManager>(context, listen: false);
provider.setAll(context); //this function calls all my API calls, but for testing, I commented out all other functions and kept only the one written above
}
#override
Widget build(BuildContext context) {
var provider = Provider.of<MyManager>(context);
return Scaffold(
appBar: const MyAppBar(title: 'Ronda',canGoBack: false,),
body: myScreens[currentPage],
bottomNavigationBar: ...
),
}
Thanks in advance!
after some research i found the solution.
You have to use WidgetsBinding.instance.addPostFrameCallback
in the parent component.
So my home page now looks like this:
#override
void initState() {
// TODO: implement initState
super.initState();
print('token: ${SimplePreferences.getToken()}');
if (SimplePreferences.getToken() == null){
Navigator.of(context).pushReplacementNamed('/Auth');
}
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
var provider = Provider.of<MyManager>(context, listen: false);
provider.setAll(context); //this function calls all my API calls, but for testing, I commented out all other functions and kept only the one written above
});
}
I don't quite understand why though. If someone could explain it to me, I'd be very happy
Use Consumer to access the Provider's Variable
return Consumer<YourProviderName>(builder : (context, value, child){
return value.isLoading? const Center(
child: CircularProgressIndicator(),
):YourWidget(),
});

How to dismiss an AlertDialog in Flutter?

I have a utility class that shows an alert dialog to which I'm passing a VoidCallback which usually contains Navigator.of(context).pop() to dismiss the alert dialog. However it throws a _CastError (Null check operator used on a null value) on button press.
class CustomAlertDialog {
static void show(
BuildContext context, {
Key? key,
required VoidCallback onPress,
}) {
showDialog(
context: context,
builder: (context) => AlertDialog(
content: ElevatedButton(
onPressed: onPress,
child: const Text(
"test",
),
),
),
);
}
}
If I explicitly pass Navigator.pop(context,true) to the onPress method it works just fine, however I need this to be a reusable static method with different onPress functionalities that does more than just pop the dialog.
class CustomAlertDialog {
static void show(
BuildContext context, {
Key? key,
required VoidCallback onPress,
}) {
showDialog(
context: context,
builder: (context) => AlertDialog(
content: ElevatedButton(
onPressed: () {
return Navigator.pop(context, true);
},
child: const Text(
"test",
),
),
),
);
}
}
Your Parent Context does not contain the CustomAlertDialog.
You should pass the context provide by the builder into the onPress function and call navigator.pop with the context provided in the builder function in showDialog.
class CustomAlertDialog {
static void show(
BuildContext context, {
Key? key,
required void Function(BuildContext context) onPress,
}) {
showDialog(
context: context,
builder: (context) => AlertDialog(
content: ElevatedButton(
onPressed: (){ onPress(context) },
child: const Text(
"test",
),
),
),
);
}
}
you can try this :
void show(BuildContext context, String title, {Function callback})
onPressed: () {
if (callback != null) {
Navigator.pop(context, true);
callback();
} else {
Navigator.pop(context, true);
}
},

how to solve white screen error, when navigator.pop shows white screen for QR scan in flutter. How to do Multiple Scan in flutter?

How to do Multiple Scan in flutter
var passthroughData;
PassthroughQrScanData? passthroughQrScan;
MobileScannerController cameraController = MobileScannerController();
bool _screenOpened = false;
class PassthroughQrScanPage extends StatefulWidget {
final String? schedule_id;
final String? compoundCode;
final String? lotNo;
PassthroughQrScanPage({
this.schedule_id,
this.compoundCode,
this.lotNo,
});
#override
State<StatefulWidget> createState() => PageState();
}
class PageState extends State<PassthroughQrScanPage> {
final ApiRepository repository = ApiRepository(
apiClient: ApiClient(
httpClient: http.Client(),
),
);
#override
void initState() {}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: BlocProvider(
create: (context) => PassthroughqrscanBloc(repository),
child: BlocBuilder<PassthroughqrscanBloc, PassthroughqrscanState>(
builder: (context, state) {
if (state is PassthroughqrscanEmpty) {
return scan(context);
}
if (state is PassthroughqrscanError) {
return ShowErrorMessage(
context, state.error.message.toString());
}
if (state is PassthroughqrscanLoaded) {
String json = jsonEncode(state.entity);
print("------>>>>>>>>>>>D>S>D>>$json");
Prefs().setPassthroughData(json);
// Navigator.pop(context);
WidgetsBinding.instance.addPostFrameCallback((_) {
showDialog(
context: context,
builder: (ctxDialog) => PassDialog(
compoundCode: widget.compoundCode.toString(),
lotNo: widget.lotNo.toString(),
schedule_id: widget.schedule_id.toString(),
screenClosed: _screenWasClosed));
});
}
return Container();
Center(
child: CircularProgressIndicator(),
);
},
),
),
),
);
}
Widget ShowErrorMessage(BuildContext context, String error) {
print("------------------------------/./././$error");
WidgetsBinding.instance.addPostFrameCallback((_) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(error)));
});
return scan(context);
}
void _screenWasClosed() {
_screenOpened = false;
}
scan(BuildContext mcontext) => MobileScanner(onDetect: (barcode, args) {
String code = barcode.rawValue ?? "";
debugPrint('Barcode found! $code');
if (code.isNotEmpty) {
if (!_screenOpened) {
_screenOpened = true;
passthroughData = jsonDecode(code);
passthroughQrScan = PassthroughQrScanData.fromJson(passthroughData);
BlocProvider.of<PassthroughqrscanBloc>(mcontext, listen: false)
..add(VerifyPassthroughBatch(
passthroughQrScan?.operationName ?? "",
widget.schedule_id.toString(),
passthroughQrScan?.transactionId ?? "",
passthroughQrScan?.transactionRange ?? ""));
}
}
});
// void _foundBarcode(Barcode barcode, MobileScannerArguments? args) {
// /// open screen
//
//
// }
}
class PassDialog extends StatefulWidget {
// const PassDialog({Key? key}) : super(key: key);
String? schedule_id;
String? compoundCode;
String? lotNo;
final Function() screenClosed;
PassDialog(
{required this.schedule_id,
required this.compoundCode,
required this.lotNo,
required this.screenClosed});
#override
State<PassDialog> createState() => _PassDialogState();
}
class _PassDialogState extends State<PassDialog> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return SizedBox(
width: 150,
height: 100,
child: AlertDialog(
content: Row(
children: [
ElevatedButton(
onPressed: () {
widget.screenClosed();
Navigator.of(
context,
rootNavigator: true,
).pop(
context,
);
},
child: Text("Continue")),
SizedBox(
width: 10,
),
ElevatedButton(
onPressed: () {
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.push(
context,
new MaterialPageRoute(
builder: (_) => GluePassthroughUploadPage(
id: widget.schedule_id.toString(),
compoundCode: widget.compoundCode.toString(),
lotNo: widget.lotNo.toString(),
)));
});
},
child: Text("Show Add page")),
],
),
),
);
}
}
Future buildShowDialog(BuildContext context) {
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return Center(
child: CircularProgressIndicator(),
);
});
}
After QR scan, bloc activates and shows Alert dialog box, when API call is correct. Then, When I give continue in Alert dialog box navigator.pop is added so pop should show a mobile scanner to scan another QR but it shows White screen why? any camera controller should be activated when I give pop. how to solve this white screen error????

Refresh StatefulBuilder Dialog without using onPressed

I need to update the text of my dialog while my report is loading. setState doest not work here.
class ReportW extends StatefulWidget {
const ReportW({Key key}) : super(key: key);
#override
_ReportWState createState() => _ReportWState();
}
class _ReportWState extends State<ReportMenuDownloadW> {
String loadingText;
void updateLoadingText(text){
setState(() {loadingText = text;});
}
#override
Widget build(BuildContext context) {
return MyWidget(
label:REPORT_LABEL,
onTap: () async {
showDialog(context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (context, setState) {
return Dialog(
child: Column(
children: [
CircularProgressIndicator(),
Text(loadingText),
],
),
);});
});
await loadPDF(context,updateLoadingText);
Navigator.pop(context);
},
);
}
}
Is there an alternative solution if it is not possible ? I just need a progress text indicator over my screen while loading.
In your case you can use GlobalKey. For your code:
Define globalKey inside your widget:
// Global key for dialog
final GlobalKey _dialogKey = GlobalKey();
Set globalKey for your StatefulBuilder:
return StatefulBuilder(
key: _dialogKey,
builder: (context, setState) {
return Dialog(
child: Column(
children: [
CircularProgressIndicator(),
Text(loadingText),
],
),
);
},
);
Now you can update UI of your dialog like this:
void updateLoadingText(text) {
// Check if dialog displayed, we can't call setState when dialog not displayed
if (_dialogKey.currentState != null && _dialogKey.currentState!.mounted) {
_dialogKey.currentState!.setState(() {
loadingText = text;
});
}
}
Pay attention, you get unexpected behavior if user will close dialog manually.
How to prevent closing dialog by user: in showDialog use barrierDismissible: false and also wrap your dialog to WillPopScope with onWillPop: () async {return false;}
Possible question:
Why we check _dialogKey.currentState != null?
Because opening dialog and set globalKey take some time and while it's not opened currentState is null. If updateLoadingText will be call before dialog will be open, we shouldn't update UI for dialog.
Full code of your widget:
class OriginalHomePage extends StatefulWidget {
OriginalHomePage({Key? key}) : super(key: key);
#override
_OriginalHomePageState createState() => _OriginalHomePageState();
}
class _OriginalHomePageState extends State<OriginalHomePage> {
String loadingText = "Start";
// Global key for dialog
final GlobalKey _dialogKey = GlobalKey();
void updateLoadingText(text) {
// Check if dialog displayed, we can't call setState when dialog not displayed
if (_dialogKey.currentState != null && _dialogKey.currentState!.mounted) {
_dialogKey.currentState!.setState(() {
loadingText = text;
});
}
}
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () async {
showDialog(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
key: _dialogKey,
builder: (context, setState) {
return Dialog(
child: Column(
children: [
CircularProgressIndicator(),
Text(loadingText),
],
),
);
},
);
},
);
await loadPDF(context, updateLoadingText);
Navigator.pop(context);
},
child: Text("Open"),
);
}
}
Also i rewrote your code a bit, it seems to me more correct:
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
child: Text("Open"),
onPressed: () => _showDialog(),
),
),
);
}
// Global key for dialog
final GlobalKey _dialogKey = GlobalKey();
// Text for update in dialog
String _loadingText = "Start";
_showDialog() async {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return WillPopScope(
onWillPop: () async {
return false;
},
child: StatefulBuilder(
key: _dialogKey,
builder: (context, setState) {
return Dialog(
child: Padding(
padding: EdgeInsets.all(8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
Text(_loadingText),
],
),
),
);
},
),
);
},
);
// Call some function from service
await myLoadPDF(context, _setStateDialog);
// Close dialog
Navigator.pop(context);
}
// Update dialog
_setStateDialog(String newText) {
// Check if dialog displayed, we can't call setState when dialog not displayed
if (_dialogKey.currentState != null && _dialogKey.currentState!.mounted) {
_dialogKey.currentState!.setState(() {
_loadingText = newText;
});
}
}
}
Result:
Updated dialog

Flutter rebuild parent screen with provider on navigator push

I want to create a license validation system for my application in order to activate it if a license is entered or already present.
If when starting the application the license does not exist then I display a page which allows me to scan it by QR code.
If the scanned license is valid then I push a success page with a button on it that allows me to unlock the application. When I click on this button I want to close my success page and rebuild my application with the homepage of the application unlocked.
This works up to the success page. When I click on my button to unblock the app, I can't rebuild the parent page with the unblock app.
MyApp: This change choose the screen if I have a license or not.
class AppScreen extends StatelessWidget {
const AppScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Impact',
debugShowCheckedModeBanner: false,
theme: AppTheme().data,
home: ChangeNotifierProvider<LicenseNotifier>(
create: (BuildContext context) => LicenseNotifier(),
child: Consumer(
builder: (context, LicenseNotifier license, _) {
return license.showScreenWithLicenseState();
},
),
),
);
}
}
My license notifier:
class LicenseNotifier with ChangeNotifier {
LicenseState state = LicenseState.Uninitialized;
String getLicenseInApp;
String licenseScanned;
String personnalKey;
final String _scanBarColor = "#f57873";
LicenseNotifier(){
personnalKey = "1010";
_checkIfAppIsUnlocked();
}
Future<void> _checkIfAppIsUnlocked() async {
if (getLicenseInApp != null) {
state = LicenseState.Authenticated;
}
else{
state = LicenseState.Unauthenticated;
}
notifyListeners();
}
Future scanQRCode(BuildContext context) async{
await FlutterBarcodeScanner.scanBarcode(_scanBarColor, "Cancel", true, ScanMode.QR).then((value) {
licenseScanned = value.toString();
});
licenseValidator(context);
}
void licenseValidator(BuildContext context){
if(licenseScanned == personnalKey){
showSuccessLicenseScreen(context);
}
notifyListeners();
}
void showSuccessLicenseScreen(BuildContext context){
Navigator.push(context, MaterialPageRoute(
builder: (context) => ChangeNotifierProvider<LicenseNotifier>(
create: (BuildContext context) => LicenseNotifier(),
child: LicenseSuccessScreen(title: "Valid license")
),
));
}
activateApp(BuildContext context) {
Navigator.pop(context);
state = LicenseState.Authenticated;
notifyListeners();
}
showScreenWithLicenseState(){
switch (state) {
case LicenseState.Uninitialized:
return SplashScreen();
break;
case LicenseState.Unauthenticated:
case LicenseState.Authenticating:
return LicenseActivationScreen(title: "Activate license");
break;
case LicenseState.Authenticated:
return ChangeNotifierProvider<BottomNavigationBarNotifier>(
create: (BuildContext context) => BottomNavigationBarNotifier(),
child: NavigationBarScreen(),
);
break;
}
return null;
}
}
My success screen: When I scan a valid license
class LicenseSuccessScreen extends StatelessWidget{
final String title;
const LicenseSuccessScreen({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context)
{
return GestureDetector(
onTap: (() => FocusScope.of(context).requestFocus(FocusNode())),
child: Scaffold(
appBar: AppBar(
backgroundColor: AppColors.colorContrastGreen,
elevation: 0,
centerTitle: true,
title: Text(title),
),
body : _buildBody(context),
),
);
}
Widget _buildBody(BuildContext context)
{
var _licenseProvider = Provider.of<LicenseNotifier>(context);
return Container(
color: AppColors.colorContrastGreen,
padding: EdgeInsets.symmetric(horizontal: AppUi.RATIO * 5),
height: double.infinity,
child: Column(
children: [
ContainerComponent(
background: AppColors.colorBgLight,
alignement: CrossAxisAlignment.center,
children: [
ButtonComponent.primary(
context: context,
text: "Débloquer",
onPressed: () async{
_licenseProvider.activateApp(context);
},
),
],
),
],
),
);
}
}
So when I click on my button who call "activateApp" in notifier, the success screen closes but I haven't my application content. It just show the LicenseActivationScreen. How to resolve this problem ?