Flutter
Flutter
CSM
https://www.danielteng.com/courses/csm/
環境安裝
新增環境變數
- cd $home
- ls -al
- nano .zshrc
- export PATH="$PATH:/Users/${userName}/Documents/Libary/flutter/bin"
透過 flutter doctor 確認環境已設定完成
安裝cocoapods前要先確定Ruby的安裝版本是否一致
- Install Ruby
- brew install ruby
- Install cocoapods
- sudo gem install cocoapods
- Install Ruby
pod install 在M系列Mac上執行時,需要先執行
sudo arch -x86_64 gem install ffi
arch -x86_64 pod install
若在執行 Android Studio時出現 "Gradle project sync failed"
要去注意 Android Studio -> Tool -> SDK Manager -> SDK Platforms -> Show Package Details
版本最少安裝 Android 9(Pie) -> Android SDK Platform 28
IDE相關設定
- Visual Studio Code
- Dart
- Flutter
- Code Spell Checker(檢查拼錯字)
- Indent-Rainbow(縮排變色)
- Material Icon Theme(文件圖標)
- Prettier - Code formatter(格式化插件)
若在Visual Studio Code中,字體顏色變成 檢查主題色彩是不是Dark+,若是選擇Dark則會有上圖的情況 調整後,如下圖
套件管理工具
M1晶片安裝完成後,需要額外執行以下語法,Homebrew才能使用
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
Method Channel
套件
網路行為API呼叫需要同時import 這兩個
http: ^0.13.5 http
intl: ^0.17.0 intl
Pub Dev 套件查詢網站
permission_handler 權限管理
showcaseview 製作導引用
modal_bottom_sheet 下方彈窗
shared preferences 儲存機制 範例說明
Easy Loading 讀取動畫
flutter_downloader 檔案下載 範例說明
Dropdown_button2 下拉式清單
若要做日期格式的多語系可參考 ==DateFormat(‘MM/dd EEEE’, locale).format(now);== 日期多語系
Camera 相機
auto_size_text 文字自適應
fl_chart 圖表顯示
flutter_gherkin 自動化測試BDD
[firebase_crashlytics ] (https://pub.dev/packages/firebase_crashlytics/example) 紀錄CRASH LOG (範例說明)
先建立自己的interceptor
將interceptor掛到client上
go_router 路由管理
官方
- Drawer 選單
參考文件
工具
- Json to Dart Class Generator
- AppIconGenerator 產生AppIcon
Android Icon Flutter -> android -> app -> src -> main -> res -> 圖片對應路徑 iOS Icon Flutter ->iOS -> Runner -> Assets.xcassets -> 圖片對應路徑 (Contents.json => 必要檔案)
Flutter App Life Cycle
iOS打包發佈相關
- 確認程式碼可以Build成功
- 確認環境 (下圖範例為uat) (IOS打包的時候,僅能透過Any Devices和實體機,不可用模擬器打包)
- 設定版號
- 打包
- 發布
- 依需求選擇後續設置直到 Upload 完成
Archive Manager(管理打包後的檔案) Windows -> Organizer
Android
Android檔案傳輸 檔案傳輸工具
手機安裝APK cd Library/Android/sdk/platform-tools adb install -r 將檔案拖曳進Terminal
手機截圖 adb shell screencap -p /sdcard/Download/s1.png adb pull /sdcard/Download/s1.png
手機錄影 adb shell screenrecord /sdcard/Download/d1.mp4 control +c adb pull /sdcard/Download/d1.mp4
- 確認程式碼可以Build成功
- 確認Version.properties 版本號
- 產生Apk檔
- 執行完畢後至Firebase將檔案上傳即可
Flutter Key的作用
推播
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
if (message.notification != null) {
Clipboard.setData(
ClipboardData(text: "keys :" + message.data.keys.first ?? "none"));
Future.delayed(const Duration(seconds: 3), () {});
}
});
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
檔案容量處理
getFileSize(String filepath, int decimals) async {
var file = File(filepath);
int bytes = await file.length();
if (bytes <= 0) return "0 B";
const suffixes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
var i = (log(bytes) / log(1024)).floor();
var gg =
((bytes / pow(1024, i)).toStringAsFixed(decimals)) + ' ' + suffixes[i];
return ((bytes / pow(1024, i)).toStringAsFixed(decimals)) + ' ' + suffixes[i];
}
1 byte = 8 bits 1KB (kilobyte) = 1024b 1MB (megabyte) = 1024KB 1GB (Gigabyte) = 1024MB 1TB (trillionbyte) = 1024gb 1PB (petabyte, billions of bytes, beat bytes) = 1024tb 1eb (Exabyte) = 1024pb 1zb (zettabyte) = 1024eb 1yb (yottabyte, 100 million bytes, Yao bytes) = 1024zb 1BB (brontobyte) = 1024yb
小技巧:
限制檔案大小上限時,不應該把檔案容量寫的與限制規格剛好,有時候檔表頭會有些META DATA 會造成太剛好的檔案上傳後檔案變大而上傳失敗!
舉例: 限制3MB檔案上傳,建議限制2.96MB
- const & final 差異
- Final 宣告的 property 是在專案執行(run time)階段的常數。
- Constant 宣告的 property 是在專案編譯(compile time)階段的常數。
final date1 = DateTime.now(); const date2 = DateTime.now(); // Compile error
正則
範例(最少輸入八碼,必須包含大小寫英文及數字):
bool isMatch(String password) {
RegExp regExpStr = RegExp(r'^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}$');
return regExpStr.hasMatch(password);
}
r'^
(?=.*[A-Z]) // should contain at least one upper case
(?=.*[a-z]) // should contain at least one lower case
(?=.*?[0-9]) // should contain at least one digit
(?=.*?[!@#\$&*~]) // should contain at least one Special character
.{8,} // Must be at least 8 characters in length
$
排序
上架
1.xcode-select –install 2.sudo gem install fastlane 3.到專案資料夾[IOS OR Android] 4.打開fastlane -> fasefile 看指令
- fastlane uat
過程中遇到問題可能要更新一些東西
fastlane install_plugins
bundle exec fastlane update_plugins
- iOS
憑證
IOS安裝憑證時,需要至官方網站
封包攔截
XCode環境
Explicit = 只能獨一無二 (通常是域名的反解) Wildcard = 可重複使用,但是上架不能用
1.Identify 2.Profile Member ship Certificate Expire date
發布用的Certificate一個公司最多三張
ELK
在呼叫API時,寫入log到txt,在Post時,把txt內的log讀出來整理,post時在一次送出
為何不在每次呼叫API就寫LOG到ELK? 如果使用者在呼叫API的過程中,手機網路異常或者有其他因素影響,會造成這個log沒有被記錄下來,而透過寫入log到txt的方式,可以透過排程補送log,查詢之前造成異常的原因,一方面也避免頻繁送log
注意事項
- 資料結構需要百分之百相同(ELK需要的格式)
- Content-Type和Auth要設定
- 資料在送出後,透過Flutter Console,印出來的資料前後有 "" 實際送出不會有""
Content-Type 在發送排程Log時需要使用application/vnd.api+json
但是在Flutter原生的http client在Post的時候會把Content-Type加上charset,變成 application/vnd.api+json; charset=utf-8
造成status code 回傳成功
(200 or 201) 但是Kibana沒有資料寫入
參考網址
解法:透過Dio來發送Post請求
//key = LogstashEventHubToken
//resourceUri = host
String createToken(String resourceUri, String keyName, String key) {
var timespan = (DateTime.now().millisecondsSinceEpoch / 1000).round();
var week = 60 * 60 * 24 * 7;
var expiry = (timespan + week).toString();
String stringToSign = Uri.encodeFull(resourceUri) + "\n" + expiry;
var keyBytes = utf8.encode(key);
var hmacSha256 = Hmac(sha256, keyBytes); //
var signature = base64
.encode(hmacSha256.convert(utf8.encode(stringToSign)).bytes)
.toString();
var sasToken = "SharedAccessSignature sr=" +
Uri.encodeComponent(resourceUri) +
"&sig=" +
Uri.encodeComponent(signature) +
"&se=" +
expiry +
"&skn=" +
keyName;
return sasToken;
}
鍵盤
- 收鍵盤
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
FocusScope.of(context).unfocus();
})
- Resize頁面造成鍵盤出現時將既有元件
In your Scaffold, set **resizeToAvoidBottomPadding** property to **false**.
Android 權限設定
Flutter flow
App內顯示文章
- WebView 隱私權條款
JavascriptMode.unrestricted 在WebView內啟用JavaScript , JavaScript預設是關閉的,若沒有啟用可能導致某些網頁顯示不出來
class PrivacyPolicyScreen extends StatefulWidget {
const PrivacyPolicyScreen({Key? key}) : super(key: key);
@override
_PrivacyPolicyScreenState createState() {
return _PrivacyPolicyScreenState();
}
}
class _PrivacyPolicyScreenState extends State<PrivacyPolicyScreen> {
//建立Controller
final Completer<WebViewController> _controller =
Completer<WebViewController>();
@override
Widget build(BuildContext context) {
_loadHtmlFromAssets();
return Scaffold(
appBar: AppBar(
backgroundColor: xeBlue,
elevation: 0,
leading: (GestureDetector(
behavior: HitTestBehavior.opaque,
//上一頁
child: Container(
padding: const EdgeInsets.all(12),
child: getImage("assets/images/new/icon_arrow_back.png",
color: Colors.white, width: 30, height: 30, fit: BoxFit.fill),
),
onTap: () {
Navigator.pop(context);
},
)),
),
//實際呼叫WebView
body: WebView(
initialUrl: 'https://flutter.dev',
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
),
);
}
//準備html App內顯示
_loadHtmlFromAssets() async {
String fileText =
await rootBundle.loadString('assets/privacy/privacy.html');
_controller.future.then((value) => value.loadHtmlString(fileText));
}
}
色碼轉換
GitFlow
hotfix 完成 => dev 完成 => main 發版
圖片
Android
iOS
- CADisableMinimumFrameDurationOnPhone 螢幕刷新率
佈局
Row 在自適應寬度時,要設定mainAxisSize: MainAxisSize.min不然就要設定父層寬度
Container(
padding: const EdgeInsets.only(left: 20, right: 20),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CustomizeImageAsset("assets/images/new/icon_exclamation_mark.png",
height: 20 * heightScale, width: 20 * heightScale),
const SizedBox(
width: 8,
),
CustomizeText(
text: tr("PT_PleaseCheckWithinTheSetRange"),
color: xeErrorRed,
fontSize: 17,
align: TextAlign.center,
)
]),
)
ListView 如果要依照放入的物件自適應高度則可以設定,如下圖放入四個語系字串。
shrinkWrap: true
鎖定轉向
await _controller.lockCaptureOrientation(); //CameraController
演算法相關
資安
取得路徑
- path_provider 取得路徑
建立資料夾
圖片閃爍
無法打開iproxy
Scroll(滾動、捲動)
取得當前國家代碼
原生Flutter作法:
WidgetsBinding.instance.window.locale.countryCode
String getCountryCode() {
return WidgetsBinding.instance.window.locale.countryCode;
} //台灣取得"TW" 大陸取得"CN"
其他做法: https://stackoverflow.com/questions/57977167/device-country-in-flutter
引用 geolocator: ^5.1.1 geocoder: ^0.2.1
Future<String> getCountryName() async {
Position position = await Geolocator().getCurrentPosition(desiredAccuracy: LocationAccuracy.high); //取得當前經緯度
final coordinates = new Coordinates(position.latitude, position.longitude);
var addresses = await Geocoder.local.findAddressesFromCoordinates(coordinates);
var first = addresses.first;
return first.countryName; // this will return country name
}
但是要注意 大陸地區無法使用這個方案,geocoder目前已沒有在維護
有出一個 geocder2 有Null safety的版本,不過需要透過Google Map在大陸地區無法使用。
單元測試
路由管理
自定義快捷(BottomNavigationBar)
新專案
- 啟用新專案時,會有大量註解,可以透過 Ctrl+F 搜尋
//.*
用空字串替換掉,如下圖
BLOC
async gives you a Future
async* gives you a Stream
模擬器&鍵盤
iOS Simulator 鍵盤如果預設被關閉,在做Flutter TextField 時,會變成這樣如下圖
可以透過Command+K 啟用鍵盤
- 啟用結果如下圖
若鍵盤被遮蔽如下圖,可以透過
isScrollControlled
來控制,如下圖可以透過
flutter isScrollControlled:true
避免鍵盤被遮蔽如下圖