Flutter: showDialog: build function returned null - dart

I have a StatefulWidget. Then when I click a button, it shows an alert dialog. When I implement:
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Hello"),
);
}
}
Everything works fine. But when I transfered the things inside the builder to a different StatefulWidget, then this error occurs:
A build function returned null.
I/flutter ( 3647): The offending widget is: Builder
I/flutter ( 3647): Build functions must never return null. To return an empty space that causes the building widget to
I/flutter ( 3647): fill available room, return "new Container()". To return an empty space that takes as little room as
I/flutter ( 3647): possible, return "new Container(width: 0.0, height: 0.0)".
Here is the code:
Here is the calling StatefulWidget:
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) {
LastVacDialog(
currentDose: currDose,
currentDate: currDate,
currentIndex: i,
setValue: changeDoseValueAndDate,
);
},
);
},
Here is the new StatefulWidget:
class LastVacDialog extends StatefulWidget {
LastVacDialog({
this.currentDose,
this.currentDate,
this.setValue,
this.currentIndex,
});
final int currentDose;
final DateTime currentDate;
final void Function(int, DateTime, int) setValue;
final currentIndex;
#override
LastVacDialogState createState() => new LastVacDialogState();
}
class LastVacDialogState extends State<LastVacDialog> {
int _dose;
DateTime _today;
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text("Last Dose"),
);
}
}
Is there something wrong with my code? I just omitted some variables for simplicity.

