今早正在愉快地使用Flutter开发客户端的时候,突然发现所有接口都使用不了了,觉得很奇怪,服务器上什么都没有动怎么突然不行了呢?

于是登录CentOS服务器查看Spring Boot服务的日志,发现了以下异常:

1
2
3
4
org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is java.lang.IllegalStateException: Unable to create the directory [/tmp/tomcat.1964230947136987004.8000] to use as the base directory
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:157)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:142)

哦,原来是Tomcat无法创建它的临时工作目录了。

于是就执行

1
df -h

查了一下服务器的硬盘信息:

1
2
3
4
5
6
7
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1 99G 94G 0 100% /
devtmpfs 7.8G 0 7.8G 0% /dev
tmpfs 7.8G 0 7.8G 0% /dev/shm
tmpfs 7.8G 532K 7.8G 1% /run
tmpfs 7.8G 0 7.8G 0% /sys/fs/cgroup
tmpfs 1.6G 0 1.6G 0% /run/user/1000

果然!根目录下使用率100%,硬盘被占满了!这是个严重的问题,不过该怎么排查哪些文件占用的空间呢?查了一番资料,发现使用下面命令可以一层层的跟踪排查:

1
du -x -m --max-depth 1 /

先从根目录开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@cscc log]# du -x -h -m --max-depth 1 / 
5 /tmp
4104 /root
668 /opt
1 /media
55960 /var
708 /WWW
1 /srv
38 /etc
2544 /home
27338 /data
1 /mnt
214 /boot
4115 /usr
1 /lost+found
95687 /

OK,发现/var目录占用最多,于是就怀疑是日志文件的问题,因为系统日志都在/var目录下。接着进入/var/log目录,执行命令确定一下:

1
ll -h

果然,全部都是日志文件占用的空间:

1
2
3
4
5
-rw-------  1 root    root            6.8G Oct 10 11:22 messages
-rw------- 1 root root 7.9G Sep 15 03:33 messages-20190915
-rw------- 1 root root 9.9G Sep 22 03:37 messages-20190922
-rw------- 1 root root 11G Sep 29 03:11 messages-20190929
-rw------- 1 root root 11G Oct 6 03:49 messages-20191006

也可以执行命令查看目录所占的空间:

1
du -sh *

服务器一共就100G的存储空间,却被这些日志文件占去了一大半,查看了一下内容,发现大部分都是服务器上的定时任务的信息,因为定时任务非常频繁,而且打印的信息量比较大,看着这块要优化一下了。

先把这些日志删除一下,保证服务正常:

1
rm messages*

删除后,启动服务器,一切终于正常了!

本文原创地址为:http://examplecode.cn/2019/10/10/centos-disk-usage-100/
转载请注明出处。

在客户端开发的过程中,JSON的序列化与反序列化是一个常见的操作,通常我们的API服务端会返回JSON格式的数据。此时我们就需要解析JSON并转化为客户端中的对象。在客户端开发的初期,我们需要人工解析每一个字段,类似于很多obj.id = json[‘id’],obj.name = json[‘name’]…这样的代码。

遥想当年笔者刚接触Android开发的时候,刚开始并没有使用Gson,Jackson等自动解析工具,所以就出现了全体成员编写解析JSON代码的壮观景象!

手动解析JSON的缺点是:一、效率低下,我们浪费了大量的时间在写一些枯燥无比的代码,这样简直就是浪费生命。二、容易出错,我们谁都不能保证我们在敲代码的过程中不会敲错一个字母,于是就会出现解析异常,程序崩溃等BUG。

在Flutter发布的初期,并没有一个成熟的方式解析JSON,好在目前我们有了’json_serializable‘这个插件。通过这个插件,我们可以生成所有序列化/反序列化的方法。本文我们就来介绍如何生成解析JSON的方法。

一、添加插件依赖

在我们Flutter工程的pubspec.yaml中添加如下:

1
2
3
4
5
6
7
8
9
# 其他的依赖也添加到这里
dependencies:
analyzer: 0.38.2
json_annotation: ^3.0.0

# 这里是开发期间使用的插件
dev_dependencies:
build_runner: ^1.6.7
json_serializable: ^3.2.2

