Following up on some Tips from my previous post on the development of Flutter, today I will share some of the problems I encountered. This post is more detailed than the previous post. I hope that “the devil is in the details”. All of the examples in this article are in my open source Flutter_deer. Hope Star, Fork support, there are problems can Issue. Attached link: github.com/simplezhli/…
1. setState() called after dispose()
I stumbled across this in the console, and the complete error message is as follows:
Unhandled Exception: setState() called after dispose(): _AboutState#9c33a(lifecycle state: defunct, not mounted)
Of course Flutter also gives the cause of the problem and the solution behind the error message:
This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback. The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the “mounted” property of this object before calling setState() to ensure the object is still in the tree. This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().
This error occurs when the widget was disposed of during the Dispose method, but the setState method was called after that. This error occurs when, for example, a timer or animation callback calls setState() but the page is closed. This error does not usually crash the program, but causes a memory leak.
So the solution is divided into two parts:
- Stop or destroy a listener in time, such as a timer:
Timer _countdownTimer;
@override
void dispose(a) { _countdownTimer? .cancel(); _countdownTimer =null;
super.dispose();
}
Copy the code
- We need to call it again just to be sure
setState()
Before judging whether the current page exists:
_countdownTimer = Timer.periodic(Duration(seconds: 2), (timer) {
if(mounted){ setState(() { }); }});Copy the code
What is Mounted in the source code
BuildContext get context => _element;
StatefulElement _element;
/// Whether this [State] object is currently in a tree.
///
/// After creating a [State] object and before calling [initState], the
/// framework "mounts" the [State] object by associating it with a
/// [BuildContext]. The [State] object remains mounted until the framework
/// calls [dispose], after which time the framework will never ask the [State]
/// object to [build] again.
///
/// It is an error to call [setState] unless [mounted] is true.bool get mounted => _element ! =null;
Copy the code
BuildContext is an abstract class of Element, so you can think of Mounted as whether or not the context exists. When you use context in a callback, you also need to determine mounted. For example, we want to pop up a Dialog, or exit the current page when the request interface succeeds. The concept of BuildContext is an important one to master. Using it incorrectly will generally not crash, but will invalidate the code.
This problem detailed code see: click to view
2. Close the listener Dialog
Problem description: I will pop up a loading prompt before each interface request, and close it when the interface request succeeds or fails. However, if we manually close it by clicking the return key in the request, when the actual request succeeds or fails to close it, we mistakenly close the current page because we called navigator-pop (context).
The breakthrough is to know when the Dialog is closed, so you can use WillPopScope to intercept the return key input and record the Dialog closing.
bool _isShowDialog = false;
void closeDialog(a) {
if (mounted && _isShowDialog){
_isShowDialog = false; Navigator.pop(context); }}void showDialog(a) {
/// Avoid repeated pop-ups
if(mounted && ! _isShowDialog){ _isShowDialog =true;
showDialog(
context: context,
barrierDismissible: false,
builder:(_) {
return WillPopScope(
onWillPop: () async {
// Intercept the return key to prove that dialog was manually shut down
_isShowDialog = false;
return Future.value(true);
},
child: ProgressDialog(hintText: "Loading...")); }); }}Copy the code
This problem detailed code see: click to view
3.addPostFrameCallback
The addPostFrameCallback callback method is triggered when the Widget is rendered, so it is usually used to get the size and location of the Widget on the page.
Before the second point, I mentioned that I would pop loading before the interface request. If I put the request method in the initState method, the exception is as follows:
inheritFromWidgetOfExactType(_InheritedTheme) or inheritFromElement() was called before initState() completed. When an inherited widget changes, for example if the value of Theme.of() changes, its dependent widgets are rebuilt. If the dependent widget’s reference to the inherited widget is in a constructor or an initState() method, then the rebuilt dependent widget will not reflect the changes in the inherited widget. Typically references to inherited widgets should occur in widget build() methods. Alternatively, initialization based on inherited widgets can be placed in the didChangeDependencies method, which is called after initState and whenever the dependencies change thereafter.
Reason: pop up a DIalog showDialog method will be called the Theme, of (the context, shadowThemeOnly: true), and this method will get Theme object through inheritFromWidgetOfExactType come across components.
InheritFromWidgetOfExactType method call inheritFromElement:
However, when _StateLifecycle is created and defunct, data cannot be obtained across components, that is, when initState() and after Dispose (). So the error message tells us to call didChangeDependencies.
However, after didChangeDependencies, the new exception:
setState() or markNeedsBuild() called during build. This Overlay widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
Note that the page cannot call setState() or markNeedsBuild() methods at build time. So we need to create the new component (Dialog in this case) after the build is complete.
So the solution is to use the addPostFrameCallback callback method and wait for the page to finish building before requesting the data:
@override
void initState(a) {
WidgetsBinding.instance.addPostFrameCallback((_){
/// Interface request
});
}
Copy the code
There are many scenarios that lead to this kind of problem, but the general solution is the above method.
This problem detailed code see: click to view
4. Delete the emoji
No more beeps, just look at the picture:
Simply put, it takes two clicks to delete an emoji. Encounter individual emoji, need to delete 11 times!! The problem, not to joke about the Flutter, is that the basic emoji is more or less problematic on all platforms.
Here’s why:
1.5.4 + hotfix. 2
Github.com/flutter/eng…
Fortunately, this issue has been fixed in the latest stable release 1.7.8+ HotFix.3. Unfortunately, I found other issues, such as the occasional crash when DELETING text on my Mi MIX 2S, but other models worked fine. The exception is as follows:
I also found the same problem in the Issue of Flutter. For details, please pay attention to this Issue :github.com/flutter/flu… According to the Folks at the Flutter team, it is unlikely that this problem will be fixed in the stable version 1.7.
Therefore, it is advisable to upgrade with caution, especially for production environments. The issue will have to be put on hold until a more stable version is released…
Update 19.07.20, with the official release of 1.7.8+ HotFix.4, fixes this issue. After testing and fixing the problem, you can rest assured to use.
5. The keyboard
1. Whether to bounce
MediaQuery.of(context).viewInsets.bottom > 0
Copy the code
ViewInsets. Bottom is the height of the top of the keyboard from the bottom, which is the height of the keyboard when it bounces. If you want to go to the keyboard’s eject state in real time, use didChangeMetrics. Here is the full version:
import 'package:flutter/material.dart';
typedef KeyboardShowCallback = void Function(bool isKeyboardShowing);
class KeyboardDetector extends StatefulWidget {
KeyboardShowCallback keyboardShowCallback;
Widget content;
KeyboardDetector({this.keyboardShowCallback, @required this.content});
@override
_KeyboardDetectorState createState(a) => _KeyboardDetectorState();
}
class _KeyboardDetectorState extends State<KeyboardDetector>
with WidgetsBindingObserver {
@override
void initState(a) {
WidgetsBinding.instance.addObserver(this);
super.initState();
}
@override
void didChangeMetrics(a) {
super.didChangeMetrics(); WidgetsBinding.instance.addPostFrameCallback((_) { print(MediaQuery.of(context).viewInsets.bottom); setState(() { widget.keyboardShowCallback ? .call(MediaQuery.of(context).viewInsets.bottom >0);
});
});
}
@override
void dispose(a) {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
Widget build(BuildContext context) {
returnwidget.content; }} code from project GSYFlutterDemo: HTTPS://github.com/CarGuo/GSYFlutterDemo
Copy the code
2. Pop up the keyboard
if (MediaQuery.of(context).viewInsets.bottom == 0) {final focusScope = FocusScope.of(context);
focusScope.requestFocus(FocusNode());
Future.delayed(Duration.zero, () => focusScope.requestFocus(_focusNode));
}
Copy the code
_focusNode is the focusNode attribute of the corresponding TextField.
3. Close the keyboard
FocusScope.of(context).requestFocus(FocusNode());
/ / / 1.7.8 began to recommend the following way (https://github.com/flutter/flutter/issues/7247)
FocusScope.of(context).unfocus();
Copy the code
I’m going to mention close here, because normally when the keyboard pops up, you hit back and close the page, the keyboard automatically collapses. But in this order:
Page closed –> Keyboard closed
This will cause the keyboard to appear on your previous page for a short time, which will lead to a brief widget overflow (see section 1 on overflow).
So you need to manually call the code to close the keyboard before the page closes. Deactivate or Dispose, but the context is null, so intercept the return key:
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
// Intercept the return key
FocusScope.of(context).unfocus();
return Future.value(true);
},
child: Container()
);
}
Copy the code
Of course, if you are returning from your own code call, you can close the keyboard before returning to the page:
FocusScope.of(context).unfocus();
Navigator.pop(context);
Copy the code
This problem detailed code see: click to view
6. The Android 9.0 adapter
For all new Flutter projects, the Android targetSdkVersion default is 28. So it is inevitable that Android 9.0 ADAPTS or even 6, 7 and 8 ADAPTS, and one of the problems I encountered is that the access to Audev.net 2D map does not show up on the 9.0 machine.
The main reason for the problem is that Android 9.0 requires encrypted connections by default, which simply means that HTTP requests are not allowed and HTTPS is required. Autonavi’s 2D map SDK is suspected to use HTTP request, so it will not be loaded.
There are two solutions:
-
Change targetSdkVersion below 28 (not recommended in the long run)
-
Android -> app -> SRC -> main -> res add network_security_config. XML file:
<?xml version="1.0" encoding="utf-8"? >
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
Copy the code
Androidmanifest.xml application:
android:networkSecurityConfig="@xml/network_security_config"
Copy the code
This issue is only a small part of the Android adaptation, and there are adaptation issues in iOS as well. For example, common permission adaptation.
It must be said that the development of Flutter requires some understanding of native development. This was especially true when I was writing the map plugin for Flutter, so I was already working on Android, so the Android part was done very quickly. The iOS part is very difficult, first of all, the OC grammar is not good, and secondly, to tell the truth, I am not sure when I have finished writing, so I still need to consult my iOS colleagues to make sure. So the emergence of cross-platform solutions does not have an impact on native development, but rather puts forward higher requirements for native development.
This problem detailed code see: click to view
7. Other
-
Json parsing was a hassle in the development of Flutter, and of course there are many plug-ins to solve our problems. I personally recommend using FlutterJsonBeanFactory. A series of use can see about it: www.jianshu.com/nb/33360539
-
UI level functionality is best addressed with Flutter. For example, for the Toast function, a lot of people choose flutterToast, and I recommend solutions like OkToast that use Flutter. Because FlutterToast calls the native Android Toast, the style of flutterToast is not unified on different systems. At the same time, the notification permission is limited on some systems, so the Toast cannot be displayed.
Space is limited, so share the Tips above and like them if they help you! In fact, it is not more convenient to find when encountering problems later 🤔.
Github: github.com/simplezhli/…