Add the Word Return in Front of - LastVacDialog
builder: (BuildContext context) {
return LastVacDialog(
...
As Error is Stating Build function must never return null. So return your LastVacDialog Widget by adding return in front of it.

Related

How to fix FutureBuilder open multiple times error?

these my two classes(two pages). these two classes open multiple times.
I put debug point in futurebuilder in two classes.
debug point running,
MainCategory page and got to the next page
SubCategory page and again running MainCategory page(previous page) futurebuilder and again running MainCategory page futurebuilder
navigate subcategory page to third page running subcategory page and main category page
I upload my two classes to GitHub and please let me know what the issue is.
MainCategory code: https://github.com/bhanuka96/ios_login/blob/master/MainCategory.dart
SubCategory code: https://github.com/bhanuka96/ios_login/blob/master/subCategory.dart
As stated in the documentation, you should not fetch the Future for the Futurebuilder during the widget's build event.
https://docs.flutter.io/flutter/widgets/FutureBuilder-class.html
The future must have been obtained earlier, e.g. during
State.initState, State.didUpdateConfig, or
State.didChangeDependencies. It must not be created during the
State.build or StatelessWidget.build method call when constructing the
FutureBuilder. If the future is created at the same time as the
FutureBuilder, then every time the FutureBuilder's parent is rebuilt,
the asynchronous task will be restarted.
So, try to move your call to getRegister method outside the build method and replace it with the returned Future value.
For example, below I have a class that returns a Future value which will be consumed with the help of FutureBuilder.
class MyApiHelper{
static Future<List<String>> getMyList() async {
// your implementation to make server calls
return List<String>();
}
}
Now, inside your widget, you will have something like this:
class _MyHomePageState extends State<MyHomePage> {
Future<List<String>> _myList;
#override
void initState() {
super.initState();
_myList = MyApiHelper.getMyList();
}
#override
Widget build(BuildContext context) {
return Scaffold(body: FutureBuilder(
future: _myList,
builder: (_, AsyncSnapshot<List<String>> snapLs) {
if(!snapLs.hasData) return CircularProgressIndicator();
return ListView.builder(
itemCount: snapLs.data.length,
itemBuilder: (_, index) {
//show your list item row here...
},
);
},
));
}
}
As shown above, the Future is fetched in the initState function and used inside the build method and used by FutureBuilder.
I hope this was helpful.
Thanks.
If you happen to use Provider, here's (in my opinion) a clearer alternative based on your question:
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureProvider<List<String>>(
create: (_) => MyApiHelper.getMyList(),
child: Consumer<List<String>>(
builder: (_, list, __) {
if (list == null) return CircularProgressIndicator();
return ListView.builder(
itemCount: list.length,
itemBuilder: (_, index) {
//show your list item row here...
},
);
};
),
);
}
}
This can also be achieved of course as a StatefulWidget as suggested by the other answer, or even with flutter_hooks as explained in Why is my Future/Async Called Multiple Times?
You can create new Widget and pass Function to
returnFuture as
() {
return YourFuture;
}
import 'dart:developer';
import 'package:flutter/material.dart';
class MyFutureBuilder<T> extends StatefulWidget {
final Future<T> Function() returnFuture;
final AsyncWidgetBuilder<T> builder;
final T initialData;
MyFutureBuilder({
this.returnFuture,
#required this.builder,
this.initialData,
Key key,
}) : super(key: key);
#override
_MyFutureBuilderState<T> createState() => _MyFutureBuilderState<T>();
}
class _MyFutureBuilderState<T> extends State<MyFutureBuilder<T>> {
bool isLoading = false;
Future<T> future;
#override
void initState() {
super.initState();
future = widget.returnFuture();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
builder: widget.builder,
initialData: widget.initialData,
future: future,
);
}
}
Example
MyFutureBuilder<List<User>>(
returnFuture: () {
return moderatorUserProvider
.getExecutorsAsModeratorByIds(val.users,
save: true);
},
builder: (cont, asyncData) {
if (asyncData.connectionState !=
ConnectionState.done) {
return Center(
child: MyCircularProgressIndicator(
color: ModeratorColor.executors.color,
),
);
}
return Column(
children: asyncData.data
.map(
(singlExecutor) =>
ChooseInfoButton(
title:
'${singlExecutor.firstName} ${singlExecutor.secondName}',
subTitle: 'Business analyst',
middleText: '4.000 NOK',
subMiddleText: 'full time',
label: 'test period',
subLabel: '1.5 month',
imageUrl:
assetsUrl + 'download.jpeg',
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) =>
ModeratorExecutorEditPage(),
),
);
},
),
)
.toList());
},
)
```

How to use showModalBottomSheet in StatelessWidget?

I try to code like this
showModalBottomSheet(
context: context, // I got error here (Undefined name 'context'.dart(undefined_identifier))
builder: (context){
return Container(
);
);
I got error on context: context, error message is
Undefined name 'context'.dart(undefined_identifier)
This is how you call the showModalBottomSheet in a StatelessWidget
class TestStatelessWidget extends StatelessWidget{
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text('Press me'),
onPressed: ()=>showPress(context),
);
}
void showPress(BuildContext context){
showModalBottomSheet(context:context, builder: (context){
return Text('hello');
});
}
}
You are getting this error because you don't have access to context everywhere in a StatelessWidget class. All you need to do is pass context from build() if you are using this outside build() method else use it within build() method.
Solution 1. (Using inside build())
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text("Show sheet"),
onPressed: () {
showModalBottomSheet(context: context, builder: (context) => YourWidget());
},
);
}
Solution 2. (Using outside build())
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text("Show sheet"),
onPressed: () => _showSheet(context),
);
}
void _showSheet(BuildContext context) {
showModalBottomSheet(context: context, builder: (context) => YourWidget());
}
You did not close your showModalBottomSheet's builder correctly.
There is a } missing.
This would be the correct usage:
showModalBottomSheet(
context: context,
builder: (builder){
return Container();
}
);

Flutter scoped_model not find when try to access with Navigator page

I am using flutter scoped_model, When we try to access ScopedModel from child Widget which able to access without error.
But same code was not working with when i try to access it from Widget which load using Navigator.push, it gives error Error: Could not find the correct ScopedModel.
PageModel declare at top page.
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
class PageModel extends Model {
String title;
static PageModel of(BuildContext context) =>
ScopedModel.of<PageModel>(context);
loadTitle() {
title = 'Old Title ';
}
updateTitle() {
title = 'New Title';
notifyListeners();
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final PageModel model = PageModel();
#override
Widget build(BuildContext context) {
return ScopedModel<PageModel>(
model: model,
child: Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: HomePageBody(),
),
);
}
}
class HomePageBody extends StatefulWidget {
#override
_HomePageBodyState createState() => _HomePageBodyState();
}
class _HomePageBodyState extends State<HomePageBody> {
#override
void initState() {
PageModel.of(context).loadTitle();
super.initState();
}
#override
Widget build(BuildContext context) {
return ScopedModelDescendant<PageModel>(
builder: (BuildContext context, child, PageModel model) {
return Column(
children: <Widget>[
Text(model.title),
RaisedButton(
child: Text('Edit'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPage(), fullscreenDialog: true),
);
},
),
],
);
});
}
}
class DetailPage extends StatefulWidget {
#override
_DetailPageState createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Edit Page'),
),
body: RaisedButton(
child: Text('Update'),
onPressed: () {
PageModel.of(context).updateTitle();
},
),
);
}
}
From DetailPage when we call PageModel.of(context).updateTitle(); following error coming,
/flutter (26710): ══╡ EXCEPTION CAUGHT BY GESTURE ╞═══════════════════════════════════════════════════════════════════
I/flutter (26710): The following ScopedModelError was thrown while handling a gesture:
I/flutter (26710): Error: Could not find the correct ScopedModel.
I/flutter (26710):
I/flutter (26710): To fix, please:
I/flutter (26710):
I/flutter (26710): * Provide types to ScopedModel<MyModel>
I/flutter (26710): * Provide types to ScopedModelDescendant<MyModel>
I/flutter (26710): * Provide types to ScopedModel.of<MyModel>()
I/flutter (26710): * Always use package imports. Ex: `import 'package:my_app/my_model.dart';
I/flutter (26710):
I/flutter (26710): If none of these solutions work, please file a bug at:
I/flutter (26710): https://github.com/brianegan/scoped_model/issues/new
What you’re doing is trying to find a PageModel of that context which there is none, since you haven’t created any for that specific widget context.
What you want to wrap your RaisedButton in a ScopedModelDescendant<PageModel> and update your model by using the model.updateTitle() instead.
That will look for the closest PageModel ancestor in the tree.

