当前位置:网站首页>Flutter's most common mistakes

Flutter's most common mistakes

2021-01-09 10:26:14 osc_ tb68dlqx

Bily Bily comics APP practice Flutter It's been more than half a year , I analyze the errors collected online , I picked out some common mistakes , Listed in this article , Available for practice Flutter As a reference for beginners .

Typical mistake one : Can't grasp Future

Typical error messages :NoSuchMethodError: The method 'markNeedsBuild' was called on null.

This error often occurs in asynchronous tasks (Future) Handle , For example, a page requests a network API data , Refresh... Based on data Widget State.

The asynchronous task ends on the page pop after , But there was no inspection State Is it still  mounted, Continue to call  setState  It's a mistake .

Sample code

A very common code to get network data , call  requestApi(), wait for Future Get from response, , in turn, setState Refresh Widget:

1
2
3
4
5
6
7
8
9
10
class AWidgetState extends State<AWidget> {
  // ...
  var data;
  void loadData() async {
    var response = await requestApi(...);
    setState((){
      this.data = response.data;
    })
  }
}

Cause analysis

response  The acquisition of is async-await Asynchronous task , It's quite possible that AWidgetState By  dispose Then wait until you return , It's time to State  The binding of  Element  It's gone . Therefore, setState You need to be fault tolerant .

terms of settlement : setState Check before that if  mounted

1
2
3
4
5
6
7
8
9
10
11
12
class AWidgetState extends State {
  // ...
  var data;
  void loadData() async {
    var response = await requestApi(...);
    if (mounted) {
      setState((){
        this.data = response.data;
      })
    }
  }
}

This mounted It's important to check , In fact, as long as asynchronous is involved, there are various callbacks (callback), Don't forget to check the value .

such as , stay  FrameCallback To perform an animation (AnimationController):

1
2
3
4
5
6
@override
void initState(){
  WidgetsBinding.instance.addPostFrameCallback((_) {
    if (mounted) _animationController.forward();
  });
}

AnimationController It is possible to follow State Together  dispose 了 , however FrameCallback It will still be implemented , And then lead to the abnormality .

And such as , Do something in the callback of animation monitoring :

1
2
3
4
5
6
7
8
@override
void initState(){
  _animationController.animation.addListener(_handleAnimationTick);
}

void _handleAnimationTick() {
  if (mounted) updateWidget(...);
}

The same in _handleAnimationTick Before being recalled ,State It may have been dispose 了 .

If you don't understand why , Please have a careful aftertaste Event loop  And review Dart Thread model of .

Typical mistake two :Navigator.of(context) It's a null

Typical error messages :NoSuchMethodError: The method 'pop' was called on null.

Often in  showDialog  post-processing dialog Of pop() appear .

Sample code

Get network data in some way , In order to better prompt users , I'll play one first loading window , Then perform other operations according to the data …

1
2
3
4
5
6
7
8
9
10
11
12
13
// show loading dialog on request data
showDialog<void>(
  context: context,
  barrierDismissible: false,
  builder: (_) {
    return Center(
      child: CircularIndicator(),
    );
  },
);
var data = (await requestApi(...)).data;
// got it, pop dialog
Navigator.of(context).pop();

Cause analysis :

The reason for the mistake is —— Android Native Return key : Although the code specifies barrierDismissible: false, Users cannot close the pop-up window by clicking the translucent area , But when the user clicks Return key when ,Flutter The engine code calls  NavigationChannel.popRoute(), Finally, this loading dialog Even the page was turned off , Leading to Navigator.of(context) The return is null, Because it's time to context Has been unmount, We can't find its root from a withered leaf , So the mistake comes up .

in addition , In code Navigator.of(context)  Used context Not quite right , It actually belongs to showDialog The caller's not dialog all , In theory, we should use builder It came from inside context, I can find roots along the wrong tree trunk , But that's not the case , Especially when your APP Are there in Navigator When nesting, you should pay more attention to .

terms of settlement

First , Make sure  Navigator.of(context)  Of  context  yes dialog Of context;
secondly , stay Dialog Widget The outer layer can be wrapped with a  WillPopScope  Handle the return key , Or to be on the safe side  null, In case of being manually closed .