添加完后在工程根目录下执行’flutter pub get’,或者在Android Studio中点击’Packages get’按钮,下载插件。

注意这里指定版本的依赖’analyzer: 0.38.2’,加入这个是因为在json_annotation的最新3.x版本存在一些问题,会导致生成解析JSON错误,如果后续解决了这个问题,可以把这个依赖删除。

二、定义Model

下面我们定义一个Model,这里我们以User举例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import 'package:json_annotation/json_annotation.dart';
import 'package:x_marriage/core/models/region.dart';

part 'user.g.dart';

@JsonSerializable(explicitToJson: true)
class User {

int id;
String name;
Region address;

@JsonKey(name: 'login_time')
DateTime loginTime;

User();

factory User.fromJson(Map<String, dynamic> json) =>
_$UserFromJson(json);

Map<String, dynamic> toJson() => _$UserToJson(this);
}

定义完后,我们会发现AS会提示错误:

原因很简单,因为文件”user.g.dart”还有下面的方法我们还没有生成。不过不用担心,下面我们执行生成命令后,这些错误都会消失。

注意这里:

1
@JsonSerializable(explicitToJson: true)

这个注解的意思是在我们User中有嵌套对象时,调用User的toJson()方法时,也会调用嵌套对象的toJson()方法。比如上面的User中的address。

还有一点我们需要注意:

1
2
@JsonKey(name: 'login_time')
DateTime loginTime;

当API返回的字段与我们的字段不一致时,比如客户端使用驼峰的命名方式,服务器采用下划线的命名方式,我们就可以使用‘@JsonKey’来指定解析特定字段。

三、执行命令,生成解析代码

在工程的根目录下执行:

1
flutter pub run build_runner build

执行后我们会看到日志信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[INFO] Generating build script...
[INFO] Generating build script completed, took 481ms

[INFO] Initializing inputs
[INFO] Reading cached asset graph...
[INFO] Reading cached asset graph completed, took 140ms

[INFO] Checking for updates since last build...
[INFO] Checking for updates since last build completed, took 1.1s

[INFO] Running build...
[INFO] 1.1s elapsed, 0/16 actions completed.
[INFO] 2.3s elapsed, 0/16 actions completed.
[INFO] 3.4s elapsed, 0/16 actions completed.
[INFO] 4.5s elapsed, 0/16 actions completed.
[INFO] 10.4s elapsed, 1/16 actions completed.
[INFO] 11.6s elapsed, 3/18 actions completed.
[INFO] 12.8s elapsed, 17/29 actions completed.
[INFO] 13.8s elapsed, 53/65 actions completed.
[INFO] 15.0s elapsed, 65/79 actions completed.
[INFO] 16.0s elapsed, 87/97 actions completed.
[INFO] Running build completed, took 16.4s

[INFO] Caching finalized dependency graph...
[INFO] Caching finalized dependency graph completed, took 129ms

[INFO] Succeeded after 16.5s with 24 outputs (118 actions)

执行完后我们会发现工程中多了刚才User中缺少的文件:

在这个文件中,之前我们报错的方法’$UserFromJson’和 ‘$UserToJson’也都生成好了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'user.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

User _$UserFromJson(Map<String, dynamic> json) {
return User()
..id = json['id'] as int
..name = json['name'] as String
..address = json['address'] == null
? null
: Region.fromJson(json['address'] as Map<String, dynamic>)
..loginTime = json['login_time'] == null
? null
: DateTime.parse(json['login_time'] as String);
}

Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
'id': instance.id,
'name': instance.name,
'address': instance.address?.toJson(),
'login_time': instance.loginTime?.toIso8601String(),
};

这样解析JSON的代码就生成完毕了!Happy Coding!

其他一些问题的探讨

模板类

简单来说,生成自动解析JSON的代码就两步:一、定义Model。二、执行生成命令。非常简单。

但是我们发现定义Model时略显啰嗦。需要定义part …,和这两个方法’$UserFromJson’和 ‘$UserToJson’,相信没人喜欢每次都写这样的代码!笔者的做法是定义一个模板文件,每次需要新建Model类时就拷贝一下,改一下文件名和类名,然后只需要定义其属性即可。

