Typical error one: Future that cannot be mastered
Typical error message: NoSuchMethodError: The method 'markNeedsBuild' was called on null.
This error is often found in asynchronous task (Future) processing, such as a page requesting a web API data and refreshing the Widget State based on the data.
This error occurs when the asynchronous task ends after the page has been popped, but does not check if the State is still mounted
and continues to call setState
.
Sample code
A very common code to get network data, call requestApi()
, wait for Future to get response
from it, and then setState
to refresh the widget.
Cause analysis
The response
is an async-await
asynchronous task, and it is entirely possible that the AWidgetState
is dispose
before it returns, when the Element
bound to the State
is no longer there. So you need to tolerate errors when setState
.
Solution: Check if mounted
before setState
.
Cause analysis
The response
is an async-await
asynchronous task, and it is entirely possible that the AWidgetState
is dispose
before it returns, when the Element
bound to the State
is no longer there. So you need to tolerate errors when setState
.
Solution: Check if mounted
before setState
The AnimationController
may dispose
along with the State, but the FrameCallback
will still be executed, leading to an exception.
Another example is to do something in the callback of the animation listener.
It is also possible that the State has been dispose
before _handleAnimationTick
is called back.
If you don’t understand why, please take a closer look at the Event loop
and review Dart’s thread model.
Typical error #2: Navigator.of(context) is a null
Typical error message: NoSuchMethodError: The method 'pop' was called on null.
Often occurs in showDialog
after processing pop() of dialog.
Sample code
Get network data in a method, to better prompt the user, a loading window will pop up first, and then perform other actions based on the data…
Cause Analysis.
The reason for the error is - Android’s native back button: although the code specifies barrierDismissible: false
, the user cannot tap the translucent area to close the popup window, but when the user clicks the back button, the Flutter engine code will call NavigationChannel.popRoute()
and eventually the loading dialog is turned off, including the page, which in turn causes Navigator.of(context)
to return null
because the context
has been unmounted
and cannot be found from an already withered The error occurs because the context
has been unmounted
and its root cannot be found from a faded leaf.
Also, the context
used in the code Navigator.of(context)
is not quite correct, it actually belongs to the showDialog
caller and not to the dialog, in theory it should use the context
passed from the builder
, and the root can be found along the wrong trunk, but This is not the case in practice, especially if you have Navigator
nested in your app.
Solution
First, make sure that the context
of Navigator.of(context)
is the context
of the dialog.
Second, you can wrap a WillPopScope
around the Dialog Widget outer layer to handle the return key, or check null
as a safety precaution in case it is manually closed.
Pass in GlobalKey
when showDialog
, and use GlobalKey
to get the correct context
.
|
|
A key.currentContext
of null
means that the dialog has been dispose
, i.e. unmounted
from the WidgetTree.
In fact, similar XXX.of(context)
methods are common in Flutter code, such as MediaQuery.of(context)
, Theme.of(context)
, DefaultTextStyle.of(context)
, DefaultAssetBundle.of(context)
and so on, be careful that the context
passed in is from the correct node, otherwise you will have a surprise waiting for you.
When writing Flutter code, make sure you have a clear idea of the trunk of context
in your head.
Typical mistake #3: Schrödinger’s position in ScrollController
When getting the position
or offset
of the ScrollController
or calling jumpTo()
methods, StateError
errors often occur.
Error messages: StateError Bad state: Too many elements
, StateError Bad state: No element
Sample code
After a button click, the ScrollController
controls the ListView
scrolling to the beginning.
Cause Analysis
First look at the source code of ScrollController
.
|
|
Obviously, the offest
of the ScrollController
is obtained from the position
, which is derived from the variable _positions
.
The StateError
error is thrown by the _positions.single
line.
So the question is, why does this _positions
suddenly not have a drop left, and suddenly it gives too much? ˊ_>?
We still have to go back to the source code of ScrollController
to find out.
|
|
- Why there is no data (No element).
ScrollController
has notattach
aposition
yet. There are two reasons: one may be that it hasn’t been mounted to the tree (not used byScrollable
); the other is that it has beendetach
. 2. - Why too many elements.
ScrollController
hasattach
a new one before it has time todetach
the oldposition
. The reason for this is most likely that theScrollController
is being used incorrectly and is being followed by multipleScrollable
s at the same time.
Solution
For the No element error, just determine if _positions
is empty, i.e. hasClients
.
For the too many elements error, make sure that the ScrollController
is bound by only one Scrollable
, don’t let it split, and is properly dispose()
.
|
|
Typical mistake #4: Getting around null
The language Dart can be static or dynamic, and the type system is unique. Everything can be assigned the value null
, which often causes comrades who are used to writing Java code to get dizzy because bool
, int
, double
, which seem to be “primitive” types, are attached by null
.
Typical error message.
Failed assertion: boolean expression must not be null
NoSuchMethodError: The method '>' was called on null.
NoSuchMethodError: The method '+' was called on null.
NoSuchMethodError: The method '*' was called on null.
Sample code
This error occurs more often when using data mods returned by the server.
|
|
Cause analysis
StyleItem.fromJson()
is not fault-tolerant to the data and should assume that the values in the map are likely to be null
.
Solution: Error tolerance
Be sure to get used to Dart’s type system, anything can be null
, for example, the following code, you can think of several possible errors.
|
|
As a tip, onDone()
can also be null
.
Pay special attention when passing data with the native MethodChannel
, and be careful.
Typical error 5: The dynamic in generic is not dynamic at all
Typical error message.
type 'List<dynamic>' is not a subtype of type 'List<int>'
type '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'Map<String, String>'
Often occurs when assigning a value to a List, Map variable.
Sample code
This error also occurs more often when using the data mod returned by the server.
Reason analysis
The generic type of the map converted by jsonDecode()
method is Map<String, dynamic>
, meaning that value may be of any type (dynamic), and when value is a container type, it is actually List<dynamic>
or Map<dynamic, dynamic>
, etc.
In Dart’s type system, although dynamic
can represent all types and can be automatically converted if the data types in fact match (runtime type is equal) when assigning values, the generic dynamic
is not automatically convertible in generic types. Think of List<dynamic>
and List<int>
as two run-time types.
Solution: use List.from, Map.from
Summary
To sum up, these typical errors are not troublesome, but caused by not understanding or not familiar with Flutter and Dart language, the key is to learn to handle fault tolerance.