showDialog  Time passes in  GlobalKey, adopt  GlobalKey To get the right context.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
GlobalKey key = GlobalKey();

showDialog<void>(
  context: context,
  barrierDismissible: false,
  builder: (_) {
     // Disallow pop by 'back' button when barrierDismissible is false
    return WillPopScope(
      onWillPop: () => Future.value(false),
      child: KeyedSubtree(
        key: key,
        child: Center(
          child: CircularIndicator(),
        )
      )
    );
  },
);
var data = (await requestApi(...)).data;

if (key.currentContext != null) {
  Navigator.of(key.currentContext)?.pop();
}

key.currentContext  by null It means to be dialog Has been dispose, That is to say, it has been from WidgetTree in unmount.

Actually , Allied XXX.of(context) Method in Flutter It's common in code , such as  MediaQuery.of(context)Theme.of(context)DefaultTextStyle.of(context),DefaultAssetBundle.of(context) wait , Pay attention to the incoming context It's from the right node , Otherwise there will be surprises waiting for you .

Write Flutter Code , Be sure to be right in your mind context There is a clear understanding of the trunk vein of , If you don't quite understand context, You can see  《 In depth understanding of BuildContext》 - Vadaski.

Typical mistake three :ScrollController Schrodinger's position

In obtaining ScrollController Of positionoffset, Or call jumpTo() Other methods , Often StateError error .

error message :StateError Bad state: Too many elements,StateError Bad state: No element

Sample code

After a button is clicked , adopt ScrollController  control ListView Scroll to start :

1
2
3
4
5
final ScrollController _primaryScrollController = ScrollController();
//  Back to the beginning 
void _handleTap() {
  if(_primaryScrollController.offset > 0) _primaryScrollController.jumpTo(0.0)
}

Cause analysis

First look at ScrollController Source code :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ScrollController extends ChangeNotifier {
  //...
  @protected
  Iterable<ScrollPosition> get positions => _positions;
  final List<ScrollPosition> _positions = <ScrollPosition>[];
  
  double get offset => position.pixels;
  
  ScrollPosition get position {
    assert(_positions.isNotEmpty, 'ScrollController not attached to any scroll views.');
    assert(_positions.length == 1, 'ScrollController attached to multiple scroll views.');
    return _positions.single;
  }
  //...
}

Obviously ,ScrollController  Of  offest  It's from  position  gain , and position  It's from variables  _positions.

StateError error , Namely _positions.single  This line throws :

1
2
3
4
5
6
7
8
9
10
11
abstract class Iterable<E> {
  //...
  E get single {
    Iterator<E> it = iterator;
    if (!it.moveNext()) throw IterableElementError.noElement();
    E result = it.current;
    if (it.moveNext()) throw IterableElementError.tooMany();
    return result;
  }
//...
}

So here comes the question , This _positions  Why all of a sudden there is not a drop left , Suddenly I think it's given too much ?ˊ_>ˋ

Or go back to  ScrollController  Find the source code of .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ScrollController extends ChangeNotifier {
  // ...
  void attach(ScrollPosition position) {
    assert(!_positions.contains(position));
    _positions.add(position);
    position.addListener(notifyListeners);
  }

  void detach(ScrollPosition position) {
    assert(_positions.contains(position));
    position.removeListener(notifyListeners);
    _positions.remove(position);
  }
}
  1. Why there is no data (No element):
    ScrollController Not yet  attach  One  position. There are two reasons : One possibility is that it hasn't been  mount  To the tree ( Has not been Scrollable Use to ); The other is that it has been  detach 了 .

  2. Why more (Too many elements):
    ScrollController Not yet  detach old  position, Then again attach A new one . Most of the reasons are ScrollController The usage of is not right , By more than one at a time  Scrollable Pay attention to .

terms of settlement

in the light of No element error , Just judge  _positions Is it empty or not , namely hasClients.

1
2
3
4
5
final ScrollController _primaryScrollController = ScrollController();
//  Back to the beginning 
void _handleTap() {
  if(_primaryScrollController.hasClients && _primaryScrollController.offset > 0) _primaryScrollController.jumpTo(0.0)
}

