Flutter項目復盤整理記錄

剛剛使用Flutter開發完一個項目,對項目來說不大基本功能都會有,主流的一些插件都有使用到。現在回過來做一個項目復盤,針對項目使用的一些插件和遇到的一些問題進行分析總結以作記錄。

網絡框架+路由導航+狀態管理+json數據序列化

Android打包release版本之Androidx支持包兼容問題

項目中使用的主要功能插件

  • 路由導航 fluro
  • 狀態管理 provider
  • 網絡框架 Dio
  • JSON和數據序列化

路由導航之fluro

使用fluro對路由進行統一配置、統一管理,應用開發使用很是方便。

配置路由

創建routes.dart類進行路由配置。
APP打開的第一個頁面路徑必須是'/',這里涉及到兩個參數:

  • routePath:對應頁面路徑
  • handler:對應打開頁面的handler
class Routes {
  static String root = "/";
  static String demoSimple = "/demo";

  static void configureRoutes(Router router) {
    router.notFoundHandler = Handler(
        handlerFunc: (BuildContext context, Map<String, List<String>> params) {
      print("ROUTE WAS NOT FOUND !!!");
      return;
    });
    router.define(root, handler: rootHandler);
    router.define(demoSimple, handler: demoRouteHandler);
  }
}
構建頁面handler

創建route_handler.dart進行頁面handler配置。
不需要傳遞參數的頁面使用rootHandler的寫法,如果要傳遞參數按demoRouteHandler的寫法。
fluro有個問題不能傳遞中文,需要編碼解決傳遞。

var rootHandler = Handler(
    handlerFunc: (BuildContext context, Map<String, List<String>> params) {
  return HomePage();
});

var demoRouteHandler = Handler(
    handlerFunc: (BuildContext context, Map<String, List<String>> params) {
  String message = params["message"]?.first;
  String colorHex = params["color_hex"]?.first;
  String result = params["result"]?.first;
  Color color = Color(0xFFFFFFFF);
  if (colorHex != null && colorHex.length > 0) {
    color = Color(ColorHelpers.fromHexString(colorHex));
  }
  return DemoSimpleComponent(message: message, color: color, result: result);
});
初始化fluro路由初始化

在main.dart進行初始化

    final router = Router();
    Routes.configureRoutes(router);
    Application.router = router;
執行頁面跳轉
Application.router.navigateTo(context, '${Routes.demoSimple}?message=xxx&colorHex=xxx');
解決中文不能傳遞問題

將中文參數在傳遞前進行解碼轉換,傳遞后取出參數解析

/// fluro 參數編碼解碼工具類
class FluroConvertUtils {
  /// fluro 傳遞中文參數前,先轉換,fluro 不支持中文傳遞
  static String fluroCnParamsEncode(String originalCn) {
    StringBuffer sb = StringBuffer();
    var encoded = Utf8Encoder().convert(originalCn);
    encoded.forEach((val) => sb.write('$val,'));
    return sb.toString().substring(0, sb.length - 1).toString();
  }

  /// fluro 傳遞后取出參數,解析
  static String fluroCnParamsDecode(String encodedCn) {
    var decoded = encodedCn.split('[').last.split(']').first.split(',');
    var list = <int>[];
    decoded.forEach((s) => list.add(int.parse(s.trim())));
    return Utf8Decoder().convert(list);
  }
}

provider

使用provider插件包對APP狀態進行管理。實現provider對狀態進行管理分為三部分:

  • ChangeNotifier
  • ChangeNotifierProvider
  • Consumer
ChangeNotifier

ChangeNotifier它是Flutter開發包中的一個簡單類,為監聽器提供更改通知。
ChangeNotifier 是封裝應用狀態的一種方法。對于非常簡單的應用程序,可以使用一個ChangeNotifier。在復雜的模型中,你將有幾個模型,因此有幾個ChangeNotifiers。

創建model繼承ChangeNotifier

class CartModel extends ChangeNotifier {
  /// Internal, private state of the cart.
  final List<Item> _items = [];

  /// An unmodifiable view of the items in the cart.
  UnmodifiableListView<Item> get items => UnmodifiableListView(_items);

  /// The current total price of all items (assuming all items cost $42).
  int get totalPrice => _items.length * 42;

  /// Adds [item] to cart. This is the only way to modify the cart from outside.
  void add(Item item) {
    _items.add(item);
    // This call tells the widgets that are listening to this model to rebuild.
    notifyListeners();
  }
}