How to prevent passing down BuildContext?

Currently I get the BuildContext from the build method in HomeScreen, and then I have to pass it down to _gridSliver then down to _storeCard.
How can I write the code so that I don't need to pass the context down?
Maybe I can create a new private StatelessWidget called _StoreCard that will have its own build method and thus its own BuildContext?
class HomeScreen extends StatelessWidget {
HomeScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StoreConnector<AppState, List<MyStore.Store>>(
converter: (Store<AppState> store) => store.state.home.stores,
builder: (BuildContext context, List<MyStore.Store> stores) =>
CustomScrollView(slivers: <Widget>[_gridSliver(stores, context)]));
}
Widget _gridSliver(stores, context) {
return SliverGrid(
delegate: SliverChildListDelegate(List<Widget>.from(stores.map(_storeCard, context))));
}
Widget _storeCard(MyStore.Store store, BuildContext context) {
return InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => StoreScreen(storeId: store.id)),
);
},
child: Container(child: Text(store.name))
);
}
}
Another instance of this problem is I navigate on a child function.
#override
Widget build(BuildContext context) {
return Column(
children: [
WhiteButton(text: "Login with Facebook", onPressed: _loginWithFacebook),
WhiteButton(text: "Login with Google", onPressed: _loginWithGoogle),
])
)
}
_loginWithFacebook(context) async {
...
var user = User.fromFacebook(result.accessToken.token, json.decode(graphResponse.body));
await _login(user, context);
}
}
_loginWithGoogle(context) async {
...
GoogleSignInAccount googleUser = await _googleSignIn.signIn();
await _login(User.fromGoogle(googleUser), context);
}
_login(user, context) async {
var fetchedUser = await MeService.getUser(user);
if (fetchedUser != null) {
loginSuccess(fetchedUser);
Navigator.popUntil(context, ModalRoute.withName(MainRoutes.root));
} else {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => RegisterScreen(user: user)),
);
}
}
To get a new BuildContext, you have two main solutions:
Extract part of the subtree into a new widget, typically StatelessWidget. And then use it's BuildContext from the build method
Use Builder widget, which is basically a reusable widget made to obtain a BuildContext:
Example:
#override
Widget build(BuildContext context) {
return Builder(
builder: (context) {
// do something with this new context
},
);
}
You have to use a Bloc pattern that uses an Inherited Widget, but still you'll have to pass context, but in a more straight forward way. I recommend using this app by Stephen Grider, to figure out how the whole thing works. He explains in his tutorial how to put the whole thing together but I can't link you to that because that would be advertising.
The idea is, you first create a file Bloc.dart that is going to contain your logic, then you create what is called a Provider, in a Provider.dart.
Provider.dart:
class Provider extends InheritedWidget {
final bloc = Bloc();
Provider({Key key, Widget child}) : super(key: key, child: child);
bool updateShouldNotify(_) => true;
static Bloc of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(Provider) as Provider).bloc;
}
}
In your file that contains the Material App, you wrap the material App with the provider:
Widget build(BuildContext context) {
return Provider(
child: MaterialApp(
And then you use the provider in every other class down the three of widgets.
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
final bloc = Provider.of(context); // this is where you insert the provider
return StoreConnector<AppState, List<MyStore.Store>>(
converter: (Store<AppState> store) => store.state.home.stores,
builder: (BuildContext context, List<MyStore.Store> stores) =>
CustomScrollView(slivers: <Widget>[_gridSliver(stores, context)]));
}
Widget _gridSliver(stores) {
final bloc = Provider.of(context);
return SliverGrid(
delegate: SliverChildListDelegate(List<Widget>.from(stores.map(_storeCard, context))));
}
Widget _storeCard(MyStore.Store store) {
final bloc = Provider.of(context);
return InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => StoreScreen(storeId: store.id)),
);
},
child: Container(child: Text(store.name))
);
}
}
I'm a total noob with flutter and take everything with grain of salt, but this is what I would use. Hope it helps.