代码也放上,如果有人喜欢,可以也定义这样一个类在工程中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import 'package:json_annotation/json_annotation.dart';

part 'template_model.g.dart';

@JsonSerializable(explicitToJson: true)
class TemplateModel {

String value;

TemplateModel();

factory TemplateModel.fromJson(Map<String, dynamic> json) =>
_$TemplateModelFromJson(json);

Map<String, dynamic> toJson() => _$TemplateModelToJson(this);
}

为什么需要生成代码?

比如其他的一些序列化/反序列化的库,比如Gson、Jackson等,我们不需要生成代码,直接可以进行解析,而在Flutter中,我们却需要用生成代码的方式,为什么呢?

因为Flutter禁用了Dart中的反射机制。上面的库中由于使用了反射机制,所以在运行时可以动态地解析Json字段并赋值。

为什么Flutter要禁用反射机制呢?因为在构建阶段Flutter可以知道哪些代码被使用了,哪些代码没有被使用,在构建时可以删除掉没用使用的代码,从而减小安装包的体积。如果使用了反射,则很难去发现那些未被使用的代码。

本文原创地址为:http://examplecode.cn/2019/10/06/flutter-json-serializable/
转载请注明出处。

近日,公司的前端设计的同事问我如果在手机浏览器中访问他设计的界面。顿时脑中浮现出了无数种方法:Nginx,Tomcat,Spring Boot,NodeJS,Apache,Django,Flask,Tornado…

作为公司的技术扛把子,总不能跟设计说这些东西吧,技术的目的就是让生活变得简单,不是吗?

于是经过研究,发现一种使用Python可以快速搭建HTTP服务器的方法。

好在公司的设计使用的Mac电脑,Python是内置的,如果使用Windows也不用担心,点击这里去官网下载安装一下即可。

下面我们就来介绍一下:

首先进入到目标目录,比如:cd ~/Download/design/,然后根据Python的版本不同执行以下命令:

Python 2.x

1
2
3
4
5
//使用默认端口号8000
python -m SimpleHTTPServer

//自定义端口号
python -m SimpleHTTPServer 3333

Python 3.x

1
2
3
4
5
6
7
8
//使用默认端口号8000
python3 -m http.server

//自定义端口号
python3 -m http.server 3333

//限制访问IP
python3 -m http.server --bind 127.0.0.1

刷新页面

如果页面更改后没有刷新,在命令行执行刷新命令

1
refresh

这样仅需一行代码,即可快速搭建一个Http服务器。

本机访问

1
http://127.0.0.1:端口号

其他设备(手机)访问

如果其他设备需要访问这个Http服务器,首先要确保这些设备与你的机器在同一个局域网内,并且互通。

然后找到本机的IP进行访问:

1
http://本机IP:端口号

本文原创地址为:http://examplecode.cn/2019/04/26/python-http-server/
转载请注明出处。

在移动开发的过程中我们会不可避免地使用到数据的持久化。当我们需要存储一些简单的数据时,我们会使用到NSUserDefaults (iOS) 或 SharedPreferences (Android),本文我们就来介绍这个方便的Flutter插件shared_preferences来实现本地存储的需求。

添加依赖

要使用这个插件,首先在工程的pubspec.yaml文件中加入对这个插件的依赖:

1
2
dependencies:
shared_preferences: ^0.5.2

下载插件

1
flutter packages get

使用示例

1
2
3
4
5
6
_incrementCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int counter = (prefs.getInt('counter') ?? 0) + 1;
print('Pressed $counter times.');
await prefs.setInt('counter', counter);
}

注意:在使用这个插件时,我们需要进行异步调用,也就是需要使用async/await。

工具类

我这里也封装了一个工具类pref_util.dart,需要的话可以直接拷贝到工程中,可以少写一些枯燥的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import 'package:shared_preferences/shared_preferences.dart';

setPrefString(String key, String value) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString(key, value);
}

setPrefInt(String key, int value) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setInt(key, value);
}

setPrefDouble(String key, double value) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setDouble(key, value);
}

setPrefStringList(String key, List<String> value) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setStringList(key, value);
}

setPrefBool(String key, bool value) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool(key, value);
}

