Flutter填坑指南,总有一个遇得到, 持续填坑中...

8,784 阅读3分钟

1、Waiting for another flutter command to release the startup lock...

打开新的项目或者使用Flutter Packages get时出现: Waiting for another flutter command to release the startup lock...

解决方案:

先打开任务管理器,结束掉所有dart.exe即可,如果依然提示就打开你的flutter安装文件夹,找到\bin\cache中的lockfile文件删除。之后重启项目。

2、The Gradle failure may have been because of AndroidX incompatibilities in this Flutter app

Android dependency 'androidx.core:core' has different version for the compile (1.0.0) and runtime (1.0.1) classpath. You should manually set the same version via DependencyResolution

解决方案一:

设法通过添加这样的代码片段来修复错误  
在项目 android > build.gradle 文件中  buildscript { }  中添加此代码片段
 subprojects {
    project.configurations.all {
        resolutionStrategy.eachDependency { details ->
            if (details.requested.group == 'com.android.support'
                    && !details.requested.name.contains('multidex') ) {
                details.useVersion "27.1.1"
            }
            if (details.requested.group == 'androidx.core'
                    && !details.requested.name.contains('androidx') ) {
                details.useVersion "1.0.1"
            }
        }
    }
}

解决方案二:

1.android/gradle/wrapper/gradle-wrapper.properties里面
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip

2.android/build.gradle
dependencies { classpath 'com.android.tools.build:gradle:3.3.0' }

3.android/gradle.properties
加入
android.enableJetifier=true
android.useAndroidX=true

4.android/app/build.gradle 修改版本号:
make sure compileSdkVersion and targetSdkVersion are at least 28.
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

5.android/app/build.gradle /defaultConfig加上
multiDexEnabled true

3、Error connecting to the service protocol: HttpException: Connection closed before full header was...

最后是使用安卓系统版本是9.1 Android版本太高,换8.1就好了

查看 Android Studio Sdk Manager 版本 

查看项目App目录下 compileSdkVersion

4、type 'List' is not a subtype of type 'List'

// 这里的问题是类型推断以意想不到的方式失败。解决方案是为map方法提供类型参数
Wrap(
  children:item['casts'].map<Widget>((casts){
  return Text('data');
  }).toList(),
)

更复杂的答案是,虽然类型children是List<Widget>,该信息不会回流到map调用。这可能是因为map后面是toList因为没有办法输入注释闭包的返回。

5、chewie插件在ios下播放失败的问题

<!-- 在项目根目录 > ios > Runner > info.plist 文件中  
      <dict>尾部
      添加视频播放需要的nsapp传输安全的key 
-->

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <true/>
</dict>

6、snackBar报错的问题无法弹出,Scaffold.of() called with a context that does not contain a Scaffold.

// 定义一个GlobarKey
GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey();
   
  // 
return new Scaffold(
    key: _scaffoldkey,
    ........
    ),
    .......
)
  
// 通过key调用snackbar
onPressed: (){
  _scaffoldKey.currentState.showSnackBar(
      SnackBar(
        content: Text("这是一个SnackBar"),
        duration: Duration(
            milliseconds: 1
        ),
        action: SnackBarAction(
            label: "别点我",
            onPressed: () {
              print("SnackBar上面的按钮被点击了");
            }
        ),
      )
    );                
  },
)

7、脚手架sliver、scaffold中设置appbar、tabbar的高度或者背景色

// 尤其是AppBar和TabBar,如果不希望被限制size,需要提供preferredSize或默认值
// 想要设置高度或者背景色,在AppBar外包一层PreferredSize,设置preferredSize的属性为想要的高度即可。
SliverPersistentHeader(
  pinned: true,
  delegate: StickyTabBarDelegate(
    child:PreferredSize(
      preferredSize: Size.fromHeight(40),
      child: Material(
        color: Colors.grey[200],
        child: TabBar(
          labelColor: Colors.black,
          controller: this._tabController,
          indicatorColor: Colors.black,
          tabs: <Widget>[
            Tab(text: '评论'),
            Tab(text: '话题区'),
          ],
      ),
      ),
    ),
  ),
),