Flutter: Inherited Widget and Routes

I want to have an inherited widget at the root of my application, which will contain my data providers, which I would use throughout the app. So I have this inherited widget, but every time I try to load it I get this The getter 'data' was called on null and I can't figure out why.
So here's my main.dart:
void main() => runApp(new MatAppRoot());
class MatAppRoot extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'MyCoolApp',
routes: <String, WidgetBuilder>{
'Login': (BuildContext context) => new LoginPage(),
'Cool': (BuildContext context) => new CoolPage(),
},
home: new CoolApp(),
);
}
}
class CoolAppextends StatefulWidget {
final Widget child;
CoolApp({this.child});
#override
CoolAppState createState() => new CoolAppState();
static CoolAppState of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(CoolInherit) as CoolInherit).data;
}
}
class CoolAppState extends State<CoolApp> {
String randomString = 'AYEEAS!!!';
#override
void initState() { super.initState();
Navigator.of(context).pushNamedAndRemoveUntil('Login', (Route<dynamic> route) => false);
}
#override
Widget build(BuildContext context) {
return new CoolInherit(
data: this,
child: new LoginPage(),
);
}
}
class CoolInherit extends InheritedWidget {
final CoolAppState data;
CoolInherit({
Key key,
this.data,
Widget child,
}): super(
key: key,
child: child
);
#override
bool updateShouldNotify(CoolInherit old) {
return true;
}
}
then my LoginPage basically redirects after the login like this:
if (logInSuccessful) {
Navigator.of(context).pushNamedAndRemoveUntil('Cool', (Route<dynamic> route) => false);
}
In my Cool page I try to load another page when clicking a button like this:
viewCoolDetails() {
Navigator.push(
context,
new MaterialPageRoute(builder: (context) => new CoolDetailsPage()),
);
}
but in my CoolDetailsPage it crashes when I do this:
#override
Widget build(BuildContext context) {
final inheritedWidget = CoolApp.of(context);
print(inheritedWidget.randomString); <-- ERROR: The getter 'data' was called on null
return new Text('Cool!');
}
Error Details:
I/flutter ( 6129): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 6129): The following NoSuchMethodError was thrown building CoolDetailsPage(dirty, state:
I/flutter ( 6129): _CoolDetailsPage#ba0bb):
I/flutter ( 6129): The getter 'data' was called on null.
I/flutter ( 6129): Receiver: null
I/flutter ( 6129): Tried calling: data
I/flutter ( 6129):
I/flutter ( 6129): When the exception was thrown, this was the stack:
I/flutter ( 6129): #0 Object.noSuchMethod (dart:core/runtime/libobject_patch.dart:46:5)
I/flutter ( 6129): #1 CoolApp.of (/lib/main.dart:56:83)
... etc etc
main.dart:56 is return (context.inheritFromWidgetOfExactType(CoolInherit) as CoolInherit).data; and so if my detective work is up to par, I suspect it is something to with navigations/context, which is preventing my final widget from accessing the inheritedWidget, but I'm not sure about that.
UPDATE:
the best I can tell, I need to insert my InheritedWidget at a higher level; before the navigator. so I inserted this into the MaterialApp:
builder: (context, child) {
return new CoolApp(child: child);
},
but that didn't seen to work...
E/flutter (32321): [ERROR:topaz/lib/tonic/logging/dart_error.cc(16)] Unhandled exception:
E/flutter (32321): Navigator operation requested with a context that does not include a Navigator.
E/flutter (32321): 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.
E/flutter (32321): #0 Navigator.of.<anonymous closure> (package:flutter/src/widgets/navigator.dart:1180:9)
E/flutter (32321): #1 Navigator.of (package:flutter/src/widgets/navigator.dart:1187:6)
I had the same problem for a long time and I realized that if you wrap the MaterialApp with the Inherited Widget, your data is accessible through the entire app. But in your case, you need to pass data after the user login so to do that you need to create a new Navigator and wrap it with your Inherited Widget. You can see this project https://github.com/johnstef99/inherited-widget-demo
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'InheritedWidget Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyNav(),
);
}
}
Route generatePage(child) {
return MaterialPageRoute(builder: (context) => child);
}
class MyNav extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MyData(
data: 'omg',
child: Navigator(
onGenerateRoute: (settings) {
switch (settings.name) {
case 'page1':
return generatePage(PageOne());
case 'page2':
return generatePage(PageTwo());
case 'page3':
return generatePage(PageThree());
}
},
initialRoute: 'page1',
),
);
}
}
class MyData extends InheritedWidget {
MyData({Key key, this.child, this.data}) : super(key: key, child: child);
final Widget child;
final String data;
static MyData of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(MyData) as MyData);
}
#override
bool updateShouldNotify(MyData oldWidget) {
return true;
}
}
class PageOne extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page 1'),
),
backgroundColor: Colors.red,
body: RaisedButton(
child: Text("Goto page 2, data=${MyData.of(context).data}"),
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (_) => PageTwo()));
},
),
);
}
}
class PageTwo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page 2'),
),
backgroundColor: Colors.red,
body: RaisedButton(
child: Text("Goto page 3, data=${MyData.of(context).data}"),
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (_) => PageThree()));
},
),
);
}
}
class PageThree extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page 3'),
),
backgroundColor: Colors.green,
body: RaisedButton(
child: Text("Goto page 4, data=${MyData.of(context).data}"),
onPressed: null,
),
);
}
}
That is because you're trying to access CoolApp which is in the route / from another route (dynamic).
But inside your dynamic route, there's no CoolApp. So CoolApp.of(context) returns null, and therefore accessing .data crashes.
You need to find a way to have a CoolApp instance inside your new route.
For more informations, take a look at Get access to the context of InheritedWidget

Resources