getPrefString(String key, {String defaultValue = ''}) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString(key) ?? defaultValue;
}

getPrefInt(String key, {int defaultValue = 0}) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getInt(key) ?? defaultValue;
}

getPrefDouble(String key, {double defaultValue = 0}) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getDouble(key) ?? defaultValue;
}

getPrefStringList(String key, {List<String> defaultValue = const []}) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getStringList(key) ?? defaultValue;
}

getPrefBool(String key, {bool defaultValue = false}) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getBool(key) ?? defaultValue;
}

removePref(String key) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.remove(key);
}

prefContains(String key) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.get(key) != null;
}

本文原创地址为:http://examplecode.cn/2019/04/25/flutter-shared-preference/
转载请注明出处。

我的博客中关于Flutter的所有文章可以点击这里找到,欢迎关注!

如果我们的APP是国际化的产品,通常我们需要获取当前设备的语言设置信息。本文我们就来介绍如果正确获取到当前的语言设置。

获取当前的语言设置

正确的获取方式应该是在我们APP创建一个监听的回掉函数,用来监听当前设备语言设置的变化。

注册这个监听方法后在APP第一次启动的时候会被调用。

注册方式:

1
2
3
4
5
6
7
8
9
10
11
12
class MyApp extends StatelessWidget {
// 这个Widget是我们APP的根Widget
@override
Widget build(BuildContext context) {
return MaterialApp(
home: StartPage(),
localeResolutionCallback: (deviceLocale, supportedLocales) {
print('deviceLocale: $deviceLocale');
},
);
}
}

在监听到当前设备的最新语言设置后,我们可以将这个值保存在本地存储中,比如(Shared Preference)中,以便在其他地方使用。

关于如何使用Shared Preference,可以点击这里参考我的这篇文章

本文原创地址为:http://examplecode.cn/2019/04/15/flutter-device-language-info/
转载请注明出处。

我的博客中关于Flutter的所有文章可以点击这里找到,欢迎关注!

是否联网及网络类型

通过使用插件connectivity可以获取到当前设备是否联网,以及当前的网络类型是WIFI还是移动网络。

添加依赖

要使用这个插件,首先在工程的pubspec.yaml文件中加入对这个插件的依赖:

1
2
dependencies:
connectivity: ^0.4.2

下载插件

1
flutter packages get

判断是否联网

1
2
3
4
5
6
import 'package:connectivity/connectivity.dart';

Future<bool> isConnected() async {
var connectivityResult = await (Connectivity().checkConnectivity());
return connectivityResult != ConnectivityResult.none;
}

获取网络类型

1
2
3
4
5
6
7
8
import 'package:connectivity/connectivity.dart';

var connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.mobile) {
// 网络类型为移动网络
} else if (connectivityResult == ConnectivityResult.wifi) {
// 网络类型为WIFI
}

总结

通过这个插件可以获取到当前网络是WIFI或者移动网络,但不能获取到具体的移动网络类型,如2G/3G/4G/4G+等。

本文原创地址为:http://examplecode.cn/2019/04/13/flutter-network-type/
转载请注明出处。

我的博客中关于Flutter的所有文章可以点击这里找到,欢迎关注!

获取APP包的信息

通过使用插件package_info可以获取到当前APP的包名,版本名,版本号等信息。

添加依赖

要使用这个插件,首先在工程的pubspec.yaml文件中加入对这个插件的依赖:

1
2
dependencies:
package_info: ^0.4.0+2

下载插件

1
flutter packages get

获取APP的信息:

1
2
3
4
5
6
7
8
9
10
11
12
import 'package:package_info/package_info.dart';

PackageInfo packageInfo = await PackageInfo.fromPlatform();

//APP名称
String appName = packageInfo.appName;
//包名
String packageName = packageInfo.packageName;
//版本名
String version = packageInfo.version;
//版本号
String buildNumber = packageInfo.buildNumber;

获取设备相关的其他信息

关于如何获取屏幕宽度高度和分辨率等信息,可以参考这篇文章

获取设备信息,可以参考这篇文章

本文原创地址为:http://examplecode.cn/2019/04/12/flutter-package-info/
转载请注明出处。

我的博客中关于Flutter的所有文章可以点击这里找到,欢迎关注!