8、去掉状态栏代码实现

Flutter做屏效果显示的时候,调用SystemChrome.setEnabledSystemUIOverlays([]); 这个方法把状态栏和虚拟按键隐藏掉

跳转到其他页面后需要调用把状态栏显示出来,调用SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.top]);

需要一起调用底部虚拟按键(华为系列某些手机有虚拟按键),则SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.top, SystemUiOverlay.bottom]);

9、Image.File 加载图像时文件内容变化但图像不变

/* 在Flutter中,我们可以用下面的代码从文件中加载图像:

Image.file(File(_fileName));

这个时候,当_fileName这个文件名称和路径不变,文件内容变化时,Flutter并不会更新显示。问题产生的原因是Flutter自动使用了缓存。

Image.file 实际上会将 image 设置为 FileImage 这个 ImageProvider。FileImage 的代码中,在进行 operator 时,只判断了文件路径和缩放比例。正是因为如此,我们文件路径不变,缩放比例不变时,Flutter会认为我们还是用的原图,并不会重新进行加载。

于是,我想到了办法是扩展一个FileImage,将这个 operator 的方式改一下。 */

新创建一个类

class FileImageEx extends FileImage {
  int fileSize;
  FileImageEx(File file, { double scale = 1.0 })
      : assert(file != null),
        assert(scale != null),
        super(file, scale: scale) {
    fileSize = file.lengthSync();
  }

  @override
  bool operator ==(dynamic other) {
    if (other.runtimeType != runtimeType)
      return false;
    final FileImageEx typedOther = other;
    return file?.path == typedOther.file?.path
        && scale == typedOther.scale && fileSize == typedOther.fileSize;
  }

}

接下来,直接使用下面的代码来加载图像:

Image(image: FileImageEx(File(_fileName)));

10、Inkell 去掉水波纹的方法

// 设置highlightColor为透明,同时设置radius为0

InkWell(
  highlightColor:Colors.transparent,
  radius: 0.0,
  onTap: (){
  },
  child: Text('跳过 ${_time}s'),
),

11、打包release版本安卓apk包真机无法请求网络

// 原因:安卓开发中flutter应用没有网络权限

在项目目录android\app\src\profile\AndroidManifest.xml   manifest 里添加这段代码

<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />


在项目目录 android/src/main/AndroidManifest.xml 里也有一个 AndroidManifest.xml文件!跟之前的只不过是文件夹位置不同而已,同样在manifest标签下加入相同配置就行了,不要放到application里

<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />


重新打包即可

12、setstate() called after dispose() ,setstate导致的内存泄漏

// flutter端请求网络时,调用的是宿主App的网络请求。
// flutter通过消息通道发送一个消息,然后await等待消息返回,最终宿主app会调用reply.reply(obj)方法返回数据。
// 如果在这个过程中,flutter页面关闭,就会出现如下异常,类似Android中的内存泄漏。

/* setState() called after dispose(): _DetailCommentsState#5c3a1(lifecycle state: defunct, not mounted)
I/flutter ( 4677): 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.
I/flutter ( 4677): 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.
I/flutter ( 4677): 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(). */


// 我们的错误原因是异步消息未返回,所以在setState方法之前调用mouted属性进行判断即可。具体示例如下:

if(mounted){
  setState(() {
    _movie = res.data; 
    _themeColor = paletteGenerator.colors.toList()[1];
    _detailThemeColor = paletteGenerator.colors.toList()[0];
  });
}

13、xCode打包提示:Could not find included file 'Generated.xcconfig' in search paths (in target 'Runner')


// 尝试执行  flutter build ios  然后重新启动Xcode

