I'm not sure if the initState is the right function for this.
What I'm trying to achieve is to check when the page is rendered to perform some checks and based on them opening a AlertDialog to make some settings if needed.
I've got a Page which has a state.
It's initState function looks like this:
#override
void initState() {
super.initState();
if (!_checkConfiguration()) {
_showConfiguration(context);
}
}
The _showConfiguration like this:
void _showConfiguration(BuildContext context) {
AlertDialog dialog = new AlertDialog(
content: new Column(
children: <Widget>[
new Text('#todo')
],
),
actions: <Widget>[
new FlatButton(onPressed: (){
Navigator.pop(context);
}, child: new Text('OK')),
],
);
showDialog(context: context, child: dialog);
}
If there's a better way to make this checks and if needed call the modal, please point me in the proper direction, I was looking for a onState or onRender function, or a callback I could assign to the build function to be called on render but wasn't able to find one.
Edit: It seams over here they had a similar problem: Flutter Redirect to a page on initState
The member variable context can be accessed during initState but can't be used for everything. This is from the flutter for initState documentation:
You cannot use [BuildContext.inheritFromWidgetOfExactType] from this
method. However, [didChangeDependencies] will be called immediately
following this method, and [BuildContext.inheritFromWidgetOfExactType]
can be used there.
You could move your initialization logic to didChangeDependencies, however that might not be exactly what you want as didChangeDependencies can be called multiple times in the lifecycle of the widget.
If you instead make an asynchronous call which delegates your call until after the widget has been initialized, you can then use context as you intend.
A simple way to do that is to use a future.
Future.delayed(Duration.zero,() {
... showDialog(context, ....)
});
Another way, which may be more 'correct', is to use flutter's scheduler to add a post-frame callback:
SchedulerBinding.instance.addPostFrameCallback((_) {
... showDialog(context, ....)
});
And finally, here's a little trick I like to do to use asynchronous calls in the initState function:
() async {
await Future.delayed(Duration.zero);
... showDialog(context, ...)
}();
Here's a fully fleshed out example using the simple Future.delayed:
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
bool _checkConfiguration() => true;
void initState() {
super.initState();
if (_checkConfiguration()) {
Future.delayed(Duration.zero,() {
showDialog(context: context, builder: (context) => AlertDialog(
content: Column(
children: <Widget>[
Text('#todo')
],
),
actions: <Widget>[
FlatButton(onPressed: (){
Navigator.pop(context);
}, child: Text('OK')),
],
));
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
);
}
}
With more context from the OP provided in comments, I can give a slightly better solution to their specific problem. Depending on the app, you may actually want to make a decision based on which page to show depending on whether it's the first time the app is opened i.e. set home to something different. And dialogs aren't necessarily the best UI element on mobile; it may be better to show a full page with the settings they need to add and a next button.
Wrapping with Future
#override
void initState() {
super.initState();
_store = Store();
new Future.delayed(Duration.zero,() {
_store.fetchContent(context);
});
}
====== UPDATED ======
Just like pointed by Lucas Rueda ( thanks to him :), When we need to get context inside initState() in order to work with "Provider", we should set the parameter listen to be = false. It makes sense because we should not listen to the initState() phase. So, for example, it should be:
final settingData = Provider.of<SettingProvider>(context, listen: false);
=========== OLD ANSWER =======
Most examples of initState() in this thread may be works for "UI" things such as "Dialog" which is the case in the root question of this thread.
But unfortunately, it doesn't work for me when applying it to get context for "Provider".
Hence, I pick didChangeDependencies() approach. As mentioned in the accepted answer, it has a caveat which is, it can be called multiple times in the lifecycle of the widget. However, it quite easy to handle it. Just use a single helper variable which is bool to prevent multiple calls inside didChangeDependencies(). Here is the example usage of _BookListState class with variable _isInitialized as the main "stopper" of "multiple calls":
class _BookListState extends State<BookList> {
List<BookListModel> _bookList;
String _apiHost;
bool _isInitialized; //This is the key
bool _isFetching;
#override
void didChangeDependencies() {
final settingData = Provider.of<SettingProvider>(context);
this._apiHost = settingData.setting.apiHost;
final bookListData = Provider.of<BookListProvider>(context);
this._bookList = bookListData.list;
this._isFetching = bookListData.isFetching;
if (this._isInitialized == null || !this._isInitialized) {// Only execute once
bookListData.fetchList(context);
this._isInitialized = true; // Set this to true to prevent next execution using "if()" at this root block
}
super.didChangeDependencies();
}
...
}
Here is error logs when I am trying to do initState() approach:
E/flutter ( 3556): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: 'package:provider/src/provider.dart': Failed assertion: line 242 pos 7: 'context.owner.debugBuilding ||
E/flutter ( 3556): listen == false ||
E/flutter ( 3556): debugIsInInheritedProviderUpdate': Tried to listen to a value exposed with provider, from outside of the widget tree.
E/flutter ( 3556):
E/flutter ( 3556): This is likely caused by an event handler (like a button's onPressed) that called
E/flutter ( 3556): Provider.of without passing `listen: false`.
E/flutter ( 3556):
E/flutter ( 3556): To fix, write:
E/flutter ( 3556): Provider.of<SettingProvider>(context, listen: false);
E/flutter ( 3556):
E/flutter ( 3556): It is unsupported because may pointlessly rebuild the widget associated to the
E/flutter ( 3556): event handler, when the widget tree doesn't care about the value.
E/flutter ( 3556):
E/flutter ( 3556): The context used was: BookList(dependencies: [_InheritedProviderScope<BookListProvider>], state: _BookListState#1008f)
E/flutter ( 3556):
E/flutter ( 3556): #0 _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:46:39)
E/flutter ( 3556): #1 _AssertionError._throwNew (dart:core-patch/errors_patch.dart:36:5)
E/flutter ( 3556): #2 Provider.of
package:provider/src/provider.dart:242
E/flutter ( 3556): #3 _BookListState.initState.<anonymous closure>
package:perpus/…/home/book-list.dart:24
E/flutter ( 3556): #4 new Future.delayed.<anonymous closure> (dart:async/future.dart:326:39)
E/flutter ( 3556): #5 _rootRun (dart:async/zone.dart:1182:47)
E/flutter ( 3556): #6 _CustomZone.run (dart:async/zone.dart:1093:19)
E/flutter ( 3556): #7 _CustomZone.runGuarded (dart:async/zone.dart:997:7)
E/flutter ( 3556): #8 _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1037:23)
E/flutter ( 3556): #9 _rootRun (dart:async/zone.dart:1190:13)
E/flutter ( 3556): #10 _CustomZone.run (dart:async/zone.dart:1093:19)
E/flutter ( 3556): #11 _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:1021:23)
E/flutter ( 3556): #12 Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:18:15)
E/flutter ( 3556): #13 _Timer._runTimers (dart:isolate-patch/timer_impl.dart:397:19)
E/flutter ( 3556): #14 _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:428:5)
E/flutter ( 3556): #15 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)
E/flutter ( 3556):
Simple use Timer.run()
#override
void initState() {
super.initState();
Timer.run(() {
// you have a valid context here
});
}
We can use Global key as:
class _ContactUsScreenState extends State<ContactUsScreen> {
//Declare Global Key
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
//key
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text('Contact Us'),
),
body:
}
//use
Future<void> send() async {
final Email email = Email(
body: _bodyController.text,
subject: _subjectController.text,
recipients: [_recipientController.text],
attachmentPaths: attachments,
isHTML: isHTML,
);
String platformResponse;
try {
await FlutterEmailSender.send(email);
platformResponse = 'success';
} catch (error) {
platformResponse = error.toString();
}
if (!mounted) return;
_scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text(platformResponse),
));
}
}
This work using a key in your method build widget.
First create the key:
final GlobalKey<NavigatorState> key =
new GlobalKey<NavigatorState>();
After we bind with our widget:
#override
Widget build(BuildContext context) {
return Scaffold(key:key);
}
Finally we use the key calling .currentContext parameter.
#override
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((_) {
// your method where use the context
// Example navigate:
Navigator.push(key.currentContext,"SiestaPage");
});
}
Happy coding.
Related
I want to go to Second Page with Navigator.push method. So I am using ElevatedButton Widget for this purpose. I'm also using Statefull Widget. My source code like in below:
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
import 'ScannerPage.dart';
void main() {
runApp(const QrCodeMainWindow());
}
class QrCodeMainWindow extends StatefulWidget {
const QrCodeMainWindow({Key? key}) : super(key: key);
#override
State<QrCodeMainWindow> createState() => _QrCodeMainWindowState();
}
class _QrCodeMainWindowState extends State<QrCodeMainWindow> {
final String _data = "";
QRViewController? controller;
Barcode? result;
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
#override
Widget build(BuildContext context) {
final ButtonStyle style = ElevatedButton.styleFrom(
textStyle: const TextStyle(fontSize: 20),
);
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('QR Code Scanner App'),
backgroundColor: Colors.blueAccent,
),
body: Column(
children: [
ElevatedButton(
child: Text('Scan'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ScannerPage()),
);
}),
Text(_data)
],
),
),
);
}
When I run this App succesfuly openning on simulator (Iphone 13). But when I press the button I get below error.
How Can I solve this error ?
======== Exception caught by gesture ===============================================================
The following assertion was thrown while handling a gesture:
Navigator operation requested with a context that does not include a Navigator.
The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.
When the exception was thrown, this was the stack:
#0 Navigator.of.<anonymous closure> (package:flutter/src/widgets/navigator.dart:2553:9)
#1 Navigator.of (package:flutter/src/widgets/navigator.dart:2560:6)
#2 Navigator.push (package:flutter/src/widgets/navigator.dart:2016:22)
#3 _QrCodeMainWindowState.build.<anonymous closure> (package:qr_code_scanner_example/main.dart:51:29)
#4 _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:989:21)
#5 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:198:24)
#6 TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:608:11)
#7 BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:296:5)
#8 BaseTapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:230:7)
#9 PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:563:9)
#10 PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:94:12)
#11 PointerRouter._dispatchEventToRoutes.<anonymous closure> (package:flutter/src/gestures/pointer_router.dart:139:9)
#12 _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:539:8)
#13 PointerRouter._dispatchEventToRoutes (package:flutter/src/gestures/pointer_router.dart:137:18)
#14 PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:123:7)
#15 GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:439:19)
#16 GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:419:22)
#17 RendererBinding.dispatchEvent (package:flutter/src/rendering/binding.dart:322:11)
#18 GestureBinding._handlePointerEventImmediately (package:flutter/src/gestures/binding.dart:374:7)
#19 GestureBinding.handlePointerEvent (package:flutter/src/gestures/binding.dart:338:5)
#20 GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:296:7)
#21 GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:279:7)
#25 _invoke1 (dart:ui/hooks.dart:170:10)
#26 PlatformDispatcher._dispatchPointerDataPacket (dart:ui/platform_dispatcher.dart:331:7)
#27 _dispatchPointerDataPacket (dart:ui/hooks.dart:94:31)
(elided 3 frames from dart:async)
Handler: "onTap"
Recognizer: TapGestureRecognizer#ba4ae
debugOwner: GestureDetector
state: possible
won arena
finalPosition: Offset(46.3, 135.7)
finalLocalPosition: Offset(46.3, 26.7)
button: 1
sent tap down
I'm trying to follow the Computer Vision with ML Kit - Flutter In Focus tutorial, where I followed the tutorial step-by step, and still didn't manage to make it work.
my code is as follow:
import 'package:flutter/material.dart';
import 'dart:io';
import 'dart:async';
import 'package:image_picker/image_picker.dart';
import 'package:firebase_ml_vision/firebase_ml_vision.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: FacePage(),
);
}
}
class FacePage extends StatefulWidget{
#override
createState() => _FacePageState();
}
class _FacePageState extends State<FacePage>{
File _imageFile;
List<Face> _faces;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Face Detector'),
),
body: ImageAndFaces(),
floatingActionButton: FloatingActionButton(
onPressed: _getImageAndDetectFace,
tooltip: 'Pick an Image',
child: Icon(Icons.add_a_photo),
),
);
}
void _getImageAndDetectFace() async {
final imageFile = await ImagePicker.pickImage(
source: ImageSource.gallery,
);
final image = FirebaseVisionImage.fromFile(imageFile);
final faceDetector = FirebaseVision.instance.faceDetector(
FaceDetectorOptions(
mode: FaceDetectorMode.accurate,
enableLandmarks: true,
),
);
List<Face> faces = await faceDetector.detectInImage(image);
if(mounted) {
setState(() {
_imageFile = imageFile;
_faces = faces;
});
}
}
}
class ImageAndFaces extends StatelessWidget {
ImageAndFaces({this.imageFile, this.faces});
final File imageFile;
final List<Face> faces;
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Flexible(
flex: 2 ,
child: Container(
constraints: BoxConstraints.expand(),
child: Image.file(imageFile, fit: BoxFit.cover),
),
),
Flexible(flex: 1 ,
child: ListView(
children: faces.map<Widget>((f) => FaceCoordinates(f)).toList(),
),
),
],
);
}
}
class FaceCoordinates extends StatelessWidget {
FaceCoordinates(this.face);
final Face face;
#override
Widget build(BuildContext context) {
final pos = face.boundingBox;
return ListTile(
title: Text('(${pos.top}, ${pos.left}, ${pos.bottom}, ${pos.right})'),
);
}
}
I'm getting the following exception stack:
I/flutter ( 5077): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 5077): The following assertion was thrown building ImageAndFaces(dirty):
I/flutter ( 5077): 'package:flutter/src/painting/image_provider.dart': Failed assertion: line 532 pos 14: 'file !=
I/flutter ( 5077): null': is not true.
I/flutter ( 5077):
I/flutter ( 5077): Either the assertion indicates an error in the framework itself, or we should provide substantially
I/flutter ( 5077): more information in this error message to help you determine and fix the underlying cause.
I/flutter ( 5077): In either case, please report this assertion by filing a bug on GitHub:
I/flutter ( 5077): https://github.com/flutter/flutter/issues/new?template=BUG.md
I/flutter ( 5077):
I/flutter ( 5077): When the exception was thrown, this was the stack:
I/flutter ( 5077): #2 new FileImage (package:flutter/src/painting/image_provider.dart:532:14)
I/flutter ( 5077): #3 new Image.file (package:flutter/src/widgets/image.dart:254:16)
I/flutter ( 5077): #4 ImageAndFaces.build (package:visionappwork/main.dart:94:28)
I/flutter ( 5077): #5 StatelessElement.build (package:flutter/src/widgets/framework.dart:3789:28)
I/flutter ( 5077): #6 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3736:15)
.........
Does anybody know what the problem is?
I tried anything I can think of, including trying to catch the 'ImageAndFaces' class constructor as it creates an instance, without any success.
I'm new to flutter and dart, so maybe it's a stupid mistake.
Thanks a lot!
The reason you're having a problem is that imageFile starts out null. Since it's being passed in to Image.file(imageFile, fit: BoxFit.cover) you're seeing the failure due to the assertion that the file passed to Image.file is not null.
You need to add some logic to check whether imageFile is null and do something different if it is.
I'm writing a simple reminder app that is essentially a ListView of TextFields that, when blurred or submitted, update the database. I use a bunch of GestureDetectors and FocusNodes to blur the TextField when a user taps on the checkbox or outside the TextField.
It works very well when this is the only route. However, when I push the same exact page on top of the existing one, the focus behavior becomes completely buggy and the app unusable.
Here's a video to demonstrate: https://www.youtube.com/watch?v=13E9LY8yD3A
My code is essentially this:
/// main.dart
class MyApp extends StatelessWidget {
static FocusScopeNode rootScope; // just for debug
#override
Widget build(BuildContext context) {
rootScope = FocusScope.of(context);
return MaterialApp(home: ReminderPage());
}
}
-
/// reminder_page.dart
class ReminderPage extends StatelessWidget {
final _blurNode = FocusNode();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Remind'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: () {
// Push new identical page.
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ReminderPage(),
));
},
),
],
),
body: StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('reminders').snapshots(),
builder: (context, snapshot) {
return _buildBody(context, snapshot.data);
},
),
);
}
Widget _buildBody(BuildContext context, QuerySnapshot data) {
List<Reminder> reminders =
data.documents.map((s) => Reminder.fromSnapshot(s)).toList();
return GestureDetector(
onTap: () {
_blur(context);
},
child: ListView(
children: reminders.map((r) => ReminderCard(r)).toList(),
),
);
}
void _blur(context) {
FocusScope.of(context).requestFocus(_blurNode);
}
}
-
/// reminder_card.dart
class ReminderCard extends StatelessWidget {
final Reminder reminder;
final TextEditingController _controller;
final _focusNode = FocusNode();
final _blurNode = FocusNode();
ReminderCard(this.reminder)
: _controller = TextEditingController(text: reminder.text) {
_focusNode.addListener(() {
if (!_focusNode.hasFocus) {
reminder.updateText(_controller.text); // update database
}
});
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
_blur(context);
},
child: Row(
children: <Widget>[
_buildCheckBox(context),
_buildTextField(context),
],
),
);
}
Widget _buildCheckBox(context) {
return Checkbox(
value: reminder.done,
onChanged: (done) {
print(MyApp.rootScope.toStringDeep()); // print Focus tree
_blur(context);
reminder.updateDone(done); // update database
},
);
}
Widget _buildTextField(context) {
return TextField(
onSubmitted: reminder.updateText, // update database
focusNode: _focusNode,
);
}
void _blur(context) {
FocusScope.of(context).requestFocus(_blurNode);
}
}
I found this question which sounds very similar, but I didn't understand how the custom transition solves anything and has anything to do with focus. And like the OP, I tried a lot of different things to mess with FocusScope, include call detach(), reparentIfNeeded(), or passing the FocusScope of the root all the way down so a new FocusScope is not created each time, but none of those gave anything close to working. And I also tried the custom transition, to no avail.
The debug output shows this on the first route (when I check boxes):
I/flutter (28362): FocusScopeNode#68466
I/flutter (28362): └─child 1: FocusScopeNode#5b855
I/flutter (28362): └─child 1: FocusScopeNode#76ef6
I/flutter (28362): FocusScopeNode#68466
I/flutter (28362): └─child 1: FocusScopeNode#5b855
I/flutter (28362): └─child 1: FocusScopeNode#76ef6
I/flutter (28362): focus: FocusNode#f07c7(FOCUSED)
I/flutter (28362): FocusScopeNode#68466
I/flutter (28362): └─child 1: FocusScopeNode#5b855
I/flutter (28362): └─child 1: FocusScopeNode#76ef6
I/flutter (28362): focus: FocusNode#f138f(FOCUSED)
I/flutter (28362): FocusScopeNode#68466
I/flutter (28362): └─child 1: FocusScopeNode#5b855
I/flutter (28362): └─child 1: FocusScopeNode#76ef6
I/flutter (28362): focus: FocusNode#e68b3(FOCUSED)
And this on the second route:
I/flutter (28362): FocusScopeNode#68466
I/flutter (28362): └─child 1: FocusScopeNode#5b855
I/flutter (28362): ├─child 1: FocusScopeNode#a1008
I/flutter (28362): └─child 2: FocusScopeNode#76ef6
I/flutter (28362): focus: FocusNode#a76e6
I/flutter (28362): FocusScopeNode#68466
I/flutter (28362): └─child 1: FocusScopeNode#5b855
I/flutter (28362): ├─child 1: FocusScopeNode#a1008
I/flutter (28362): │ focus: FocusNode#02ebf(FOCUSED)
I/flutter (28362): │
I/flutter (28362): └─child 2: FocusScopeNode#76ef6
I/flutter (28362): focus: FocusNode#a76e6
I/flutter (28362): FocusScopeNode#68466
I/flutter (28362): └─child 1: FocusScopeNode#5b855
I/flutter (28362): ├─child 1: FocusScopeNode#a1008
I/flutter (28362): │ focus: FocusNode#917da(FOCUSED)
I/flutter (28362): │
I/flutter (28362): └─child 2: FocusScopeNode#76ef6
I/flutter (28362): focus: FocusNode#a76e6
So it looks like the FocusScope of the first route becomes child 2 when we push the second route, which sounds correct to me.
What am I doing wrong?
Thanks to Lucas' comments above and this other SO question I was able to fix the problem.
First, I reduced the number of FocusNodes: just one per TextField, and one for the parent ReminderPage. The parent now has a function blur() that unfocuses all TextFields; that way, when I click the checkbox of a TextField while editing another, the one being edited is unfocused.
Second, I changed my reminder.updateText() function (not shown here) so it only updates the database when the text is different from the existing text. Otherwise, we would be rebuilding the card because of the StreamBuilder, messing up the focus of the TextField being edited.
Third, I'm now listening to the TextEditingController instead of the FocusNode to make changes to the database. But I still only update the database when the FocusNode is unfocused, otherwise the StreamBuilder would rebuild the page and mess up with focus again.
But that still doesn't explain why it works reasonably well when the ReminderPage is the homepage of the app, and not when it's pushed on top of a route. The answer comes from this other SO question which was hitting the same issue: the widget was constantly rebuilt when placed after a splash screen, but not when used as the app homepage. I still don't understand why this makes any difference, but the same fix worked for me: change it to StatefulWidget and only rebuild when something actually changed.
The final code looks like this. I highlighted the diffs with // ---> comments.
/// main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: ReminderPage());
}
}
-
/// reminder_page.dart
class ReminderPage extends StatelessWidget {
final _blurNode = FocusNode();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Remind'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: () {
// Push new identical page.
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ReminderPage(),
));
},
),
],
),
body: StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('reminders').snapshots(),
builder: (context, snapshot) {
return _buildBody(context, snapshot.data);
},
),
);
}
Widget _buildBody(BuildContext context, QuerySnapshot data) {
List<Reminder> reminders =
data.documents.map((s) => Reminder.fromSnapshot(s)).toList();
return GestureDetector(
onTap: () {
// ---> Blur all TextFields when clicking in the background.
blur(context);
},
child: ListView(
// ---> Passing the parent to each child so they can call parent.blur()
children: reminders.map((r) => ReminderCard(r, this)).toList(),
),
);
}
// ---> This will unfocus all TextFields.
void blur(context) {
FocusScope.of(context).requestFocus(_blurNode);
}
}
-
/// reminder_card.dart
// ---> Converted to a StatefulWidget! That way we can save a snapshot of reminder
// as it was when we last built the widget, and only rebuild it if it changed.
class ReminderCard extends StatefulWidget {
final Reminder reminder;
final TextEditingController _controller;
// ---> Only one focus node, for the TextField.
final _focusNode = FocusNode();
// ---> The parent.
final ReminderPage page;
ReminderCard(this.reminder, this.page)
: _controller = TextEditingController(text: reminder.text) {
// ---> Listen to text changes. But only updating the database
// if the TextField is unfocused.
_controller.addListener(() {
if (!_focusNode.hasFocus) {
reminder.updateText(_controller.text); // update database
}
});
}
#override
ReminderCardState createState() => ReminderCardState();
}
class ReminderCardState extends State<ReminderCard> {
Widget card;
Reminder snapshotWhenLastBuilt;
#override
Widget build(BuildContext context) {
// ---> Only rebuild if something changed, otherwise return the
// card built previously.
// The equals() function is a method of the Reminder class that just tests a
// few fields.
if (card == null || !widget.reminder.equals(snapshotWhenLastBuilt)) {
card = _buildCard(context);
snapshotWhenLastBuilt = widget.reminder;
}
return card;
}
Widget _buildCard(context) {
return GestureDetector(
onTap: () {
// ---> Blur all TextFields when clicking in the background.
widget.page.blur(context);
},
child: Row(
children: <Widget>[
_buildCheckBox(context),
_buildTextField(context),
],
),
);
}
Widget _buildCheckBox(context) {
return Checkbox(
value: widget.reminder.done,
onChanged: (done) {
// ---> Blur all TextFields when clicking on a checkbox.
widget.page.blur(context);
widget.reminder.updateDone(done); // update database
},
);
}
Widget _buildTextField(context) {
return TextField(
focusNode: widget._focusNode,
controller: widget._controller,
);
}
}
I have an app which exports a json object to a json file and while it's exporting, I wanted to show an alert dialog with a circular progress indicator on it. But for some reason, the alert dialog with my progress indicator is not showing up.
This is the look of my app before I export my json:
Here is the code for activating the exporting part:
...
child: FlatButton(
onPressed: () async{
//Popping the confirm dialog
Navigator.pop(context);
//Showing the progress dialog
showProcessingDialog();
//Buying some time
_timer = Timer(Duration(seconds: 5), exportData);
//Pops the progress dialog
Navigator.pop(context);
//Shows the finished dialog
showFinishedDialog();
},
child: Text(
"Yes",
...
After I click 'Yes' in this alert button, it should show the progress dialog but it doesn't show, instead it shows the finished dialog.
Like this:
Here is the code for progress dialog:
void showProcessingDialog() async{
return showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context){
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0))),
contentPadding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 0.0),
content: Container(
width: 250.0,
height: 100.0,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
CircularProgressIndicator(),
Text("Exporting...",
style: TextStyle(
fontFamily: "OpenSans",
color: Color(0xFF5B6978)
)
)
]
)
)
);
}
);
}
Here is the exportData callback:
void exportData() async{
List<dynamic> _msgList = await _msgStorage._getList;
await _expData._saveList(_msgList);
}
I have tried to add Timer class to delay showing finished dialog for 3 seconds but it doesn't work. I can confirm that my json file was exported successfully but the callback of Timer which is the progress dialog didn't show up.
I would appreciate any kind of help.
UPDATE:
I rewrote my code based on the answer of diegoveloper:
onPressed: () async{
Navigator.pop(context);
print("confirm dialog has pop");
print("showing processdialog");
showProcessingDialog();
print("processdialog is being shown.");
print("buying some time");
await Future.delayed(Duration(seconds: 5));
print("done buying some time");
print("exporting begin");
await exportData();
print("exporting done");
Navigator.pop(context);
print("processdialog has pop");
print("showing finished dialog");
showFinishedDialog();
print("finished dialog is being shown.");
},
At this point, the process dialog is being shown but after printing the "exporting done" and executing the Navigator.pop(context); it gave an error and the process dialog remains in the screen, unpopped.
Like this:
I/flutter ( 9767): confirm dialog has pop
I/flutter ( 9767): showing processdialog
I/flutter ( 9767): processdialog is being shown.
I/flutter ( 9767): buying some time
I/flutter ( 9767): done buying some time
I/flutter ( 9767): exporting begin
I/flutter ( 9767): exporting done
E/flutter ( 9767): [ERROR:flutter/shell/common/shell.cc(184)] Dart Error: Unhandled exception:
E/flutter ( 9767): Looking up a deactivated widget's ancestor is unsafe.
E/flutter ( 9767): At this point the state of the widget's element tree is no longer stable. To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling inheritFromWidgetOfExactType() in the widget's didChangeDependencies() method.
After I comment out the await Future.delayed(Duration(seconds: 5)); it worked fine.
My question is why did it failed when using Future.delayed?
Here is the full error:
I/flutter ( 9767): confirm dialog has pop
I/flutter ( 9767): showing processingdialog
I/flutter ( 9767): processdialog is being shown.
I/flutter ( 9767): buying some time
I/flutter ( 9767): done buying some time
I/flutter ( 9767): exporting begin
I/flutter ( 9767): exporting done
E/flutter ( 9767): [ERROR:flutter/shell/common/shell.cc(184)] Dart Error: Unhandled exception:
E/flutter ( 9767): Looking up a deactivated widget's ancestor is unsafe.
E/flutter ( 9767): At this point the state of the widget's element tree is no longer stable. To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling inheritFromWidgetOfExactType() in the widget's didChangeDependencies() method.
E/flutter ( 9767):
E/flutter ( 9767): #0 Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure> (package:flutter/src/widgets/framework.dart:3246:9)
E/flutter ( 9767): #1 Element._debugCheckStateIsActiveForAncestorLookup (package:flutter/src/widgets/framework.dart:3255:6)
E/flutter ( 9767): #2 Element.ancestorStateOfType (package:flutter/src/widgets/framework.dart:3303:12)
E/flutter ( 9767): #3 Navigator.of (package:flutter/src/widgets/navigator.dart:1288:19)
E/flutter ( 9767): #4 ChatWindow.showExportedDialog.<anonymous closure>.<anonymous closure> (package:msgdiary/main.dart:368:37)
E/flutter ( 9767): <asynchronous suspension>
E/flutter ( 9767): #5 _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:507:14)
E/flutter ( 9767): #6 _InkResponseState.build.<anonymous closure> (package:flutter/src/material/ink_well.dart:562:30)
E/flutter ( 9767): #7 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:102:24)
E/flutter ( 9767): #8 TapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:242:9)
E/flutter ( 9767): #9 TapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:175:7)
E/flutter ( 9767): #10 PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:315:9)
E/flutter ( 9767): #11 PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:73:12)
E/flutter ( 9767): #12 PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:101:11)
E/flutter ( 9767): #13 _WidgetsFlutterBinding&BindingBase&GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:180:19)
E/flutter ( 9767): #14 _WidgetsFlutterBinding&BindingBase&GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:158:22)
E/flutter ( 9767): #15 _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:138:7)
E/flutter ( 9767): #16 _WidgetsFlutterBinding&BindingBase&GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:101:7)
E/flutter ( 9767): #17 _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:85:7)
E/flutter ( 9767): #18 _invoke1 (dart:ui/hooks.dart:168:13)
E/flutter ( 9767): #19 _dispatchPointerDataPacket (dart:ui/hooks.dart:122:5)
UPDATE:
It was my fault. I need to study more about context. It seems that I was popping the same context for the two dialogs. I changed the name of the dialog and it worked.
Why don't you extract it to a custom dialog widget and handle its states dynamically? It's cleaner and more customizable, also giving a timer (like you did of 5 seconds) it's not a good practice since you can't be sure how much time it will take to do its work.
Then I can suggest, for example, to create an enum DialogState with 3 states
enum DialogState {
LOADING,
COMPLETED,
DISMISSED,
}
Then create your own Dialog widget that when built receives its current state
class MyDialog extends StatelessWidget {
final DialogState state;
MyDialog({this.state});
#override
Widget build(BuildContext context) {
return state == DialogState.DISMISSED
? Container()
: AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
),
content: Container(
width: 250.0,
height: 100.0,
child: state == DialogState.LOADING
? Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
Padding(
padding: const EdgeInsets.only(left: 10.0),
child: Text(
"Exporting...",
style: TextStyle(
fontFamily: "OpenSans",
color: Color(0xFF5B6978),
),
),
)
],
)
: Center(
child: Text('Data loaded with success'),
),
),
);
}
}
and then, in your screen, you can insert it anywhere you want. I changed my exportData function to dummy a request that takes 5 seconds.
class MyScreen extends StatefulWidget {
_MyScreenState createState() => _MyScreenState();
}
class _MyScreenState extends State<MyScreen> {
DialogState _dialogState = DialogState.DISMISSED;
void _exportData() {
setState(() => _dialogState = DialogState.LOADING);
Future.delayed(Duration(seconds: 5)).then((_) {
setState(() => _dialogState = DialogState.COMPLETED);
Timer(Duration(seconds: 3), () => setState(() => _dialogState = DialogState.DISMISSED));
});
}
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Stack(
alignment: Alignment.center,
children: <Widget>[
RaisedButton(
child: Text('Show dialog'),
onPressed: () => _exportData(),
),
MyDialog(
state: _dialogState,
)
],
),
),
);
}
}
You can do the following:
onPressed: () async{
//Popping the confirm dialog
Navigator.pop(context);
//Showing the progress dialog
showProcessingDialog();
//wait 5 seconds : just for testing purposes, you don't need to wait in a real scenario
await Future.delayed(Duration(seconds: 5));
//call export data
await exportData();
//Pops the progress dialog
Navigator.pop(context);
//Shows the finished dialog
await showFinishedDialog();
},
I am getting the following error when beginning to implement the camera plugin on my flutter app:
[VERBOSE-2:dart_error.cc(16)] Unhandled exception:
MissingPluginException(No implementation found for method init on channel plugins.flutter.io/camera)
#0 MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:153:7)
<asynchronous suspension>
#1 _channel (package:camera/camera.dart:7:5)
#2 _channel (package:camera/camera.dart:6:21)
#3 availableCameras (package:camera/camera.dart:42:41)
<asynchronous suspension>
#4 main (file:///Users/waynerumble/Desktop/scott_and_viki/lib/main.dart:10:19)
<asynchronous suspension>
#5 _startIsolate.<anonymous closure> (dart:isolate/runtime/libisolate_patch.dart:279:19)
#6 _RawReceivePortImpl._handleMessage (dart:isolate/runtime/libisolate_patch.dart:165:12)
[VERBOSE-2:dart_error.cc(16)] Unhandled exception:
MissingPluginException(No implementation found for method list on channel plugins.flutter.io/camera)
#0 MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:153:7)
<asynchronous suspension>
#1 availableCameras (package:camera/camera.dart:42:50)
<asynchronous suspension>
#2 main (file:///Users/waynerumble/Desktop/scott_and_viki/lib/main.dart:10:19)
<asynchronous suspension>
#3 _startIsolate.<anonymous closure> (dart:isolate/runtime/libisolate_patch.dart:279:19)
#4 _RawReceivePortImpl._handleMessage (dart:isolate/runtime/libisolate_patch.dart:165:12)
The error doesn't occur until i start using the plugin itself, i.e. if i replace Future<null> main() etc with void main() => runApp(new App()); the app runs fine. I've followed install instructions from the link provided and tried pasting in all the example incode in place of mine but still get the errors
My main.dart:
import 'package:flutter/material.dart';
import 'Localisations.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'dart:async';
import 'package:camera/camera.dart';
List<CameraDescription> cameras;
Future<Null> main() async {
cameras = await availableCameras();
runApp(new App());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
var statusBarHeight = MediaQuery.of(context).padding.top;
var titleText = new Text(Localize.of(context).appTitle,
textAlign: TextAlign.center,
style: new TextStyle(fontFamily: 'CallingAngelsPersonalUse',
fontSize: 50.0,
color: Colors.black)
);
var backgroundImage = new BoxDecoration(
image: new DecorationImage(
image: new AssetImage('assets/background.png'),
fit: BoxFit.cover,
),
);
var mainContainer = new Container(
padding: EdgeInsets.only(top: statusBarHeight),
height: double.infinity,
width: double.infinity,
decoration: backgroundImage,
child: new Column(
children: <Widget>[
new Container(
margin: EdgeInsets.only(top: 10.0),
child: titleText
)
],
),
);
return new Scaffold(
body: mainContainer,
);
}
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
onGenerateTitle: (BuildContext context) => Localize.of(context).appTitle,
localizationsDelegates: [
const LocalizeDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale('en', '')
],
home: new MyApp(),
);
}
}
My pubspec.yml:
dependencies:
flutter:
sdk: flutter
camera: ^0.1.2
path_provider: ^0.4.0
flutter_localizations:
sdk: flutter
This is my first real flutter app so any help would be appreciated.
Thanks
first check your app.build minSdkVersion.make sure minSdkVersion is 21.
minSdkVersion 21
here code example.
List<CameraDescription> cameras = [];
main() async{
WidgetsFlutterBinding.ensureInitialized();
try {
cameras = await availableCameras();
} on CameraException catch (e) {
logError(e.code, e.description);
}
runApp(MyApp());
}