I'm having a problem compiling this flutter code. It throws me the following error:
The following assertion was thrown building FutureBuilder(dirty, state: _FutureBuilderState#afa3f):
A build function returned null.
The offending widget is: FutureBuilder
Build functions must never return null.
To return an empty space that causes the building widget to fill available room, return "Container()". To return an empty space that takes as little room as possible, return "Container(width: 0.0, height: 0.0)".
The code is this:
home: Builder(builder: (context) {
return FutureBuilder(
future: DeeplinkConfig().initUniLinks(context),
builder: (_, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Container(width: 0.0, height: 0.0);
}
return snapshot.data as Widget;
},
);
}),
Please, if someone explains the error and tells me how to fix it, I would greatly appreciate it.
This error message is indicating that the FutureBuilder's builder callback is returning null as the widget to be displayed.
The builder callback should return a non-null Widget to display in the FutureBuilder. You can either return a widget with dimensions 0.0 width and height, or return a widget indicating that data is loading, like a CircularProgressIndicator.
I would recommend a FutureBuilder that returns a CircularProgressIndicator while the future is waiting:
return FutureBuilder(
future: DeeplinkConfig().initUniLinks(context),
builder: (_, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
return snapshot.data as Widget;
},
);
The issue is this line:
return snapshot.data as Widget;
Your Future is out of the waiting state but data is still null. You then cast it to a Widget, which bypasses Dart's type checking and allows the code to compile but break at runtime.
You can assign snapshot.data to a local variable and check that it's not null. That will allow Dart's type promotion to kick in and you can just return the local variable after the check passes without any cast:
return FutureBuilder<Widget>(
future: DeeplinkConfig().initUniLinks(context),
builder: (_, snapshot) {
final data = snapshot.data;
snapshot.connectionState
if (snapshot.connectionState == ConnectionState.waiting || data == null) {
return Container(width: 0.0, height: 0.0);
}
return data;
},
);
This assumes that
Your future always returns a non-null result when it completes and
That return value is a widget
Related
app was working fine all pages working fine but a single page is getting slow after adding this code to my listview
leading: FutureBuilder<Uint8List?>(
future: _thumbnailImage(files[index].path),
builder: (_, AsyncSnapshot<Uint8List?> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
if (snapshot.hasData) {
return Image.memory(snapshot.data!);
}
return Text("error");
},
),
FutureBuilder waits for the data. Until the future function _thumbnailImage(files[index].path) doesn't return the data, ListView (leading property) won't be build.
Note that FutureBuilder will impact only where it is used. Other widgets should be loaded normally.
My application have different routes and I would like to know how to call my api with cubit just once when the user come for the first time on the screen and also not to re-call the api every time he returns to the screen already initialized.
my structure use bloC
and this is my profile page initialization class
#override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final user = context.read<AuthCubit>().state;
final bloc = context.read<ProfileCubit>();
return Scaffold(
body: FutureBuilder(
future: bloc.updateProfilePicture(user!.id),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return BlocBuilder<ProfileCubit, ProfilePicture?>(
buildWhen: (prev, curr) => prev != curr,
builder: (context, picture) {
return picture != null
? Profil(profilePicture: picture, updateIndex: updateIndex)
: Profil(updateIndex: updateIndex);
},
);
}
return Center(
child: CircularProgressIndicator(
color: Colors.orange,
),
);
},
),
);
}
There are many ways to solve this problem
1- easy (but not clean code) is to use boolean global varibal
like isApiReqursted with default value (false) and when call the api set it to true
2- you can cache the response in the repoistory or bloc and make the api method frst check if there are data if there isit does not need to make http request
I have found some posts that address this issue and I have tried to use their solutions without success.
I am new to flutter so I need someone's advice on what to do here.
Here is the error message in the Debug panel:
A build function returned null.
The offending widget is: StreamBuilder<List<Event>>
Build functions must never return null.
To return an empty space that causes the building widget to fill available room, return "Container()". To return an empty space that takes as little room as possible, return "Container(width: 0.0, height: 0.0)".
The relevant error-causing widget was:
StreamBuilder<List<Event>> file:///C:/Users/nkane/AndroidStudioProjects/tonnah/lib/screens/appointment_calendar.dart:166:11
Here is the code starting at line 166:
StreamBuilder(
//stream: _firestoreService.getEventStream(_selectedDay),
stream: _db.collection('agency').doc(globals.agencyId).collection('event')
.where('eventDate', isGreaterThanOrEqualTo: Timestamp.fromDate(_selectedDay))
.snapshots().map((snapshot) => snapshot.docs
.map((document) => Event.fromFirestore(document.data()))
.toList()),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Event> snapData;
return snapData = snapshot.data;
_eventsStream = snapData;
_eventsMap = convertToMap(snapData);
//_selectedEventsMap = _eventsMap[_selectedDay] ?? [];
return _buildTableCalendar();
}
},
),
How do I fix this? I know I need a "return" but how do I use it?
You just need to return a view while the data is fetching/loading. In this case I've used a CircularProgressIndicator widget.
StreamBuilder(
stream: ...,
builder: (context, snapshot) {
if(snapshot.hasData){
return build_your_widget;
} else
return CircularProgressIndicator();
}
)
Basically I use this syntax to use a FutureBuilder:
retur FutureBuilder(
future: future,// http request
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Text("success done");
}
if (snapshot.hasData) {
if (snapshot.data["error"] == "111") {
rerurn Text("not access server")
}
if (snapshot.data["data"]["ok"] == false) {
return Text("problem");
}
return Container();
} else {
return Text("Loading");
}
});
Every time I want to make a web request, I have to write all this code again.
I want to optimize this, so I'm thinking of converting the above code into a method where I simply pass a future (http request) parameter and return what my FutureBuilder would return.
I'm trying something like this:
Future generateFutureBuilder(Future<dynamic> future, Widget widget) {
//widget is a Widget to show when it finishes
FutureBuilder(
future: future,// http request
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Text("success done");
}
if (snapshot.hasData) {
if (snapshot.data["error"] == "111") {
}
if (snapshot.data["data"]["ok"] == false) {
return Text("problem");
}
return widget;
} else {
return Text("Loading");
}
});
}
generateFutureBuilder(http.get(myurl), Container(child:Text("finished")))
but it doesn't behave like it normally does.
What I can do?
Although using a method to abstract widgets is OK, it typically is better to create a class that extends StatelessWidget. To fix your current method, you need to make it return FutureBuilder, not Future, and actually add a return statement. Another good change would be changing the use of dynamics to generics for better type safety.
But another plausible solution that will give you a little more control when building your widget is to not abstract away the FutureBuilder itself, but just the snapshot it gives you. Example:
/// Returns a widget to show if needed (either error or loading), or null if success
Widget getFutureSnapshotWidget(AsyncSnapshot snapshot) {
// if has data that is ok:
// return null;
// else:
// return either an error as a Text or a CircularProgressIndicator
}
Then you can call the following in your builder of FutureBuilder:
final widgetToShow = getFutureSnapshotWidget(snapshot);
if (widgetToShow != null) {
return widgetToShow;
}
// Show regular widget here
My requirement is to make that StreamBuilder connection state to waiting.
I'm using publish subject, whenever I want to load data in stream builder I'm just adding data to the sink by calling postStudentsToAssign() method, here this method making an API call which takes some time, in that time I to want make that streamBuilder connection state to waiting
Stream Builder:
StreamBuilder(
stream: studentsBloc.studentsToAssign,
// initialData: [],
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
// While waiting for the data to load, show a loading spinner.
return getLoader();
default:
if (snapshot.hasError)
return Center(child: Text('Error: ${snapshot.error}'));
else
return _getDrawer(snapshot.data);
}
}),
Initializing Observable:
final _assignStudentSetter = PublishSubject<dynamic>();
Observable<List<AssignMilestoneModel>> get studentsToAssign =>
_studentsToAssignFetcher.stream;
Method that add's data to Stream:
postStudentsToAssign(int studyingClass, String milestoneId, String subject,
List studentList) async {
var response = await provider.postAssignedStudents(
studyingClass, milestoneId, subject, studentList);
_assignStudentSetter.sink.add(response);
}
You can send null to the stream, so the snapshot.connectionState changes to active. I don't know why and whether it's official solution, but it works (at least now). I found this accidentally.
I would like the Flutter team to explain how to set snapshot's connectionState. It's not clear from StreamBuilder documentation. It seems you should replace the stream with a new one to have snapshot in waiting state. But it's agains the logic you want to implement.
I checked StreamBuilder source to find out that the AsyncSnapshot.connectionState starts as waiting (after stream is connected), after receiving data changes to active. snapshot.hasData returns true if snapshot.data != null. That's how following code works.
class SearchScreen extends StatelessWidget {
final StreamController<SearchResult> _searchStreamController = StreamController<SearchResult>();
final SearchService _service = SearchService();
void _doSearch(String text) async {
if (text?.isNotEmpty ?? false) {
_searchStreamController.add(null);
_searchService.search(text)
.then((SearchResult result) => _searchStreamController.add(result))
.catchError((e) => _searchStreamController.addError(e));
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: <Widget>[
SearchBar(
onChanged: (text) => _doSearch(text),
),
StreamBuilder<SearchResult>(
stream: _searchStreamController.stream,
builder: (BuildContext context, AsyncSnapshot<SearchResult> snapshot) {
Widget widget;
if (snapshot.hasData) {
widget = Expanded(
// show search result
);
}
else if (snapshot.hasError) {
widget = Expanded(
// show error
);
}
else if(snapshot.connectionState == ConnectionState.active){
widget = Expanded(
// show loading
);
}
else {
// empty
widget = Container();
}
return widget;
},
),
]),
);
}
}