這里特定于ChangeNotifier代碼就是notifyListeners()。只要模型更改調用此方法UI就會更改。

ChangeNotifierProvider

ChangeNotifierProvider是一個widget,為其后臺widget提供ChangeNotiffier實例。
ChangeNotifier至于必要的范圍至上,不要污染范圍。

void main() {
  runApp(
    ChangeNotifierProvider(
      builder: (context) => CartModel(),
      child: MyApp(),
    ),
  );
}

如果需要提供多個類,可以使用MultiProvider:

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(builder: (context) => CartModel()),
        Provider(builder: (context) => SomeOtherClass()),
      ],
      child: MyApp(),
    ),
  );
}
Consumer

現在CartModel通過頂部的ChangeNotifierProvider聲明提供給我們應用程序中的小部件,我們可以開始使用它。

return Consumer<CartModel>(
  builder: (context, cart, child) {
    return Text("Total price: ${cart.totalPrice}");
  },
);

指定想要訪問的模型類型,在這里我們需要CartModel,因此寫為Consumer<CartModel>。

Provider.of

有時,并不真正需要模型中的數據來更改UI,但仍然需要訪問它。

Provider.of<CartModel>(context, listen: false).add(item);

http請求庫Dio

dio是一個強大的Dart Http請求庫,支持Restful API、FormData、攔截器、請求取消、Cookie管理、文件上傳/下載、超時、自定義適配器等...

發起一個Get請求:

Response response;
Dio dio = new Dio();
response = await dio.get("/test?id=12&name=wendu");
print(response.data.toString());
// 請求參數也可以通過對象傳遞,上面的代碼等同于:
response = await dio.get("/test", queryParameters: {"id": 12, "name": "wendu"});
print(response.data.toString());

發起一個Post請求:

response = await dio.post("/test", data: {"id": 12, "name": "wendu"});

以流的方式接收響應數據:

Response<ResponseBody> rs = await Dio().get<ResponseBody>(url,
  options: Options(responseType: ResponseType.stream), //設置接收類型為stream
);
print(rs.data.stream); //響應流

以二進制數組的方式接收響應數據:

Response<List<int>> rs = await Dio().get<List<int>>(url,
 options: Options(responseType: ResponseType.bytes), //設置接收類型為bytes
);
print(rs.data); //二進制數組

發送 FormData:

FormData formData = new FormData.from({
    "name": "wendux",
    "age": 25,
  });
response = await dio.post("/info", data: formData);

使用代碼生成庫序列化JSON

json_serializable包是一個自動生成源代碼生成器,可以生成JSON序列化樣板。

在項目中設置json_serializable

要在項目中包含json_serializable,需要一個常規依賴和兩個dev依賴項。dev依賴項是我們項目源代碼中未包含的依賴項-它們僅在開發環境中使用。
pubspec.yaml

dependencies:
  # Your other regular dependencies here
  json_annotation: ^2.0.0

dev_dependencies:
  # Your other dev_dependencies here
  build_runner: ^1.0.0
  json_serializable: ^2.0.0

在項目根目錄下運行flutter pub以在項目中使用這些新的依賴項。

以json_serializable方式創建模型類

user.dart

import 'package:json_annotation/json_annotation.dart';

/// This allows the `User` class to access private members in
/// the generated file. The value for this is *.g.dart, where
/// the star denotes the source file name.
part 'user.g.dart';

/// An annotation for the code generator to know that this class needs the
/// JSON serialization logic to be generated.
@JsonSerializable()

class User {
  User(this.name, this.email);

  String name;
  String email;

  /// A necessary factory constructor for creating a new User instance
  /// from a map. Pass the map to the generated `_$UserFromJson()` constructor.
  /// The constructor is named after the source class, in this case, User.
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);

  /// `toJson` is the convention for a class to declare support for serialization
  /// to JSON. The implementation simply calls the private, generated
  /// helper method `_$UserToJson`.
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

如果需要,還可以輕松自定義命名策略。例如,如果API返回帶有snake_case的對象,并且您希望在模型中使用lowerCamelCase,則可以將@JsonKey批注與name參數一起使用:

/// Tell json_serializable that "registration_date_millis" should be
/// mapped to this property.
@JsonKey(name: 'registration_date_millis')
final int registrationDateMillis;
生成實用文件

