Flutter開(kāi)發(fā)總結(jié)

Flutter

使用Flutter一直是拒絕的,感覺(jué)追不動(dòng)新技術(shù)了。但是當(dāng)看到用的人越來(lái)越多,flutter的書(shū)籍已經(jīng)全面超過(guò)OC和Swift了。是時(shí)候低下還有幾許青絲的頭了,雖然學(xué)不精,但我學(xué)的廣。!_!

一、準(zhǔn)備工作

找一些網(wǎng)上的免費(fèi)資料學(xué)起來(lái),flutter中文網(wǎng)站看一個(gè)大概,一般感覺(jué)看不下去,先看個(gè)大概吧。然后詳細(xì)的學(xué)習(xí)Flutter實(shí)戰(zhàn),將所有的代碼都敲一遍。做到所有的功能自己實(shí)現(xiàn)。
建議使用Mac來(lái)開(kāi)發(fā),那么搭建環(huán)境會(huì)比較方便。Flutter使用Stable Channel,直接上最新的版本,雖然有一點(diǎn)坑,碰到問(wèn)題后網(wǎng)上的解決方法有點(diǎn)少。但是辦法總比困難多,學(xué)習(xí)的太順利,記住的東西太少。
Dart語(yǔ)言的學(xué)習(xí)感覺(jué)花一點(diǎn)時(shí)間過(guò)一下就好,簡(jiǎn)單的定義變量,函數(shù),集合看一下,夠用。

二、安裝及升級(jí)

注意:/Public/Flutter/flutter是我本地的路徑,需要替換。

  1. 參考https://flutter.dev/community/china上的描述,將flutter從下載到本地。git clone -b stable https://github.com/flutter/flutter.git
  2. 執(zhí)行flutter doctor進(jìn)行dart SDK的下載。
  3. vim .bash_profile中添加export PATH=~/Public/Flutter/flutter/bin:$PATH。
  4. source .bash_profile 讓PATH生效。
  5. 在android studio和VS code安裝flutter插件。
  6. flutter upgrade 在/Users/arthurwang/Public/Flutter/flutter 安裝flutter路徑中進(jìn)行。

三、 常用布局

  1. Container: 只有一個(gè)子Widget。默認(rèn)充滿,包含了padding、margin、color、寬高、decoration等。
  2. Padding: 只有一個(gè)子Widget。只用于設(shè)置Padding,常用于嵌套child,給child設(shè)置padding。
  3. Center:只有一個(gè)子Widget。只用于居中顯示,常用于嵌套child,給child設(shè)置居中。
  4. Stack:可以有多個(gè)子Widget。子Widget堆疊在一起。
  5. Column:可以有多個(gè)子Widget。垂直布局。
  6. Row: 可以有多個(gè)子Widget。水平布局。
  7. Expanded:只有一個(gè)子Widget。在Column和Row中充滿。(不會(huì)超出父視圖)
  8. ListView:可以有多個(gè)子Widget。
  9. Align: 只有一個(gè)子Widget。 絕對(duì)布局,可以設(shè)置位置。
  10. SizeBox: 強(qiáng)制設(shè)置它的孩子寬度或者高度為指定值。傳width、height、child。

明白這些布局的使用,基本能夠滿足頁(yè)面的布局。

四、組件

  1. MaterailApp:作為APP頂層的主要入口,可配置主題,多語(yǔ)言,路由等。
  2. Scaffold: 用戶頁(yè)面承載Widget,包含appbar、snackbar、drawer等material design設(shè)定。
  3. AppBar: 用于Scaffold的appbar,內(nèi)有標(biāo)題、二級(jí)頁(yè)面返回按鍵等。
  4. Text:顯示文本,可通過(guò)style設(shè)置TextStyle來(lái)設(shè)置字體樣式等。
  5. RichText: 福文本。設(shè)置TextSpan,可以拼接出富文本場(chǎng)景。
  6. TextField: 文本輸入框。
  7. Image: 圖片加載。
  8. FlatButton: 按鍵點(diǎn)擊。

明白基本組件,可以構(gòu)建一般頁(yè)面了。

五、 網(wǎng)絡(luò)請(qǐng)求

