Try   HackMD

Flutter初探:基本觀念筆記

(還有待整理跟補充)

Flutter框架結構

  1. 框架層Framework(Dart):
    why Dart(開發時JIT即時編譯/發布時AOT提前編譯)
    • Material/Cupertino
    • Widgets
    • Rendering
    • Animation/Painting/Gestures
  2. 引擎層Engine(C/C++)
  3. 嵌入層Platform-specific

Flutter環境設置

  1. install flutter and revise bash path for flutter
    • code . ~/.bash_profile 編輯檔案新增下面那行指定路徑
    • export PATH="$PATH:/Users/florachen/flutter/bin"
    • echo $PATH 或是 which flutter 檢查路徑對不對
    • flutter doctor檢查開發環境
    • shift command P 創建 => Flutter:New Project
    • flutter run (要開模擬器即時更改要切debug模式)
  2. Xcode(IOS simulator)
    • sudo gem install xcode-install
    • 輸入xcversion會跳出apple認證如果認證失效可以輸入( rm $HOME/.fastlane/spaceship/*/cookie )
    • xcversion list 列出所有版本以及已有安裝的版本
    • xcversion install 13.4.1
    • xcversion installed
    • xcrun version(確認有沒有安裝Xcode command line tool)
    • xcrun simctl list(列出所有可用的機型id)
    • xcrun simctl boot 5EE6154A-A997-46AF-811C-A95918878C43(在vscode中選取ios simulator)
    • xcrun simctl shutdown [device id]
  3. VScode 安裝 flutter & dart
  4. android studio
    • brew install cask android-studio
  5. firebase
  • curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
  • nvm alias default {{ 預設版本號 }} nvm list nvm use {{ 版本號 }} 切換當前命令列所使用的 Node.js 版本
  • npm install -g firebase-tools (firebase需要nodejs14)
  • firebase login
  • flutterfire configure連結現有data or 創建
  • initialize firebase 必須在一開始
  • 若設置使用者可以用信箱跟密碼登入 必須在註冊後收驗證信再讓使用者登入才能取得已驗證的userCredential

Dart

  1. variable:可更改(var)但型別不能改
  2. const 不可
  3. final:不可更改的var//可以省略類型聲明
    final str = "hi world";=>final String str = "hi world";
    *late 現在還沒但之後會有的變數(?
    dynamic dynamic是所有物件的基礎類型,也就是說它可以代表任何物件。
  4. 空安全(null-safety)
    =>fun?.call() //fun 不为空时则会被调用
  5. dynamic和Object: dynamic與Object聲明的變量都可以賦值任意對象,且後期可以改變賦值的類型
  6. 函數
    • ex:
String getName(String firstName,String lastName){return firstName+''+lastName;}   `
  //其實就是把function加上型別
void user(String user){
	    if(user != null){
		  print('user is $user');
	    }else{
		  print('user is root');
	    }}
      ===>void user(String user){
          user ??= 'root';
          print('user is $user');
          }
  1. 集合
    • Lists(同義於array) 可在定義時帶入型別或是長度,常見的方法:reversed/contains/map/forEach/
      • fold(用於Lists)
         - ex1
         final numbers=[1,2,3];
         final sum=numbers.fold(
            0,(
               int previousValue,
               int thisValue
               )=>reviousValue+thisValue)));
         =====>result is 6(起始值,作用的fun) 相似於reduce
               final numbers=[1,2,3];
         - ex2
      final names=['a','b','c']; 
      final result=names.fold(
         '',(
            result,
            str,
            )=>'$result ${str.toUpperCase()}');
      =====>result is " A B C"

Collection if & Collection for

      var isRoot = true
      var users = [
      'user1',
      'user2',
      'user3',
      if(isRoot) 'root'];
      const A=['a','b'];const B=['a','b'];===>A=B是true
  • 如果要不能修改可以使用UnmodifiableListView(users)
    • Sets
      • Sets 是沒有索引值、!不可重複!的集合,以hashCode的值來決定是否相等。
      • var sets = <int>{1,2,3,4};//如果初始化未給定型別 dart會自動判斷
      • 範例:
final person1=Person(age:10,name:'foo');
      final person2=Person(age:10,name:'foo');
      final persons={person1,person2};
      print (persons)=====>{Person:foo,10,Person:foo,10}
      此時若加上:bool operator==(Object other)=>
                identical(this,other)||other is Person &&name==other.name&&age==ther.age;
      print (persons)=====>{Person:foo,10}
  • Maps
    + putIfAbsent():當該key(height)無值時插入值170 /或是直接info['height']=180/remove(Object):
    ex:info.putIfAbsent('height',()=>170)
    + update(key, value) containsKey(key) containsValue(value)
    8. enum 是一種型別,用於自定義 type,若用在 switch 上,IDE 會自動告訴我們有哪一個 enum 尚未考慮到。
    9. 宣告class時預設都是public 可加(_)變private,
    10. 使用operator來處理等價與否,
    ex:class Point{
      const Point(this.x,this.y);
      final int x;
      final int y;  
      @override
      String toString()=>'Point($x.$y)';
      @override
      //    print(Point(0,0)==Point(0,0))//result is false so we need to do the next line
      bool operator==(Object other){
         if(other is Point){
           return x==other.x&&y==other.y;
         }
         return false;
      }
   =======>可以寫成這樣~
            bool operator==(covariant Point other){
               return x==other.x&&y==other.y;
               }
    }
​​ Iterable.generate(20,(i)=>getName(i))
​​ for (final name in iterable.take(2)){print(name)}
​​ ========>result 只會print i=0&i=1 因為Iterable是lazy的?

Widgets

  1. StatelessWidget:是一個不需要自己紀錄內部狀態的組件,需要的資料只能靠外部提供。
    (stl or stf vscode打了就會自動生成)
  2. StatefulWidget:是有自己內部狀態的組件,可以建立自己的state也可以接受外部的prop,一個StatefulWidget會分開定義「widget」和「widget所使用的狀態」。
   - class MyHomePage extends StatefulWidget {
   const MyHomePage({super.key, required this.title});
   final String title; // 外部傳入的props
   @override
   State<MyHomePage> createState() => _MyHomePageState();
   }
   - class _MyHomePageState extends State<MyHomePage> {
   int _counter = 0; // state
   void _incrementCounter() {
      setState(() {
         _counter++; // 更動自己的state
      });
   }
   @override
   Widget build(BuildContext context) {
      return Scaffold(
         ......
  1. WHY???由於Flutter 的widget創建後內部的資料是不能更動(immutable)的,要更改畫面必須重新創建,因此採用「創建widget」之後「注入state」方法,由state來掌管可變的狀態。
    此外將state與widget拆分,也可以確保每次widget重製時,state可以獨立於widget之外仍然保留狀態。
  2. 盡量只讓需要變動的組件採用StatefulWidget,而可以單純依照外部資料顯示的組件採用StatelessWidget,才能最有效地分配運算資源。

佈局約束策略

  1. xConstraints go down.
    Child Widget並不是長寬設為多少就會長成多少,必須接受來自上層的Parent Widget最大長寬度的限制。
  2. Sizes go up.
    各個Child Widget皆會將自身期望的長寬包含約束條件向上傳遞給Parent Widget。
  3. Parent sets position.
    Parent統整了所有Child Widget的尺寸資訊後,便開始在畫面上依照大小決定各自的位置

Layout

  1. Single Child Layout Widget
    Align:對齊
    AspectRatio:指定比例
    Center:置中
    ConstratinedBox:長寬限制
    Container:外框容器
    Expanded:盡可能佔據剩餘空間
    Padding:與外框的間隙:

    • fromLTRB(double left, double top, double right, double bottom):分别指定四个方向的填充。
    • all(double value) : 所有方向均使用相同数值的填充。
    • only({left, top, right ,bottom }):可以设置具体某个方向的填充(可以同时指定多个方向)。
    • symmetric({ vertical, horizontal }):用於對稱方向的填充,vertical指top和bottom,horizontal指left和right。
    ​​​     Padding(
    ​​​         //上下各添加8像素
    ​​​         padding: EdgeInsets.symmetric(vertical: 8),
    ​​​         child: Text("I am Jack"),
    ​​​      ),
    

    SingleChildScrollView:加上頁面捲軸

  2. Multi Child Layout Widget
    Column:垂直排列
    GridView:網格式排列
    ListView:列表式排列
    Row:水平排列
    Stack:堆疊排列(後面元素在前)

  3. Leaf Widget
    Widget樹的葉子節點,用於沒有子節點的widget,通常基礎組件都屬於這一類,如Image。

導頁 - navigation

route stack:進入新的頁面,頁面所在的路徑會依序被加入(push)到route stack,退出時也會從「最後進入的頁面」開始退出(pop),從route stack刪除。

  • 返回上一頁
    要返回的頁面設置Navigation.pop()
  • 指定路徑
    routes: {
    '/first': (context) => const FirstScreen(),
    },

Future

當我們呼叫一個非同步函式時,會回傳一個「未完成的Future」來等待此函式的非同步行為執行完畢,並獲得結果,也可能在執行的過程中出現問題而回傳錯誤(promise感) 一樣會有.then .catchError 最後要做的事=> whenComplete
延遲function (setTimeOut感)
Future.delayed(const Duration(seconds: 2), () => print('Here I come!'));

事件處理

  1. 原始指針(pointer):鼠標、觸控筆
    this.onPointerDown, //手指按下回调
    this.onPointerMove, //手指移动回调
    this.onPointerUp,//手指抬起回调
    this.onPointerCancel,//触摸事件取消回调
  2. 手勢:拖拉、雙擊等等
    GestureDetector是一個用於手勢識別的功能性組件,我們通過它可以來識別各種手勢。

Flutter state management: 有很多方法 內建的inherit widget或是provider flutterhook redux等等

  • Provider/Consumer (額外加入 類似redux)
  1. 設定一個「狀態管理組件」(ChangeNotifierProvider),放在想要傳遞資料以及使用資料的組件(在專案中是指MainPage, FavoritePage)的上層。
  2. 接著,「狀態管理組件」會創建一筆「可訂閱的資料模型」(extends ChangeNotifier),也就是我們這次指稱的app state,供各組件訂閱使用(Comsumer)或操作(Provider.of)。
  3. 當資料被修改,ChangeNotifier可以通知訂閱此資料模型的組件,依照最新的資料重繪。
  4. Consumer:在子組件中取用Provider/ChangeNotifierProvider提供的資料

其他:

print=console.log
command + .==>快速wrap或是unwrap
$變數 如果要印$就要加$
void是沒有return東西的function 有return就會在一開始宣告return的東西的型別
在main()的東西不會hot reload 需要restart
只有雙等號而已