14、更新showDialog以及showModalBottomSheet中的状态中的内容

// 很多人在用showDialog的时候应该都遇到过这个问题,使用showDialog后,通过setState()无法更新当前dialog。其实原因很简单,因为dialog其实是另一个页面,准确地来说是另一个路由,因为dialog的关闭也是通过navigator来pop的,所以它的地位跟你当前主页面一样。这个概念一定要明确,因为无论在Android或iOS中,daliog都是依附于当前主页面的一个控件,但是在Flutter中不同,它是一个新的路由。所以使用当前主页面的setState()来更新,当然没法达到你要的效果。

/* StatefulBuilder
很多人使用StatefulBuilder依然达不到更新的效果,是因为你用错了setState()方法。
就像我们上面说的那样,这个builder构建的控件,不会响应老页面的任何操作,因为它们是两个互不影响的路由控制的。 */

// 1、更新showDialog
showDialog(
  context: context,
  builder: (context) {
     String label = 'test';
     return StatefulBuilder(
      builder: (context, state) {
          print('label = $label');
          return GestureDetector(
              child: Text(label),
              onTap: () {
                  label = 'test8';
                  print('onTap:label = $label');
                  // 注意不是调用老页面的setState,而是要调用builder中的setState。
                  //在这里为了区分,在构建builder的时候将setState方法命名为了state。
                  state(() {});  
              },
          );
      },
    );
  }
);

// 2、更新showModalBottomSheet
showModalBottomSheet(context:context, builder:(BuildContext context){
    return StatefulBuilder(
    builder:(context1, state) {///这里的state就是setState
      return Container(
        child:OutlineButton(
          onPressed: (){
            state(() {///为了区分把setState改个名字
              btnState=!btnState;
            });
          },
          child:Stack(
            children: <Widget>[
              Opacity(
                opacity: btnState ? 0.0 : 1.0,
                child: Text("aa"),
              ),
              Opacity(
                opacity: btnState ? 1.0 : 0.0,
                child: Text("bb"),
              ) 
            ],
          ),
        ),
      ),
    }
  )
})

15、SliverPersistentHeader 组件内状态更新,但UI未更新

SliverPersistentHeader(
  pinned: true,
  delegate: SliverBarDelegate(
    PreferredSize(
      preferredSize: Size.fromHeight(ScreenAdapter.height(80)),
      child:_headActions()
    ),
  ),
),

// SliverPersistentHeader   delegate的是重写的SliverBarDelegate
class SliverBarDelegate extends SliverPersistentHeaderDelegate {
  final  widget;

  SliverBarDelegate(this.widget);

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    return Container(
      child: widget,
    );
  }
  // 是否需要重新构建  
  // 如果传递的这几个参数变化了,那就重写创建
  // 如果返回false,则可能不会重新构建报头,即使委托的实例发生了变化。
  // 源代码中有描述到



  @override
  bool shouldRebuild(SliverBarDelegate oldDelegate) {
    return true;
  }

  @override
  double get maxExtent => widget.preferredSize.height;

  @override
  double get minExtent => widget.preferredSize.height;
}

16、packages get 慢的解决方案

/* 
  国内使用 flutter packages get 命令,一直是  This is taking an unexpectedly long time 状态

  科学上网无效

  windows解决方案:配置 【环境变量】 > 【用户变量】:
  
  变量名:PUB_HOSTED_URL  值:https://pub.flutter-io.cn

  变量名:FLUTTER_STORAGE_BASE_URL  值:https://storage.flutter-io.cn

  最好重启下windows电脑,flutter packages get 执行

*/