有兩種思路(1)通過(guò)flutter和native交互,發(fā)送url和params到native,然后返回結(jié)果。(2)通過(guò)flutter和native交互,從native獲取params然后在flutter進(jìn)行網(wǎng)絡(luò)請(qǐng)求。
考慮到Android的網(wǎng)絡(luò)第三方庫(kù)使用注解來(lái)實(shí)現(xiàn)請(qǐng)求,所以放棄了思路(1),嘗試思路(2)并封裝請(qǐng)求。
網(wǎng)絡(luò)請(qǐng)求官方推薦第三方庫(kù)Dio。


class ApiService {
  // 單例配置
  static ApiService get apiService => _getInstance();
  static ApiService _apiService = ApiService._internal();

  // 網(wǎng)絡(luò)請(qǐng)求配置
  static const _platform = const MethodChannel("******");
  static Dio _dio = Dio();

  String? _baseUrl;
  static const int _GET = 0;
  static const int _POST = 1;

  // 初始化
  factory ApiService() => _getInstance();

  static ApiService _getInstance() {
    return _apiService;
  }

  ApiService._internal() {
    _dio.options.connectTimeout = 5000;
    _dio.options.receiveTimeout = 3000;
    _dio.options.contentType = Headers.formUrlEncodedContentType;
    _dio.options.responseType = ResponseType.json;

    _setBaseUrl();
  }

  Future<void> _setBaseUrl() async {
    try {
      String? baseUrl = await _platform.invokeMethod("***") as String?;
      if (null != baseUrl) {
        _dio.options.baseUrl = baseUrl;
        _baseUrl = baseUrl;
      }
    } on PlatformException catch (e) {
      // Do nothing
    }
  }

  Future<Map<String, dynamic>?> _request(
      int type, String url, Map<String, String>? params) async {
    if (null == _baseUrl) {
      await _setBaseUrl();
    }

    Map<String, String>? tokenParams;
    try {
      Map<String, String>? paramsMap;
      if (null != params) {
        paramsMap = Map();
        paramsMap["params"] = json.encode(params);
      }

      tokenParams = Map<String, String>.from(
          await _platform.invokeMethod("***", paramsMap));
    } on PlatformException catch (e) {
      // Do nothing
    }

    print("ApiService url is $url, params is $tokenParams");

    Response<Map<String, dynamic>>? response;
    try {
      switch (type) {
        case _GET:
          response = await _dio.get(url, queryParameters: tokenParams);
          break;

        case _POST:
          response = await _dio.post(url, queryParameters: tokenParams);
          break;
      }
    } on DioError catch (e) {
      print("$url 錯(cuò)誤 ${e.message}");
      throw RequestError(kUnknownError, "服務(wù)器走失了,請(qǐng)稍后重試");
    }

    print("$url response is $response");

    if (0 != response?.data?["status"]) {
      throw RequestError(response?.data?["status"], response?.data?["msg"]);
    } else {
      print("ApiService response.data is ${response?.data}");
      return response?.data;
    }
  }

  // 網(wǎng)絡(luò)請(qǐng)求

  // 獲取信息
  Future<UserInfoEntity> getUserInfo() async {
    Map<String, dynamic>? response =
        await _request(_GET, "url/url/url", null);

    if (null != response) {
      return UserInfoEntity().fromJson(response);
    } else {
      throw RequestError(kUnknownError, "服務(wù)器走失了,請(qǐng)稍后重試");
    }
  }

采用工廠模式創(chuàng)建一個(gè)網(wǎng)絡(luò)請(qǐng)求,在創(chuàng)建時(shí)設(shè)置好配置。之后添加新的接口,只需要在底部依次添加。

 Future<UserInfoEntity> getUserInfo() async {
    Map<String, dynamic>? response =
        await _request(_GET, "url/url/url", null);

    if (null != response) {
      return UserInfoEntity().fromJson(response);
    } else {
      throw RequestError(kUnknownError, "服務(wù)器走失了,請(qǐng)稍后重試");
    }
  }

采用處

try {
      UserInfoEntity entity = await ApiService.apiService.getUserInfo();
      userInfoData = entity.data;
      notifyListeners();
    } on RequestError catch (e) {
      print("e.message is ${e.message}, e.code is ${e.code}");
    }

其中的UserInfoEntity為定義的Model,RequestError為自定錯(cuò)誤類型。

六、 Json轉(zhuǎn)Model

強(qiáng)烈推薦Android Studio的插件FlutterJsonBeanFactory,使用方法參照官網(wǎng)。如果要修改或重建Model,那么將之前的model刪除再創(chuàng)建一次。有一些小的修改,可以用alt+J來(lái)刷新。
注意:Json數(shù)據(jù)如果不能“Make”,那么可以試下將最后一個(gè)逗號(hào)刪除。

七、 狀態(tài)共享

當(dāng)用戶信息此數(shù)據(jù)需要用在很多頁(yè)面,那么考慮采用官方推薦的provider來(lái)實(shí)現(xiàn)刷新Widget。

void main() => runApp(ChangeNotifierProvider<PersonalCenterProviderModel>.value(
      value: PersonalCenterProviderModel(),
      child: MyApp(),
    ));

建議將Provider添加到頂部,方便在任何頁(yè)面中獲取UserInfo。

class PersonalCenterProviderModel with ChangeNotifier {
  UserInfoData? userInfoData;

