Computer Vision with ML Kit - Flutter In Focus - dart

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.

Related

How to Render data from Request inside a FutureBuilder

I'm trying to list pokemons from my api, its works when i call the list but when i try to get the data for each pokemon it doesn't render the data.
updated the code for the #Gazihan Alankus answer.
Widget build(BuildContext context) {
return FutureBuilder<PokeData>(
future: api.getPokemonList(),
builder: (
BuildContext context,
AsyncSnapshot<PokeData> pokedata
) {
if(pokedata.hasData && pokedata.connectionState == ConnectionState.done) {
return Container(
child: ListView(
children: pokedata.data.results.map((pokemon) {
FutureBuilder(
future: api.getPokeDetail(pokemon.url),
builder: (
BuildContext context,
AsyncSnapshot snapshot
) {
print('------------widget------------');
if(snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
return Container(child: PokemonWidget(
pokemon.name,
snapshot.data.sprites.normal,
snapshot.data.types
),);
} else {
return Container(
child: CircularProgressIndicator(),
);
}
}
);
}).toList(),
),
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
);
}
But now i am getting this error when the app is running, the first request works well but it never call the second request.
flutter: ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
flutter: The following assertion was thrown during performLayout():
flutter: 'package:flutter/src/widgets/sliver.dart': Failed assertion: line 553 pos 12: 'child != null': is
flutter: not true.
flutter:
flutter: Either the assertion indicates an error in the framework itself, or we should provide substantially
flutter: more information in this error message to help you determine and fix the underlying cause.
flutter: In either case, please report this assertion by filing a bug on GitHub:
flutter: https://github.com/flutter/flutter/issues/new?template=BUG.md
flutter:
flutter: (elided 5 frames from class _AssertionError and package dart:async)
flutter:
flutter: The following RenderObject was being processed when the exception was fired:
flutter: RenderSliverList#04cee relayoutBoundary=up2 NEEDS-LAYOUT NEEDS-PAINT
flutter: creator: SliverList ← MediaQuery ← SliverPadding ← Viewport ← IgnorePointer-[GlobalKey#b48da] ←
flutter: Semantics ← Listener ← _GestureSemantics ←
flutter: RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#865eb] ← _ScrollableScope ←
flutter: _ScrollSemantics-[GlobalKey#8f9cb] ← Scrollable ← ⋯
flutter: parentData: paintOffset=Offset(0.0, 0.0) (can use size)
flutter: constraints: SliverConstraints(AxisDirection.down, GrowthDirection.forward, ScrollDirection.idle,
flutter: scrollOffset: 0.0, remainingPaintExtent: 603.0, crossAxisExtent: 375.0, crossAxisDirection:
flutter: AxisDirection.right, viewportMainAxisExtent: 667.0, remainingCacheExtent: 853.0 cacheOrigin: 0.0 )
flutter: geometry: null
flutter: no children current live
flutter: This RenderObject has no descendants.
flutter: ════════════════════════════════════════════════════════════════════════════════════════════════════
flutter: Another exception was thrown: NoSuchMethodError: The getter 'scrollOffsetCorrection' was called on null.
flutter: Another exception was thrown: NoSuchMethodError: The method 'debugAssertIsValid' was called on null.
flutter: Another exception was thrown: NoSuchMethodError: The getter 'visible' was called on null.
flutter: Another exception was thrown: NoSuchMethodError: The getter 'visible' was called on null.
One way is to create a FutureBuilder per item, like so:
Widget build(BuildContext context) {
return FutureBuilder<PokeData>(
future: api.getPokemonList(),
builder: (
BuildContext context,
AsyncSnapshot<PokeData> snapshot
) {
if(snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
return Container(
child: ListView(
children: snapshot.data.results.map((pokemon) {
FutureBuilder(
future: api.getPokeDetail(pokemon.url),
builder: (
BuildContext context,
AsyncSnapshot snapshot
) {
if(snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
return PokemonWidget(
pokemon.name,
resp.sprites.normal,
resp.types
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
}
)
}).toList(),
),
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
);
}

How do I manage conditional rendering in Flutter using Bloc without getting "boolean expression must not be null" error?

I am just trying out flutter and I cannot seem to get components to render conditionally based on a BehaviourStream in my Bloc.
I wish to initially show the "_buildPage()" widget (which is an auth form), then while _isLoading is true but (_loginSucceded is false) I wish to show the spinner. Lastly, when _loginSucceded is true and _isLoading is false, I wish to redirect the user.
Actual behaviour is once form is submitted loader shows as expected. Once the response is successfully received from the server however the auth for is rendered once again.
I think my logic is fine but it seems that when I set the values of the stream in the Bloc constructor something else is causing the app to rerender which results in null values in the stream.
Is there a way to ensure a stream always has base values after the constructor has run upon initialisation?
Or is there a better way to manage this scenario? I have only just started looking at Flutter from a JS background so I may well be missing something.
Bloc code:
import 'dart:async';
import 'dart:convert';
import 'package:rxdart/rxdart.dart';
import 'package:http/http.dart' as http;
import './auth_validator.dart';
class AuthBloc with AuthValidator {
final _email = BehaviorSubject<String>();
final _password = BehaviorSubject<String>();
final _isLoading = BehaviorSubject<bool>();
final _loginSucceded = BehaviorSubject<bool>();
AuthBloc() {
_isLoading.sink.add(false);
_loginSucceded.sink.add(false);
}
// stream getters
Stream<String> get email => _email.stream.transform(validateEmail);
Stream<String> get password => _password.stream.transform(validatePassword);
Stream<bool> get isLoading => _isLoading.stream;
Stream<bool> get loginSuccess => _loginSucceded.stream;
Stream<bool> get submitValid =>
Observable.combineLatest2(email, password, (e, p) => true);
// add data to sink onChange
Function(String) get emailChanged => _email.sink.add;
Function(String) get passwordChanged => _password.sink.add;
void submitForm() async {
try {
final Map user = {'email': _email.value, 'password': _password.value};
final jsonUser = json.encode(user);
_isLoading.sink.add(true);
// submit to server
final http.Response response = await http.post(
'http://192.168.1.213:5000/api/users/signin',
body: jsonUser,
headers: {'Content-Type': 'application/json'},
);
final Map<String, dynamic> decodedRes = await json.decode(response.body);
_isLoading.sink.add(false);
_loginSucceded.sink.add(true);
void dispose() {
_email.close();
_password.close();
_isLoading.close();
_loginSucceded.close();
}
} catch (e) {
print('error: $e');
_isLoading.sink.add(false);
}
}
}
Widget code:
import 'package:flutter/material.dart';
import '../blocs/auth_bloc.dart';
class LoginPage extends StatelessWidget {
final authBloc = AuthBloc();
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: authBloc.loginSuccess,
builder: (context, snapshot1) {
return StreamBuilder(
stream: authBloc.isLoading,
builder: (context, snapshot2) {
print('loginSuccess? ${snapshot1.data} isLoading? ${snapshot2.data}');
return Scaffold(
body: !snapshot1.data && snapshot2.data
? _circularSpinner()
: snapshot1.data && snapshot2.data
? Navigator.pushReplacementNamed(context, '/dashboard')
: _buildPage());
},
);
},
);
}
Widget _buildPage() {
return Container(
margin: EdgeInsets.all(20.0),
child: Center(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
_emailField(authBloc),
_padding(),
_passwordField(authBloc),
_padding(),
_submitButton(authBloc)
],
),
),
),
);
}
Widget _circularSpinner() {
return Center(
child: CircularProgressIndicator(),
);
}
Widget _emailField(AuthBloc authBloc) {
return StreamBuilder(
stream: authBloc.email,
builder: (BuildContext context, snapshot) {
return TextField(
onChanged: authBloc.emailChanged,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
hintText: 'you#example.com',
labelText: 'Email Address',
errorText: snapshot.error,
border: OutlineInputBorder(),
),
);
},
);
}
Widget _passwordField(AuthBloc authBloc) {
return StreamBuilder(
stream: authBloc.password,
builder: (BuildContext context, snapshot) {
return TextField(
onChanged: authBloc.passwordChanged,
obscureText: true,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
hintText: '8 characters or more with at least 1 number',
labelText: 'Password',
errorText: snapshot.error,
border: OutlineInputBorder(),
),
);
},
);
}
Widget _padding() {
return Padding(
padding: EdgeInsets.only(top: 20.0),
);
}
Widget _submitButton(AuthBloc authBloc) {
return StreamBuilder(
stream: authBloc.submitValid,
builder: (context, snapshot) {
return RaisedButton(
child: Text('Login'),
color: Colors.blue,
onPressed: snapshot.hasError ? null : authBloc.submitForm,
);
});
}
}
main.dart
import 'package:flutter/material.dart';
import './app.dart';
import 'package:flutter/material.dart';
import './pages/auth.dart';
import './pages/dashboard.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
'/': (BuildContext context) => LoginPage(),
'/dashboard': (BuildContext context) => DashBoardPage(),
},
);
}
}
Log
Restarted application in 1,462ms.
I/flutter ( 4998): loginSuccess? false isLoading? false
I/flutter ( 4998): loginSuccess? null isLoading? null
I/flutter ( 4998): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 4998): The following assertion was thrown building StreamBuilder<bool>(dirty, state:
I/flutter ( 4998): _StreamBuilderBaseState<bool, AsyncSnapshot<bool>>#34870):
I/flutter ( 4998): Failed assertion: boolean expression must not be null
I/flutter ( 4998):
I/flutter ( 4998): Either the assertion indicates an error in the framework itself, or we should provide substantially
I/flutter ( 4998): more information in this error message to help you determine and fix the underlying cause.
I/flutter ( 4998): In either case, please report this assertion by filing a bug on GitHub:
I/flutter ( 4998): https://github.com/flutter/flutter/issues/new?template=BUG.md
I/flutter ( 4998):
I/flutter ( 4998): When the exception was thrown, this was the stack:
I/flutter ( 4998): #0 LoginPage.build.<anonymous closure>.<anonymous closure>
as user #user10539074 mentioned, you need to set initial data for the first run of the streambuilder - ie. in your code - if authBloc.isLoading is bool, than change:
StreamBuilder(
stream: authBloc.isLoading,
builder: (context, snapshot2) {
to:
StreamBuilder(
stream: authBloc.isLoading,
initialData: true,
builder: (context, snapshot2) {

How to conditionally redirect in a StreamBuilder, builder function in flutter without Error?

I am trying to get an auth form to conditionally redirect in a flutter StreamBuilder widget using a ternary statement.
When the redirect condition returns true I get a red screen and the following log:
I/flutter ( 3787): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 3787): The following assertion was thrown building StreamBuilder<Map<dynamic, dynamic>>(dirty, state:
I/flutter ( 3787): _StreamBuilderBaseState<Map<dynamic, dynamic>, AsyncSnapshot<Map<dynamic, dynamic>>>#66400):
I/flutter ( 3787): setState() or markNeedsBuild() called during build.
I/flutter ( 3787): This Overlay widget cannot be marked as needing to build because the framework is already in the
I/flutter ( 3787): process of building widgets. A widget can be marked as needing to be built during the build phase
I/flutter ( 3787): only if one of its ancestors is currently building. This exception is allowed because the framework
I/flutter ( 3787): builds parent widgets before children, which means a dirty descendant will always be built.
I/flutter ( 3787): Otherwise, the framework might not visit this widget during this build phase.
I/flutter ( 3787): The widget on which setState() or markNeedsBuild() was called was:
I/flutter ( 3787): Overlay-[LabeledGlobalKey<OverlayState>#4f97a](state: OverlayState#5df28(tickers: tracking 2
I/flutter ( 3787): tickers, entries: [OverlayEntry#09e48(opaque: false; maintainState: false),
I/flutter ( 3787): OverlayEntry#61a61(opaque: false; maintainState: true), OverlayEntry#79842(opaque: false;
I/flutter ( 3787): maintainState: false), OverlayEntry#11ff2(opaque: false; maintainState: true)]))
I/flutter ( 3787): The widget which was currently being built when the offending call was made was:
I/flutter ( 3787): StreamBuilder<Map<dynamic, dynamic>>(dirty, state: _StreamBuilderBaseState<Map<dynamic, dynamic>,
I/flutter ( 3787): AsyncSnapshot<Map<dynamic, dynamic>>>#66400)
Offending widget:
import 'package:flutter/material.dart';
import '../blocs/auth_bloc.dart';
class LoginPage extends StatelessWidget {
final authBloc = AuthBloc();
Map<String, bool> initialData = {'loginSuccess': false, 'isLoading': false};
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: authBloc.redirect,
initialData: initialData,
builder: (context, snapshot) {
return Scaffold(body: _render(context, snapshot));
});
}
Widget _render(BuildContext context, AsyncSnapshot snapshot) {
return !snapshot.data['loginSuccess'] && snapshot.data['isLoading']
? _circularSpinner()
: snapshot.data['loginSuccess'] && !snapshot.data['isLoading']
? _redirect(context)
: _buildPage();
}
_redirect(BuildContext context) {
return Navigator.pushReplacementNamed(context, '/dashboard');
}
Widget _buildPage() {
return Container(
margin: EdgeInsets.all(20.0),
child: Center(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
_emailField(authBloc),
_padding(),
_passwordField(authBloc),
_padding(),
_submitButton(authBloc)
],
),
),
),
);
}
Widget _circularSpinner() {
return Center(
child: CircularProgressIndicator(),
);
}
Widget _emailField(AuthBloc authBloc) {
return StreamBuilder(
stream: authBloc.email,
builder: (BuildContext context, snapshot) {
return TextField(
onChanged: authBloc.emailChanged,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
hintText: 'you#example.com',
labelText: 'Email Address',
errorText: snapshot.error,
border: OutlineInputBorder(),
),
);
},
);
}
Widget _passwordField(AuthBloc authBloc) {
return StreamBuilder(
stream: authBloc.password,
builder: (BuildContext context, snapshot) {
return TextField(
onChanged: authBloc.passwordChanged,
obscureText: true,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
hintText: '8 characters or more with at least 1 number',
labelText: 'Password',
errorText: snapshot.error,
border: OutlineInputBorder(),
),
);
},
);
}
Widget _padding() {
return Padding(
padding: EdgeInsets.only(top: 20.0),
);
}
Widget _submitButton(AuthBloc authBloc) {
return StreamBuilder(
stream: authBloc.submitValid,
builder: (context, snapshot) {
return RaisedButton(
child: Text('Login'),
color: Colors.blue,
onPressed: snapshot.hasError ? null : authBloc.submitForm,
);
});
}
}
I have googled but cannot find anything relating to Navigator in this context.
I expect the Widget to redirect to the 'dashboard' widget. Instead I get red error screen.
I run today into the same problem. I tried the solution from #user8467470 but StreamBuilder is already registered to my Stream. So I ended up with this solution:
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<Response<ExchangeRate>>(
stream: _bloc.exchangeDataStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
switch (snapshot.data.status) {
case Status.LOADING:
return LoadingWidget(loadingMessage: snapshot.data.message);
case Status.COMPLETED:
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.pushReplacementNamed(context, '/home');
});
break;
case Status.ERROR:
return Container(
child: Text(snapshot.data.message),
);
}
}
return Container();
}));
}
This way I have only one stream in my bloc.
I have found a work around but it isn't pretty. I thought I would share it here in case anyone else was having the same problem but I of course expect other more experienced Flutter devs to offer a better solution and rationale.
It seems that for whatever reason StreamBuilder does not like you Navigating away in the builder function due to its build flow. As a result I moved the navigate away outside of the StreamBuilder but inside the build method and added a listener like this:
#override
Widget build(BuildContext context) {
// Manually listening for redirect conidtions here
// once a response is received from the server
authBloc.loginSuccess.listen((data) {
if (data) {
Navigator.pushReplacementNamed(context, '/dashboard');
}
});
return StreamBuilder(
stream: authBloc.isLoading,
initialData: false,
builder: (context, snapshot) {
print(snapshot.data);
return Scaffold(
body: snapshot.data ? _circularSpinner() : _buildPage(),
);
});
}
It seems to work now but will keep my eye on best practice for this.

FocusScope messed up by Navigator

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,
);
}
}

Flutter get context in initState method

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.

Resources