在第一次創建json_serializable類時,您將得到類似于下圖所示的錯誤。

image

這些錯誤完全正常,僅僅是因為模型類的生成代碼尚不存在。要解決此問題,請運行生成序列化樣板的代碼生成器

有兩種運行代碼生成器的方法。

One-time code generation

通過在項目根目錄中運行flutter pub run build_runner build,可以在需要時為模型生成JSON序列化代碼。這會觸發一次性構建,該構建遍歷源文件,選擇相關文件,并為它們生成必要的序列化代碼。
雖然這很方便,但如果您不必每次在模型類中進行更改時都必須手動運行構建,那將是很好的

Generating code continuously

觀察者使我們的源代碼生成過程更加方便。它會監視項目文件中的更改,并在需要時自動構建必要的文件。通過在項目根目錄中運行flutter pub run build_runner watch來啟動觀察程序。
啟動觀察器一次并使其在后臺運行是安全的。

使用json_serializable模型

要以json_serializable方式解碼JSON字符串,您實際上沒有對我們以前的代碼進行任何更改。

Map userMap = jsonDecode(jsonString);
var user = User.fromJson(userMap);

編碼也是如此。調用API與以前相同。

String json = jsonEncode(user);

使用json_serializable,您可以忘記User類中的任何手動JSON序列化。源代碼生成器創建一個名為user.g.dart的文件,該文件具有所有必需的序列化邏輯。您不再需要編寫自動化測試來確保序列化工作 - 現在libarary有責任確保序列化正常工作。

生成嵌套類的代碼

考慮以下Address類:

import 'package:json_annotation/json_annotation.dart';
part 'address.g.dart';

@JsonSerializable()
class Address {
  String street;
  String city;

  Address(this.street, this.city);

  factory Address.fromJson(Map<String, dynamic> json) => _$AddressFromJson(json);
  Map<String, dynamic> toJson() => _$AddressToJson(this);
}

Address類嵌套在User類中:

import 'address.dart';
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';

@JsonSerializable()
class User {
  String firstName;  
  Address address;

  User(this.firstName, this.address);

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

在終端中運行flutter pub run build_runner build會創建* .g.dart文件,但private _ $ UserToJson()函數類似于以下內容:

(
Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{      
  'firstName': instance.firstName,
  'address': instance.address,      
};

所有看起來都很好,但如果你對用戶對象執行print():

Address address = Address("My st.", "New York");
User user = User("John", address);
print(user.toJson());

結果卻是:

{name: John, address: Instance of 'address'}

想要的結果可能是這樣:

{name: John, address: {street: My st., city: New York}}

要使其工作,請在類聲明的@JsonSerializable()注釋中傳遞explicitToJson:true。 User類現在看起來如下:

import 'address.dart';
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';

@JsonSerializable(explicitToJson: true)
class User {
  String firstName;  
  Address address;

  User(this.firstName, this.address);

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

Flutter打包release Androidx兼容問題

項目在最后開發接近尾聲時,我試著打包release包,發現了Androidx兼容的問題。問題原因就是項目中同時使用到了Androidx包和Support包,而這兩種包只能存在一個,要么全部引用Androidx包,要么全部引用Support包。
解決Androidx包沖突有兩種方式:

  • 全部引用Androidx包
  • 全部引用Support包

這里我使用的是第一種,建議也是使用第一種,官方建議使用Androidx,以后的第三包都會轉到使用androidx的。
轉Androidx包的方法,官方提供了兩種方法,一種是自動轉(官方推薦),一種是手動轉。

使用Android Studio自動升級Androidx包

這里對Android studio版本的要求是3.2以上。
項目上右鍵android文件夾,Flutter -> Open Android module in Android Studio
在新建的Android Studio窗口選擇Refactor -> Migrate to AndroidX
點擊Do Refactor即可完成AndroidX的遷移工作。

手動升級Androidx
  1. 打開android/gradle/wrapper/gradle-wrapper.properties,修改distributionUrl的值
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
  1. 打開android/build.gradle,修改com.android.tools.build:gradle 到3.3.0
dependencies {
    classpath 'com.android.tools.build:gradle:3.3.0'
}
  1. 打開android/gradle.properties,添加兩行
android.enableJetifier=true
android.useAndroidX=true
  1. 打開android/app/build.gradle,在android{}里面,確保compileSdkVersion 和 targetSdkVersion 值為28
    替換所有過時的依賴庫android.support到androidx。
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容