  void refreshUserInfo() async {
    try {
      UserInfoEntity entity = await ApiService.apiService.getUserInfo();
      userInfoData = entity.data;
      notifyListeners();
    } on RequestError catch (e) {
      print("e.message is ${e.message}, e.code is ${e.code}");
    }
  }

}

PersonalCenterProviderModel類中可以添加很多方法來(lái)更新數(shù)據(jù)。

PersonalCenterProviderModel _model =
        Provider.of<PersonalCenterProviderModel>(context);
backgroundImage: NetworkImage(_model.userInfoData?.portrait ?? defaultImage),

使用方式,當(dāng)userInfo中的portrait發(fā)生改變時(shí),那么頁(yè)面會(huì)重建。

PersonalCenterProviderModel _model =
      Provider.of<PersonalCenterProviderModel>(context, listen: false);
      _model.refreshUserInfo();

如果僅僅是獲取model來(lái)調(diào)用方法,那么將listen設(shè)置為false。

八、Flutter和Native交互

參考module集成到iOS和Android。
iOS的使用方式

- (void)initialFlutterVC
{
    FlutterEngine *flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
    [flutterEngine run];
    [GeneratedPluginRegistrant registerWithRegistry:flutterEngine];
    self.flutterViewController = [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
    [self initialFlutterMethods];
    
    self.flutterViewController.modalPresentationStyle = UIModalPresentationFullScreen;
}

打開(kāi)Flutter頁(yè)面

- (void)personalButtonDidClick:(id)sender
{
    [self presentViewController:self.flutterViewController animated:YES completion:nil];
}

記得提前初始化flutterEngine,可以加快打開(kāi)的速度。
Android的使用方式

flutterEngine = new FlutterEngine(this);
        flutterEngine.getDartExecutor().executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault());
        FlutterEngineCache.getInstance().put(engine_personal_center, flutterEngine);

提前初始化flutterEngine

btn_jump.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(FlutterActivity.
                        withCachedEngine(engine_personal_center).build(HomeActivity.this));
            }
        });

通過(guò)緩存打開(kāi)Flutter頁(yè)面。

添加交互方法

final String CHANNEL = "qji_flutter/getUserInfo";
        MethodChannel methodChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL);
        methodChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
                switch (call.method) {
                    case "*":
                        result.success(API.SERVER_URL);
                        break;
                    case "*":
                        String paramsStr = call.argument("params");
                        Gson gson = new Gson();
                        Type jsonType = new TypeToken<HashMap<String, String>>() {
                        }.getType();
                        HashMap<String, String> params = gson.fromJson(paramsStr, jsonType);
                        result.success(ParamManager.Companion.token(params));
                        break;
                    case "*":
                        EventMessage<String> eventMessage = new EventMessage<>();
                        eventMessage.setTag(EVENT_LOGOUT);
                        EventBus.getDefault().post(eventMessage);

                        UserAccount.getInstance().logout();

                        break;

                    case "*":
                        String path = call.argument("imagePath");
                        if (null == path || path.isEmpty()) {
                            result.error("1", "上傳失敗", null);
                        } else {
                            uploadImageToOSS(path, result);
                        }

                        break;

                    case "*":
                        // DO Nothing
                        break;
                    default:
                        result.notImplemented();
                        break;
                }
            }
        });

iOS與Android的用法類似,注意必須是同一個(gè)flutterEngine,不然會(huì)找不到。

九、iOS打包

如果打包的證書(shū)不是debug或release,那么需要FLUTTER_BUILD_MODE設(shè)置為Release才能進(jìn)行Ad-hoc和Release打包。
在../qiji_flutter/.ios/Flutter/flutter_export_environment.sh中添加
export "FLUTTER_BUILD_MODE=Release"

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容