有时候我们需要获取到屏幕的宽度和高度还有密度进行计算。

使用Flutter时我们可以非常简单地通过类MediaQuery获取到。

1
2
MediaQueryData queryData;
queryData = MediaQuery.of(context);

获取屏幕密度/像素比例

1
queryData.devicePixelRatio

获取逻辑宽度高度

1
2
queryData.size.width
queryData.size.height

获取实际屏幕分辨率

注意上面我们获取到的只是逻辑上的宽高(Android上的dp/iOS上的pt),我们如果要取得屏幕的原始分辨率,需要乘以屏幕密度:

1
2
size.width * queryData.devicePixelRatio
size.height * queryData.devicePixelRatio

获取文字的缩放比例

1
queryData.textScaleFactor

获取硬件信息

如果要获取设备的详细的硬件信息,可以点击这里参考这篇文章

本文原创地址为:http://examplecode.cn/2019/04/11/flutter-screen-size-density/
转载请注明出处。

我的博客中关于Flutter的所有文章可以点击这里找到,欢迎关注!

我们在进行各个系统的原生开发时,都有对应的方法获取设备信息,那么在使用Flutter时如何获取设备相关的相关信息呢?

我们本文就来介绍一个Flutter插件:
Flutter Device Info

下面我们来逐步介绍如何获取设备信息。

首先在工程的pubspec.yaml中添加依赖

1
2
dependencies:
device_info: ^0.4.0+1

下载安装这个依赖包

在工程主目录下执行:

1
flutter packages get

在代码中使用

首先我们引入device_info.dart:

1
import 'package:device_info/device_info.dart';

iOS安装cocoapods

如果需要运行在iOS上运行,如果电脑上没有安装cocoapods,此时会报出异常:

1
2
3
4
5
6
7
Warning: CocoaPods not installed. Skipping pod install.
CocoaPods is used to retrieve the iOS platform side's plugin code that responds to your plugin usage on the Dart side.
Without resolving iOS dependencies with CocoaPods, plugins will not work on iOS.
For more info, see https://flutter.io/platform-plugins
To install:
brew install cocoapods
pod setup

此时我们则需要安装并配置cocoapods(确保机器上已经安装好了brew):

1
2
brew install cocoapods
pod setup

获取Android与iOS设备信息

1
2
3
4
5
6
7
8
9
10
void getDeviceInfo() async {
DeviceInfoPlugin deviceInfo = new DeviceInfoPlugin();
if(Platform.isAndroid) {
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
print(_readAndroidBuildData(androidInfo).toString());
} else if (Platform.isIOS) {
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
print(_readIosDeviceInfo(iosInfo).toString());
}
}

构造设备信息的Map

为了更方便的打印和使用信息,我们将AndroidDeviceInfo和IosDeviceInfo构造成Map:

Android:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Map<String, dynamic> _readAndroidBuildData(AndroidDeviceInfo build) {
return <String, dynamic>{
'version.securityPatch': build.version.securityPatch,
'version.sdkInt': build.version.sdkInt,
'version.release': build.version.release,
'version.previewSdkInt': build.version.previewSdkInt,
'version.incremental': build.version.incremental,
'version.codename': build.version.codename,
'version.baseOS': build.version.baseOS,
'board': build.board,
'bootloader': build.bootloader,
'brand': build.brand,
'device': build.device,
'display': build.display,
'fingerprint': build.fingerprint,
'hardware': build.hardware,
'host': build.host,
'id': build.id,
'manufacturer': build.manufacturer,
'model': build.model,
'product': build.product,
'supported32BitAbis': build.supported32BitAbis,
'supported64BitAbis': build.supported64BitAbis,
'supportedAbis': build.supportedAbis,
'tags': build.tags,
'type': build.type,
'isPhysicalDevice': build.isPhysicalDevice,
'androidId': build.androidId
};
}

iOS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Map<String, dynamic> _readIosDeviceInfo(IosDeviceInfo data) {
return <String, dynamic>{
'name': data.name,
'systemName': data.systemName,
'systemVersion': data.systemVersion,
'model': data.model,
'localizedModel': data.localizedModel,
'identifierForVendor': data.identifierForVendor,
'isPhysicalDevice': data.isPhysicalDevice,
'utsname.sysname:': data.utsname.sysname,
'utsname.nodename:': data.utsname.nodename,
'utsname.release:': data.utsname.release,
'utsname.version:': data.utsname.version,
'utsname.machine:': data.utsname.machine,
};
}