具体环境变量的值 需要看该网址 [Using Flutter in China](https://flutter.dev/community/china)

17、如何将String类型的颜色转化为Color所需要的int类型的颜色

  // 完整的颜色是8位,如果是RGB是6位颜色值可以直接添加
  Color(int.parse('0xff'+color)

18、is a SingleTickerProviderStateMixin but multiple tickers were created.

// 报错日志:
I/flutter ( 4025): A SingleTickerProviderStateMixin can only be used as a TickerProvider once. If a State is used for
I/flutter ( 4025): multiple AnimationController objects, or if it is passed to other objects and those objects might
I/flutter ( 4025): use it more than one time in total, then instead of mixing in a SingleTickerProviderStateMixin, use
I/flutter ( 4025): a regular TickerProviderStateMixin.

大概翻译意思:单个TickerProviderStateMixin,但创建了多个Ticker,单个statemixin 只能供一个TickerProvider使用,如果一个状态用于多个对象,它可能被传递给其他对象。

// 解决方案:将SingleTickerProviderStateMixin换成TickerProviderStateMixin

19、动画释放报错

// 报错日志:
e following assertion was thrown while finalizing the widget tree:
I/flutter ( 3776): _CustomScrollFooterState#3df1f(ticker active) was disposed with an active Ticker.
I/flutter ( 3776): _CustomScrollFooterState created a Ticker via its SingleTickerProviderStateMixin, but at the time
I/flutter ( 3776): dispose() was called on the mixin, that Ticker was still active. The Ticker must be disposed before
I/flutter ( 3776): calling super.dispose(). Tickers used by AnimationControllers should be disposed by calling
I/flutter ( 3776): dispose() on the AnimationController itself. Otherwise, the ticker will leak.
I/flutter ( 3776): The offending ticker was: Ticker(created by _CustomScrollFooterState#3df1f(lifecycle state:
I/flutter ( 3776): created))

// 解决方案:
controller.dispose()放在了 super.dispose()的后面:
@override
void dispose() {
//先调用controller.dispose释放了动画资源,再调用super
  controller.dispose();
  super.dispose();
}

20、Could not resolve all files for configuration ':app:lintClassPath'.

// 报错日志:
* What went wrong:
Execution failed for task ':app:lintVitalRelease'.
> Could not resolve all files for configuration ':app:lintClassPath'.
   > Could not download groovy-all.jar (org.codehaus.groovy:groovy-all:2.4.12)
      > Could not get resource 'https://jcenter.bintray.com/org/codehaus/groovy/groovy-all/2.4.12/groovy-all-2.4.12.jar'.
         > Could not GET 'https://jcenter.bintray.com/org/codehaus/groovy/groovy-all/2.4.12/groovy-all-2.4.12.jar'.
            > Remote host closed connection during handshake


// 解决方案:
在app的build.gradle中的android部分添加如下代码块即可

lintOptions {
      checkReleaseBuilds false
      abortOnError false
}

21、设置沉浸式状态栏,取消android状态栏阴影

if (Platform.isAndroid) {
  // 以下两行 设置android状态栏为透明的沉浸。写在组件渲染之后,是为了在渲染后进行set赋值,覆盖状态栏,写在渲染之前MaterialApp组件会覆盖掉这个值。
  SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle(statusBarColor: Colors.transparent);
  SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
}

22、更改文件名或删除名称时报错,were declared as an inputs, but did not exist. Check the definition of target:kernel_snapshot for errors

// 报错日志
C:\Users\Admin\Desktop\flutter_jahn_douban\lib\pages\tabs\book_movie\movie\movieTop\movieTopAll\movie_top_all.dart, C:\Users\Admin\Desktop\flutter_jahn_douban\lib\pages\tabs\book_movie\movie\movieTop\movieTopAll\movie.dart were declared as an inputs, but did not exist. Check the definition of target:kernel_snapshot for errors
#0      Node.computeChanges (package:flutter_tools/src/build_system/build_system.dart:777:7)
<asynchronous suspension>
#1      _BuildInstance._invokeInternal (package:flutter_tools/src/build_system/build_system.dart:517:20)
<asynchronous suspension>
#2      _BuildInstance.invokeTarget.<anonymous closure> (package:flutter_tools/src/build_system/build_system.dart:481:35)

// 解决方案:
删除.dart_tool文件夹,然后重新运行即可。

23、修改版本号不生效

FlutterApp版本号设置在pubspec.yaml中,+号前面是版本名称,后面是版本号,在此修改会自动应用到AndroidIOS项目对应版本号中,修改完安装发现并未生效,解决方法:

// 解决方案:
// 修改后执行
1、flutter get
2、flutter clean
重新 build ios 安装就能生效了

24、Fluro中传递中文参数失败

/*  The following ArgumentError was thrown while handling a gesture:
I/flutter ( 6034): Invalid argument(s): Illegal percent encoding in URI
I/flutter ( 6034): When the exception was thrown, this was the stack:
I/flutter ( 6034): #0      _Uri._uriDecode (dart:core/uri.dart:2951:11)
I/flutter ( 6034): #1      Uri.decodeComponent (dart:core/uri.dart:1120:17) */

无效的参数:URI中的非法编码百分比

// 解决方案:

通过Uri.encodeComponent(Text)转化

25、TextField组件,报错解决:The following assertion was thrown while handling a gesture

/* 在没有正常的取消输入框焦点的时候,就先清空输入框组件,整体渲染顺序是不正确的。 */

// 解决方案:
// 保证在组件build的第一帧时才去触发取消清空内容
WidgetsBinding.instance.addPostFrameCallback((_) => controller.clear());

26、TabController的监听在点击Tab后会执行两次

/* 每点击一次,监听都有两次回调 */

// 解决方案:
// 对indexIsChanging进行判断
_tabController.addListener((){
   if(_tabController.indexIsChanging){

   }
});  	

27、判断页面渲染完毕,获取指定元素的位置和大小

/* 在flutter中,我们获取大小也必须在元素渲染完成的时候才行,而有些应用场景要求在第一时间获取到这个元素的大小。那么怎么在第一时间判断元素渲染完成呢?flutter中的WidgetsBuiding这个类就可以用来判断: */

/* Schedule a callback for the end of this frame.
This callback is run during a frame, just after the persistent frame callbacks (which is when the main rendering pipeline has been flushed).
Post-frame callbacks cannot be unregistered. They are called exactly once.
 */

/*
通过 key 去获取到控件对象的 BuildContext,而我们也知道 BuildContext 的实现其实是 Element,而Element持有 RenderObject 。So,我们知道的 RenderObject ,实际上获取到的就是 RenderBox ,那么通过 RenderBox 我们就只大小和位置了。
*/
 @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback(_onAfterRendering);
  }
  // 渲染完成之后获取某个元素的大小
  void _onAfterRendering(Duration timeStamp){
        RenderBox renderBox = _notUsed.currentContext.findRenderObject();
	// 大小
        double width = renderBox.size.width;  
        double height = renderBox.size.height;  
	// 位置
        double x = renderBox.localToGlobal(Offset.zero).dx;
        setState(() {
          _currentHeight = height;
          _currentWidth = width;
          _currentLeft = x;
          _currentIndex = 1;
        });
  }

28、Cannot fit requested classes in a single dex file.

/* 
Cannot fit requested classes in a single dex file. Try supplying a main-dex list Try supplying a main-dex list. # methods: 66657 >66657 > 65536
大致意思是Android App中的方法数超过65535时,如果往下兼容到低版本设备时,就会报编译错误:如下图:
 */

// 解决方案:
在app > build.gradle文件的defaultConfig默认配置里面增加:
	multiDexEnabled true
dependencies里面添加
	implementation 'com.android.support:multidex:1.0.3'

29、A failure occurred while executing com.android.build.gradle.tasks.MergeResources$FileGenerationWorkA

QQ截图20200501011905.png

// 解决方案:
在app > build.gradle文件的defaultConfig默认配置里面添加:
	vectorDrawables.useSupportLibrary = true