Refresh StatefulBuilder Dialog without using onPressed - flutter

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

Related

Check content function (Flutter)

I use the following function to check and display the content either in Dialog or Bottom Sheet, but when executing it does not work properly, as it displays both together, what is the reason and how can the problem be solved?
Is it possible to suggest a better name for the function?
Content function:
content(BuildContext context, dynamic dialog, dynamic bottomSheet) {
(MediaQuery.of(context).orientation == Orientation.landscape) ? dialog : bottomSheet;
}
Implementation:
ElevatedButton(
child: Text('Button'),
onPressed: () {
content(context, dialog(context), bottomSheet(context));
},
),
How can this be solved?
In order to determine the Orientation of the screen, we can use the OrientationBuilder Widget. The OrientationBuilder will determine the current Orientation and rebuild when the Orientation changes.
void main() async {
runApp(const Home(
));
}
class Home extends StatefulWidget {
const Home({Key key}) : super(key: key);
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return MaterialApp(home: Scaffold(
body: Center(
child: OrientationBuilder(
builder: (context, orientation) {
return ElevatedButton(
child: Text('Button'),
onPressed: () {
revealContent(orientation,context);
},
);
},
)
),
));
}
revealContent(Orientation orientation, BuildContext context) {
orientation == Orientation.landscape ? dialog(context) : bottomSheet(context);
}
dialog(BuildContext context){
showDialog(
context: context,
builder: (context) => const Dialog(
child: Padding(
padding: EdgeInsets.all(20.0),
child: Text('test'),
),
)
);
}
bottomSheet(final BuildContext context) {
return showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (builder) => const Padding(
padding: EdgeInsets.all(20.0),
child: Text('test'),
),
);
}
}
here are screenshots:
happy coding...
The reason the function is not working properly is because you're not actually showing the dialog or bottom sheet. To show the dialog or bottom sheet, you need to call showDialog or showModalBottomSheet, respectively, and pass them the result of calling dialog or bottomSheet.
try this
void revealContent(BuildContext context, Widget dialog, Widget bottomSheet) {
(MediaQuery.of(context).orientation == Orientation.landscape)
? showDialog(context: context, builder: (context) => dialog)
: showModalBottomSheet(context: context, builder: (context) => bottomSheet);
}
You have a fundamental misunderstanding as to what your code is doing.
Take your "Implementation" and revealContent code, for example:
ElevatedButton(
child: Text('Button'),
onPressed: () {
revealContent(context, dialog(context), bottomSheet(context));
},
),
revealContent(BuildContext context, dynamic dialog, dynamic bottomSheet) {
(MediaQuery.of(context).orientation == Orientation.landscape) ? dialog : bottomSheet;
}
You think that revealContent will invoke either dialog or bottomSheet based on the orientation of the screen. What you are actually doing, however, is you are invoking both of them and then passing the result of the invocations to revealContent, which isn't actually doing anything with them.
What you need to be doing is passing the functions as callbacks to revealContent and then invoking the callbacks within the function:
ElevatedButton(
child: Text('Button'),
onPressed: () {
revealContent(context, () => dialog(context), () => bottomSheet(context));
},
),
revealContent(BuildContext context, void Function() dialog, void Function() bottomSheet) {
if (MediaQuery.of(context).orientation == Orientation.landscape) {
dialog()
} else {
bottomSheet();
}
}
You should be calling showDialog and showModalBottomSheet inside revealContent.
Dialog
dialog(BuildContext context){
return Dialog( //.. );
}
BottomSheet
bottomSheet(final BuildContext context) {
return Widget( /.. );
}
Reveal Content
void revealContent(BuildContext context, Widget dialog, Widget bottomSheet) {
if (MediaQuery.of(context).orientation == Orientation.landscape) {
return showDialog(context: context, builder: (context) => dialog);
} else {
return showModalBottomSheet(context: context, builder: (context) => bottomSheet);
}
}

disable button with press but without rebuilding the whole screen

I have a quiz screen where I am using an API with FutureBuilder. Each time build method is refreshed, the new question is fetched. There's a submit button at the bottom to save the response and reset the screen. What I want to do is to disable the submit button until new question is fetched after pressing the submit button and make enabled when new question is rebuild. I cannot call the setstate to make it null with a bool variable because new question is loaded due to this. Here's my code to reproduce the issue:
import 'package:flutter/material.dart';
class QuizForm extends StatefulWidget {
const QuizForm({Key? key}) : super(key: key);
#override
State<QuizForm> createState() => _QuizFormState();
}
class _QuizFormState extends State<QuizForm> {
int buildCount = 0 ;
getQuestion () {}
#override
Widget build(BuildContext context) {
print(buildCount);
print('Question Fetched and UI is building');
return SafeArea(child: Scaffold(
body: FutureBuilder(
future: getQuestion(),
builder: (context, snapshot){
return ListView(
children: [
ListTile(title: Text('Quiz Title'),),
ListTile(title: Text('1'),),
ListTile(title: Text('2'),),
ListTile(title: Text('3'),),
ListTile(title: Text('4'),),
SizedBox(height: 20,),
ElevatedButton(
onPressed: () async {
print('Please Wait, Answer is getting Saved');
// Button Should be shown disabled for 3 seconds
await Future.delayed(const Duration(seconds: 3));
buildCount++;
setState(() {
// this setState rebuilds the screen and new question is loaded
// because of future builder
});
}, child: Text('Submit Quiz'))
],
);
},
),
));
}
}
When you are getting data from API check if you have data in your variable , if has data return data if not then call API ,
update : with _submitEnabled value .
Here example :
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class QuizForm extends StatefulWidget {
const QuizForm({Key? key}) : super(key: key);
#override
State<QuizForm> createState() => _QuizFormState();
}
class _QuizFormState extends State<QuizForm> {
Question _cachedQuestion;
bool _submitEnabled = false;
Future<Question> getQuestion() async {
if (_cachedQuestion != null) {
return _cachedQuestion;
}
final response = await http.get('https://your-api-endpoint.com/question');
if (response.statusCode == 200) {
final question = Question.fromJson(json.decode(response.body));
_cachedQuestion = question;
_submitEnabled = true;
return question;
} else {
throw Exception('Failed to fetch question');
}
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: FutureBuilder(
future: getQuestion(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final question = snapshot.data;
return ListView(
children: [
ListTile(title: Text(question.title)),
ListTile(title: Text(
I managed to get it through ValueListenableBuilder. Here is my code that is working as expected:
import 'package:flutter/material.dart';
class QuizForm extends StatefulWidget {
const QuizForm({Key? key}) : super(key: key);
#override
State<QuizForm> createState() => _QuizFormState();
}
class _QuizFormState extends State<QuizForm> {
final _buttonEnabled = ValueNotifier(true);
int buildCount = 0;
getQuestion () {}
#override
Widget build(BuildContext context) {
print(buildCount);
return SafeArea(
child: Scaffold(
body: FutureBuilder(
future: getQuestion(),
builder: (context, snapshot) {
return ListView(
children: [
ListTile(title: Text('Quiz Title')),
ListTile(title: Text('1')),
ListTile(title: Text('2')),
ListTile(title: Text('3')),
ListTile(title: Text('4')),
SizedBox(height: 20),
ValueListenableBuilder(
valueListenable: _buttonEnabled,
builder: (context, value, child) {
return ElevatedButton(
onPressed: _buttonEnabled.value
? () async {
_buttonEnabled.value = false;
print('Please Wait, Answer is getting Saved');
await Future.delayed(const Duration(seconds: 3));
_buttonEnabled.value = true;
buildCount++;
setState(() {
});
}
: null,
child: child,
);
},
child: Text('Submit Quiz'),
),
],
);
},
),
),
);
}
}

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????

I can't change event status when page opening in flutter

I am new to FLutter. I am using Bloc pattern as a design pattern.
When clicked a button or text changed I successfully changed the event of the bloc.
But I need to get data when the page opens and bind it to a list.
I don't know how can I change the bloc event to do that?
I've tried to add BlocBuilder in InitState but it didn't work.
here is my code.
class OrderListWidget extends StatefulWidget {
const OrderListWidget({Key? key}) : super(key: key);
#override
_OrderListWidgetState createState() => _OrderListWidgetState();
}
class _OrderListWidgetState extends State<OrderListWidget> {
late List<WorkOrder> workOrderList;
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => WorkOrderBloc(
workOrderRepo: (context).read<WorkOrderRepository>(),
type: WorkOrderType.mt),
child: BlocListener<WorkOrderBloc, WorkOrderState>(
listener: (context, state) {
final formStatus = state.formStatus;
if (formStatus is FormSubmitting) {
LoadingDialog.openLoadingDialog(context, 'Please Wait');
} else {
if (formStatus is! InitialFormStatus) {
LoadingDialog.closeLoadingDialog(context);
}
if (formStatus is SubmissionFailed) {
SnackbarWidget.show(
context, formStatus.exception.toString(), Colors.red);
}
if (formStatus is SubmissionSuccess) {
setState(() {
workOrderList = state.workOrderList!;
});
}
}
},
child: BlocBuilder<WorkOrderBloc, WorkOrderState>(
builder: (context, state) {
return _myListView(context);
},
),
),
);
}
#override
initState() {
context
.read<WorkOrderBloc>()
.add(WorkOrderListing(orderType: WorkOrderType.mt));
super.initState();
}
}
Widget _myListView(BuildContext context) {
var selected = false;
return ListView.builder(
itemCount: workOrderList.length,
itemBuilder: (context, index) {
return Card(
child: CheckboxListTile(
value: selected,
onChanged: (value) {
setState() {
selected = value!;
}
},
title: Text(workOrderList[index].Name),
),
);
},
);
}
I found the way:
return BlocProvider(
create: (context) => WorkOrderBloc(workOrderRepo: WorkOrderRepository())
..add(PickingOrderListing()),
child: BlocListener<WorkOrderBloc, WorkOrderState>(
listener: (context, state) {
....
}
)
using
..add(YOUR EVENT)
after the BlocProvider worked.

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 ?