设备信息示例(基于模拟器):

Android:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
version.securityPatch: 2018-09-05,
version.sdkInt: 28,
version.release: 9,
version.previewSdkInt: 0,
version.incremental: 5124027,
version.codename: REL,
version.baseOS: ,
board: goldfish_x86_64,
bootloader: unknown,
brand: google,
device: generic_x86_64,
display: PSR1.180720.075,
fingerprint: google/sdk_gphone_x86_64/generic_x86_64:9/PSR1.180720.075/5124027:user/release-keys,
hardware: ranchu,
host: abfarm730,
id: PSR1.180720.075,
manufacturer: Google,
model: Android SDK built for x86_64,
product: sdk_gphone_x86_64,
supported32BitAbis: [x86],
supported64BitAbis: [x86_64],
supportedAbis: [x86_64, x86],
tags: release-keys,
type: user,
isPhysicalDevice: false,
androidId: 998921b52c7a7b79
}

iOS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
name: iPhone XR,
systemName: iOS,
systemVersion: 12.1,
model: iPhone,
localizedModel: iPhone,
identifierForVendor: 367F5936-39E1-4DFA-8DD2-9542424256BE,
isPhysicalDevice: false,
utsname.sysname:: Darwin,
utsname.nodename:: bogon,
utsname.release:: 18.2.0,
utsname.version:: Darwin Kernel Version 18.2.0: Thu Dec 20 20:46:53 PST 2018;
root:xnu-4903.241.1~1/RELEASE_X86_64,
utsname.machine:: x86_64
}

获取屏幕宽高密度等信息

关于如何获取屏幕宽度高度和分辨率等信息,可以参考这篇文章

本文原创地址为:http://examplecode.cn/2019/04/10/flutter-device-info/
转载请注明出处。

我的博客中关于Flutter的所有文章可以点击这里找到,欢迎关注!

当我们在开发APP时进行一些耗时操作,比如用户HTTP请求登录时,需要展示出一个模态的进度或加载对话框,防止用户重复操作。

本文我们就来介绍如何实现这样一个对话框。

实现进度对话框的话可以使用一个名叫modal_progress_hud的插件。

使用对话框插件

添加依赖

要使用这个插件,首先在工程的pubspec.yaml文件中加入对这个插件的依赖:

1
2
dependencies:
modal_progress_hud: ^0.1.3

下载插件

1
flutter packages get

示例:展示对话框

由于当前的对话框我们需要全屏展示,所以我们在整个页面之上包裹一个ModalProgressHUD,根据变量_saving的值是true还是false来控制对话框的展示与否。

1
2
3
4
5
6
7
8
9
10
11
12
...
bool _saving = false
...

@override
Widget build(BuildContext context) {
return Scaffold(
body: ModalProgressHUD(child: Container(
Form(...)
), inAsyncCall: _saving),
);
}

这里我们使用的是StatefulWidget,所以我们可以通过设置_saving的值来控制对话框的展示:

展示对话框:

1
2
3
setState(() {
_saving = true;
});

隐藏对话框:

1
2
3
setState(() {
_saving = false;
});

对话框的构造选项

我们可以定制对话框的展示,下面是我们构造对话框对象时的选项:

1
2
3
4
5
6
7
8
9
ModalProgressHUD(
@required inAsyncCall: bool,
@required child: Widget,
opacity: double,
color: Color,
progressIndicator: CircularProgressIndicator,
offset: double
dismissible: bool,
);

我们可以看到必选参数有两个:

  • inAsyncCall:控制对话框的显示状态。
  • child:子Widget。

通过其他的选项我们可以控制背景的透明度,颜色,展示样式等属性,有兴趣的同学可以逐一尝试一下。

本文原创地址为:http://examplecode.cn/2019/04/04/flutter-progress-dialog/
转载请注明出处。

我的博客中关于Flutter的所有文章可以点击这里找到,欢迎关注!