void getRequest() async {
//创建网络调用示例
Dio dio = new Dio();
//设置URI及请求user-agent后发起请求
var response = await dio.get(“https://flutter.dev”, options:Options(headers: {“user-agent” : “Custom-UA”}));
//打印请求结果
if(response.statusCode == HttpStatus.ok) {
print(response.data.toString());
} else {
print(“Error: ${response.statusCode}”);
}
}
//下载-------------
//使用FormData表单构建待上传文件
FormData formData = FormData.from({
“file1”: UploadFileInfo(File(“./file1.txt”), “file1.txt”),
“file2”: UploadFileInfo(File(“./file2.txt”), “file1.txt”),
});
//通过post方法发送至服务端
var responseY = await dio.post(“https://xxx.com/upload”, data: formData);
print(responseY.toString());
//使用download方法下载文件
dio.download(“https://xxx.com/file1”, “xx1.zip”);
//增加下载进度回调函数
dio.download(“https://xxx.com/file1”, “xx2.zip”, onReceiveProgress: (count, total) {
//do something
});
//并行请求--------------
//同时发起两个并行请求
List responseX= await Future.wait([dio.get(“https://flutter.dev”),dio.get(“https://pub.dev/packages/dio”)]);
//打印请求1响应结果
print(“Response1: ${responseX[0].toString()}”);
//打印请求2响应结果
print(“Response2: ${responseX[1].toString()}”);
//拦截器-----------------
//增加拦截器
dio.interceptors.add(InterceptorsWrapper(
onRequest: (RequestOptions options){
//为每个请求头都增加user-agent
options.headers[“user-agent”] = “Custom-UA”;
//检查是否有token,没有则直接报错
if(options.headers[‘token’] == null) {
return dio.reject(“Error:请先登录”);
}
//检查缓存是否有数据
if(options.uri == Uri.parse(‘http://xxx.com/file1’)) {
return dio.resolve(“返回缓存数据”);
}
//放行请求
return options;
}
));
//增加try catch,防止请求报错
try {
var response = await dio.get(“https://xxx.com/xxx.zip”);
print(response.data.toString());
}catch(e) {
print(e);
}
- 只能手动解析.
import ‘dart:convert’;
String jsonString = ‘’’
{
“id”:“123”,
“name”:“张三”,
“score” : 95,
“teacher”: { “name”: “李四”, “age” : 40 }
}
‘’';
//json解析
//所谓手动解析,是指使用 dart:convert 库中内置的 JSON 解码器,将 JSON 字符串解析成自定义对象的过程。
class Teacher {
String name;
int age;
Teacher({this.name, this.age});
factory Teacher.fromJson(Map<String, dynamic> parsedJson) {
return Teacher(name: parsedJson[‘name’], age: parsedJson[‘age’]);
}
@override
String toString() {
return ‘Teacher{name: $name, age: $age}’;
}
}
class Student {
String id;
String name;
int score;
Teacher teacher;
Student({this.id, this.name, this.score, this.teacher});
//从Map中取
factory Student.fromJson(Map<String, dynamic> parsedJson) {
return Student(
id: parsedJson[‘id’],
name: parsedJson[‘name’],
score: parsedJson[‘score’],
teacher: Teacher.fromJson(parsedJson[‘teacher’]));
}
@override
String toString() {
return ‘Student{id: $id, name: $name, score: $score, teacher: $teacher}’;
}
}
void main() {
final jsonResponse = json.decode(jsonString);//将字符串解码成Map对象
Student student = Student.fromJson(jsonResponse);//手动解析
print(student.teacher.name);
}
- json解析比较耗时,放compute中去进行,不用担心阻塞UI了. compute得有Widget才行.
-
由于 Flutter 仅接管了渲染层,真正涉及到存储等操作系统底层行为时,还需要依托于原生 Android、iOS.
-
三种数据持久化方法,即文件、SharedPreferences 与数据库
-
Flutter 提供了两种文件存储的目录,即临时(Temporary)目录与文档(Documents)目录:
3.1 文件
需要引入: path_provider: ^1.6.4
//创建文件目录
Future get _localFile async {
final directory = await getApplicationDocumentsDirectory();
final path = directory.path;
return File(‘$path/content.txt’);
}
//将字符串写入文件
Future writeContent(String content) async {
final file = await _localFile;
return file.writeAsString(content);
}
//从文件读出字符串
Future readContent() async {
try {
final file = await _localFile;
String contents = await file.readAsString();
return contents;
} catch (e) {
return “”;
}
}
3.2 SharedPreferences
需要引入: shared_preferences: ^0.5.6+2
//读取SharedPreferences中key为counter的值
Future_loadCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int counter = (prefs.getInt(‘counter’) ?? 0);
return counter;
}
//递增写入SharedPreferences中key为counter的值
Future_incrementCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int counter = (prefs.getInt(‘counter’) ?? 0) + 1;
prefs.setInt(‘counter’, counter);
}
3.3 数据库
需要引入: sqflite: ^1.2.1
dbDemo() async {
final Future database = openDatabase(
//join是拼接路径分隔符
join(await getDatabasesPath(), ‘student_database.db’),
onCreate: (db, version) => db.execute(
“CREATE TABLE students(id TEXT PRIMARY KEY,name TEXT,score INTEGER)”),
onUpgrade: (db, oldVersion, newVersion) {
//dosth for 升级
},
version: 1,
);
Future insertStudent(Student std) async {
final Database db = await database;
await db.insert(
‘students’,
std.toJson(),
//插入冲突策略,新的替换旧的
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
//插入3个
await insertStudent(student1);
await insertStudent(student2);
await insertStudent(student3);
Future<List> students() async {
final Database db = await database;
final List<Map<String, dynamic>> maps = await db.query(‘students’);
return List.generate(maps.length, (i) => Student.fromJson(maps[i]));
}
读取出数据库中插入的Student对象集合
students().then((list) => list.forEach((s) => print(s.name)));
//释放数据库资源
final Database db = await database;
db.close();
}
-
用AS单独打开Flutter项目中的Android工程,写代码,每次写完代码rebuild一下.然后想让Flutter代码能调到Android这边的代码,得重新运行.
-
如果AS run窗口不展示任何消息,可以使用 命令
flutter run lib/native/invoke_method.dart
执行dart,然后看错误消息. -
Flutter发起方法调用请求开始,请求经由唯一标识符指定的方法通道到达原生代码宿主,而原生代码宿主则通过注册对应方法实现,响应并处理调用请求.最后将执行结果通过消息通道,回传至Flutter.
-
方法通道是非线程安全的,需要在UI线程(Android或iOS的主线程)回调.
-
数据持久化,推送,摄像头,蓝牙等,都需要平台支持
-
轻量级解决方案: 方法通道机制 Method Channel
-
调用示例:
class _MyHomePageState extends State {
//声明MethodChannel
static const platform = MethodChannel(‘com.xfhy.basic_ui/util’);
handleButtonClick() async {
bool result;
//捕获 万一失败了呢
try {
//异步等待,可能很耗时 等待结果
result = await platform.invokeMethod(‘isEmpty’, “have data”);
} catch (e) {
result = false;
}
print(‘result : $result’);
}
}
//Android代码
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
//参考: https://flutter.dev/docs/development/platform-integration/platform-channels
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, “com.xfhy.basic_ui/util”).setMethodCallHandler { call, result ->
//判断方法名是否支持
if (call.method == “isEmpty”) {
val arguments = call.arguments
result.success(StringUtil.isEmpty(arguments as? String))
print(“success”)
} else {
//方法名暂不支持
result.notImplemented()
print(“fail”)
}
}
}
}
- Android或者iOS的数据会被序列化成一段二进制格式的数据在通道中传输,当该数据传递到Flutter后,又会被反序列化成Dart语言中的类型.
-
除去地图、WebView、相机等涉及底层方案的特殊情况外,大部分原生代码能够实现的 UI 效果,完全可以用 Flutter 实现.
-
使用这种方式对性能造成非常大的影响且不方便维护.
-
方法通道: 原生逻辑复用
-
平台视图: 原生视图复用
官网地址: https://flutter.dev/docs/development/add-to-app
-
FlutterEngine 文档: https://github.com/flutter/flutter/wiki/Experimental:-Reuse-FlutterEngine-across-screens
-
FlutterView 文档: https://github.com/flutter/flutter/wiki/Experimental:-Add-Flutter-View
-
API一会儿就过时了,得去官网看最新的才行.
-
可以在Android App中开启Flutter的Activity,Flutter的Activity是在另外一个进程,第一次进入特别慢.也可以加入Flutter的View和Fragment
-
在Android工程下新建一个Flutter的module比较简单直接
-
Android跳转Flutter,依赖FlutterView.Flutter在FlutterView中建立了自己的导航栈.
-
通常会将Flutter容器封装成一个独立的Activity或者ViewController. 这样打开一个普通的Activity既是打开Flutter界面了
-
Flutter页面跳转原生界面,需要利用方法通道,然后用原生去打开响应的界面.
-
Flutter实例化成本非常高,每启动一个Flutter实例,就会创建一套新的渲染机制,即Flutter Engine,以及底层的Isolate.而这些实例之间的内存是不相互共享的,会带来较大的系统资源消耗.
-
实际开发中,尽量用Flutter去开发闭环的业务模块.原生跳转过去就行,剩下的全部由Flutter内部完成. 尽量避免Flutter页面回到原生页面,原生页面又启动新的Flutter实例的情况.
-
Dart的一个库,可以实现在StatelessWidget中刷新数据.跨组件传递数据.全局共享数据.依赖注入
-
使用Provider后,我们就再也不需要StalefullWidget了.
-
Provider以InheritedWidget语法糖的方法,通过数据资源封装,数据注入,和数据读写这3个步骤,为我们实现了跨组件(跨页面)之间的数据共享
-
我们既可以用Provider来实现静态的数据读传递,也可以使用ChangeNotifierProvider来实现动态的数据读写传递,还用通过MultiProvider来实现多个数据资源的共享
-
Provider.of和Consumer都可以实现数据的读取,并且Consumer还可以控制UI刷新的粒度,避免与数据无关的组件的无谓刷新
-
封装数据
//定义需要共享的数据模型,通过混入ChangeNotifier管理听众
class CounterModel with ChangeNotifier {
int _count = 0;
//读方法
int get counter => _count;
//写方法
void increment() {
_count++;
notifyListeners();//通知听众刷新
}
}
- 放数据
尽量把数据放到更高的层级
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
//通过Provider组件封装数据资源
//因Provider是InheritedWidget的语法糖,所以它是一个Widget
//ChangeNotifierProvider只能搞一个
//MultiProvider可以搞多个
return MultiProvider(
providers: [
//注入字体大小 下个界面读出来
Provider.value(value: 30.0),
//注入计数器实例
ChangeNotifierProvider.value(value: CounterModel())
],
child: MaterialApp(
home: FirstPage(),
),
);
}
}
- 读数据
//示例: 读数据
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
//取出资源 类型是CounterModel
//获取计时器实例
final _counter = Provider.of(context);
//获取字体大小
final textSize = Provider.of(context);
/*
//使用Consumer2获取两个数据资源
Consumer2<CounterModel,double>(
//builder函数以参数的形式提供了数据资源
builder: (context, CounterModel counter, double textSize, _) => Text(
‘Value: ${counter.counter}’,
style: TextStyle(fontSize: textSize))
更多学习和讨论,欢迎加入我们!
有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。
这里有2000+小伙伴,让你的学习不寂寞~·
fierProvider.value(value: CounterModel())
],
child: MaterialApp(
home: FirstPage(),
),
);
}
}
- 读数据
//示例: 读数据
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
//取出资源 类型是CounterModel
//获取计时器实例
final _counter = Provider.of(context);
//获取字体大小
final textSize = Provider.of(context);
/*
//使用Consumer2获取两个数据资源
Consumer2<CounterModel,double>(
//builder函数以参数的形式提供了数据资源
builder: (context, CounterModel counter, double textSize, _) => Text(
‘Value: ${counter.counter}’,
style: TextStyle(fontSize: textSize))
[外链图片转存中…(img-m76y86Uw-1720112674668)]
更多学习和讨论,欢迎加入我们!
有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。
这里有2000+小伙伴,让你的学习不寂寞~·