in the light of Too many elements error , Make sure ScrollController There's only one  Scrollable binding , Don't let it split , And be right  dispose()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class WidgetState extends State {
  final ScrollController _primaryScrollController = ScrollController();

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      controller: _primaryScrollController,
      itemCount: _itemCount,
      itemBuilder: _buildItem,
    )
  }

  int get _itemCount => ...;
  Widget _buildItem(context, index) => ...;

  @override
  void dispose() {
    super.dispose();
    _primaryScrollController.dispose();
  }
}

Typical mistake four : Run around null

Dart This language can be static and moving , Type system It's also unique . Everything can be assigned null, That's why I'm used to writing Java Code comrades often because bool int double This looks like ”primitive” The type of being null Attach body and dizzy .

Typical error messages :

  • 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

Such mistake , It is more common to use the data returned by the server model when .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class StyleItem {
  final String name;
  final int id;
  final bool hasNew;

  StyleItem.fromJson(Map<String, dynamic> json):
    this.name = json['name'],
    this.id = json['id'],
    this.hasNew = json['has_new'];
}

StyleItem item = StyleItem.fromJson(jsonDecode(...));

Widget build(StyleItem item) {
  if (item.hasNew && item.id > 0) {
    return Text(item.name);
  }
  return SizedBox.shrink();
}

Cause analysis

StyleItem.fromJson()  There is no fault tolerance for data , It should be considered that map Inside value It could be  null.

terms of settlement : Fault tolerance

1
2
3
4
5
6
7
8
9
10
class StyleItem {
  final String name;
  final int id;
  final bool hasNew;

  StyleItem.fromJson(Map<String, dynamic> json):
    this.name = json['name'],
    this.id = json['id'] ?? 0,
    this.hasNew = json['has_new'] ?? false;
}

Be used to Dart The type of system , Anything can be null, For example, the following code , There are several possible mistakes in your details :

1
2
3
4
5
6
7
8
9
10
11
12
13
class Test {
  double fraction(Rect boundsA, Rect boundsB) {
    double areaA = boundsA.width * boundsA.height;
    double areaB = boundsB.width * boundsB.height;
    return areaA / areaB;
  }
  
  void requestData(params, void onDone(data)) {
    _requestApi(params).then((response) => onDone(response.data));
  }
  
  Future<dynamic> _requestApi(params) => ...;
}

tip ,onDone() It can also be null >﹏<.

In and native use  MethodChannel Pay more attention to , Be careful to sail for thousands of years .

Typical mistake 5 : In generics dynamic Not at all dynamic

Typical error messages :

  • type 'List<dynamic>' is not a subtype of type 'List<int>'
  • type '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'Map<String, String>'

It often happens to give a List、Map When variables are assigned .

Sample code

Such mistake , It is also more common to use the data returned by the server model when .

1
2
3
4
5
6
7
8
9
10
11
class Model {
  final List<int> ids;
  final Map<String, String> ext;

  Model.fromJson(Map<String, dynamic> json):
    this.ids = json['ids'],
    this.ext= json['ext'];
}

var json = jsonDecode("""{"ids": [1,2,3], "ext": {"key": "value"}}""");
Model m = Model.fromJson(json);

Cause analysis

jsonDecode() This method translates into map The generics of are Map<String, dynamic>, Meaning for value It could be any kind of (dynamic), When value When it's a container type , It is actually List<dynamic> perhaps Map<dynamic, dynamic> wait .

and Dart In the type system , although dynamic Can represent all types of , When assigning value , If the data types actually match ( Runtime type equal ) Can be automatically converted , But in generics  dynamic  It can't be changed automatically . It can be said that  List<dynamic>  and  List<int> There are two kinds Runtime type .

terms of settlement : Use List.from, Map.from

1
2
3
4
5
6
7
8
class Model {
  final List<int> ids;
  final Map<String, String> ext;

  Model.fromJson(Map<String, dynamic> json):
    this.ids = List.from(json['ids'] ?? const []),
    this.ext= Map.from(json['ext'] ?? const {});
}

summary

in summary , These typical mistakes , It's not a problem , It's not understanding or familiar with Flutter and Dart The result of language , The key is to learn how to deal with fault tolerance .

版权声明
本文为[osc_ tb68dlqx]所创,转载请带上原文链接,感谢
https://chowdera.com/2021/01/20210109100451304R.html

随机推荐