給Android開發者的Flutter指南 (下) [翻譯]

官方英文原文: https://flutter.io/flutter-for-android/

說明:此文上接 給Android開發者的Flutter指南(上)

四、工程結構與資源

1. 在哪放置不同分辨率(resolution-dependent)的圖片文件?

Android中,resourcesassets是兩個獨立的文件夾,而在Flutter中,只存在assets,所有放在Androidres/drawable-*文件夾中的文件全都放在Flutter中的assets文件夾中。

Flutterios一樣遵循簡單的基於密度(density-base)的格式,assets包含1.0x2.0x3.0x或者更高乘數,Flutter中並沒有dp這一說,而是使用與設備無關的邏輯像素,在devicePixelRatio 中描述了單個邏輯像素與物理像素的關係。

對應於Android密度的關係如下:

Android density qualifier Flutter pixel ratio
ldpi 0.75x
mdpi 1.0x
hdpi 1.5x
xhdpi 2.0x
xxhdpi 3.0x
xxxhdpi 4.0x

assets可以存在與任意文件夾中,Flutter沒有規定文件夾結構,因此即使你將assets放在與pubspec.yaml相同的位置,Flutter也能正確的讀取到。

Flutter 1.0 beta2以前,在Flutter中定義的assets不能被本地層(native層)訪問,同理,本地層的assetsresources文件也不能被Flutter訪問,因爲它們存在於分立的文件夾中。

而從Flutter 1.0 beta2開始,assets存儲於本地層的assets文件夾中,且可以被本地層通過AssetManager訪問,但是Flutter依然不能訪問本地層的resourcesassets

val flutterAssetStream = assetManager.open("flutter_assets/assets/my_flutter_asset.png")

如果向Flutter工程中添加一個叫做my_icon.png的圖片資源,比方說,把它放到一個叫做Images的文件夾中(名字是任意的),那麼我們應該把1.0x的基礎圖片放到Images的根目錄,而其他大小,比如2.0x3.0x等大小的圖片分別放在Images中名字爲2.0x3.0x的子文件夾中,如下示例路徑:

images/my_icon.png       // Base: 1.0x image
images/2.0x/my_icon.png  // 2.0x image
images/3.0x/my_icon.png  // 3.0x image

接着在pubspec.yaml文件中聲明這些圖片資源:

assets:
 - images/my_icon.jpeg

接着就可以通過AssetImage訪問圖片資源了:

return AssetImage("images/a_dot_burr.jpeg");

或者直接在Image控件中使用:

@override
Widget build(BuildContext context) {
  return Image.asset("images/my_image.png");
}

2. 在哪存儲strings字符串資源?怎樣處理本地化?

目前Flutter沒有像系統聲明字符串資源那樣的形式,因此當前最佳方式就是將字符串聲明成static形式,然後存儲在一個特定的類中,之後都從這個類中獲取,如下示例:

class Strings {
  static String welcomeMessage = "Welcome To Flutter";
}

然後在代碼中這樣調用這些字符串資源:

Text(Strings.welcomeMessage)

FlutterAndroid中的輔助功能有了基礎支持,目前工作還在進行中。開發者可以通過查看intl package來獲取關於國際化和本地化的信息。

3. 對應於Gradle文件的是啥?怎麼添加依賴?

Android中,依賴添加在gradle構建腳本中,而Flutter則是使用Dart自己的構建系統和Pub包管理器,Flutter的構建工具會將本地AndroidIOS的構建工作委託到它們各自的構建系統。

gradle文件在Flutter工程目錄的android文件夾下,只有需要針對單個平臺添加本地依賴時才添加到gradle,其他普通場景直接在pubspec.yaml添加外部依賴就好了。找包?上 Pub

五、 Activity 與 fragment

Note: 你幾乎不會想讓Android因爲Flutter應用而重啓activity,因爲它違背了Android文檔中的提出的建議,因此例如需要支持分屏,那麼也需要添加screenLayoutdensity

1. Flutter中與activityfragment相對應的是啥?

Android中,activity代表了用戶的單個焦點所在,而Fragment則代表用戶交互及接口的一個行爲或者說一個部分。Fragment可以模塊化你的代碼,用來爲大屏設備組合出複雜的用戶交互接口、以及比例化應用UI。在Flutter中,這兩者的概念都彙集到在Widget

正如在Intent部分所提到的,在Flutter中,Widget就代表着屏幕,因爲在Flutter中萬物皆Widget。我們使用Navigator來切換Route,而Route代表着不同屏幕或頁面、亦或只是不同狀態、或是相同數據的渲染效果。

2. 如何監聽Android中activity的生命週期事件?

Android中,我們會複寫activity中的生命週期方法,或者在Application中註冊ActivityLifecycleCallbacks,而在Flutter中,並沒有這個概念,但是我們可以通過給WidgetBinding觀察者下個鉤子(hook)來監聽生命週期事件,然後監聽didChangeAppLifecycleState()的變化事件,其生命週期事件如下:

  • inactive: 應用處於非活動狀態,此時不在接收用戶輸入。這個事件只在IOS中有效,因爲在Android中沒有與這個狀態相映射的事件。
  • paused: 當前應用對用戶可見,但不在響應用戶輸入,且運行在後臺。等同於Android中的onPause
  • resumed: 應用可見且正在響應用戶輸入。等同於Android中的onPostResume()
  • suspending: 此時應用掛起了。等同於Android中的onStop;不會觸發IOS上的事件,因爲沒有與之映射的狀態事件。

關於這些狀態的更多細節,請查看 AppLifecycleStatus documentation..

你可能注意到了,只有那麼幾個可用的Android生命週期事件。這是因爲FlutterActivity已經捕獲了幾乎所有的生命週期事件,並將它們傳送到了Flutter引擎中,然後很多事件都被它屏蔽掉了。Flutter會管理引擎的啓動和關閉動作,因而大多數情況下我們都沒多大必要在Flutter層監聽activity生命週期事件。如果要監聽或者釋放本地層的資源,那麼可以在本地層以任何頻率進行。

以下示例描述瞭如何監聽Activity中的生命週期事件:

import 'package:flutter/widgets.dart';

class LifecycleWatcher extends StatefulWidget {
  @override
  _LifecycleWatcherState createState() => _LifecycleWatcherState();
}

class _LifecycleWatcherState extends State<LifecycleWatcher> with WidgetsBindingObserver {
  AppLifecycleState _lastLifecycleState;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    setState(() {
      _lastLifecycleState = state;
    });
  }

  @override
  Widget build(BuildContext context) {
    if (_lastLifecycleState == null)
      return Text('This widget has not observed any lifecycle changes.', textDirection: TextDirection.ltr);

    return Text('The most recent lifecycle state this widget observed was: $_lastLifecycleState.',
        textDirection: TextDirection.ltr);
  }
}

void main() {
  runApp(Center(child: LifecycleWatcher()));
}

六、佈局

1. 對應於LinearLayout的是啥?

Android中,LinearLayout用於橫向和縱向佈局控件,而在Flutter中則是使用RowColumn控件來實現與之相同的行爲。

如下示例,當佈局中子控件重複利用率比較高時,使用這種容器控件就很方便了:

@override
Widget build(BuildContext context) {
  return Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Text('Row One'),
      Text('Row Two'),
      Text('Row Three'),
      Text('Row Four'),
    ],
  );
}
@override
Widget build(BuildContext context) {
  return Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Text('Column One'),
      Text('Column Two'),
      Text('Column Three'),
      Text('Column Four'),
    ],
  );
}

更多線性佈局細節,請看這裏 Flutter For Android Developers : How to design LinearLayout in Flutter?.

2. 對應於RelativeLayout的是啥?

Flutter實現與之相同效果的方法很少。可以通過組合ColumnRowStack控件來實現類似效果,也可以通過控件的構造器指定其子控件在它內部的佈局規則來實現。

關於如何構建一個RelativeLayout,可以查看這裏 StackOverflow.

3. 對應於ScrollView的是啥?

Flutter中,實現ScrollVIew的最簡單方式就是使用LsitView。這看起來好像有點誇張,但是在Flutter中,ListView控件既是Android中的ScrollView,也是ListView

@override
Widget build(BuildContext context) {
  return ListView(
    children: <Widget>[
      Text('Row One'),
      Text('Row Two'),
      Text('Row Three'),
      Text('Row Four'),
    ],
  );
}

4. 在Flutter中如何處理屏幕旋轉?

AndroidManifest.xml中添加如下配置即可:

android:configChanges="orientation|screenSize"

七、檢測手勢與觸摸事件處理

1. 如何爲控件添加 OnClick 事件監聽器?
Android中,是通過調用ViewsetOnClickListener方法綁定監聽器的,而在Flutter中可以有以下兩種添加觸摸事件監聽器的方式:

  • 如果控件支持事件檢測,那麼可以給它傳入一個函數用以處理這個事件。比如,RaisedButton包含一個onPressd參數:
@override
Widget build(BuildContext context) {
  return RaisedButton(
      onPressed: () {
        print("click");
      },
      child: Text("Button"));
}
  • 如果控件並不支持事件檢測,那麼可以將這個控件包裹在GestureDetector中,然後傳入一個函數給onTap參數:
class SampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
      child: GestureDetector(
        child: FlutterLogo(
          size: 200.0,
        ),
        onTap: () {
          print("tap");
        },
      ),
    ));
  }
}

2. 如何處理控件中的其他手勢?

使用GestureDetector可以監聽大量手勢,例如:

  • 單擊(Tab)

    • onTabDown: 觸發單擊事件的指針已經開始與屏幕在特定點上進行聯繫
    • onTapUp: 觸發單擊事件的指針停止與屏幕在特定點上的聯繫
    • onTap: 形成了單擊事件
    • onTapCancel: 觸發時會導致之前觸發onTabDown的指針無法形成單擊事件
  • 雙擊(Double Tab)

    • onDoubleTab: 用戶在屏幕的同一個點上連續快速點擊了兩次
  • 長按Long Press

    • onLongPress: 指針持續在同一個位置上與屏幕進行了一段事時間的聯繫。
  • 垂直拖動(Vertical drag)

    • onVerticalDragStart: 指針已經和屏幕聯繫,並且可能開始垂直移動。
    • onVerticalDragUpdate: 正在和屏幕聯繫的指針已經開始在垂直方向上進行移動。
    • onVerticalDragEnd: 之前與屏幕進行聯繫且在垂直方向移動的指針,現在已經不需要再與屏幕聯繫了,並且在停止聯繫的瞬間,指針依然以一定的速度移動。
  • 水平拖動(Horizontal drag)(請參考上面的垂直拖動)

    • onHorizontalDragStart
    • onHorizontalDragUpdate
    • onHorizontalDragEnd

下面示例描述了在使用GestureDetector雙擊Flutter logo時,logo旋轉的效果:

AnimationController controller;
CurvedAnimation curve;

@override
void initState() {
  controller = AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
  curve = CurvedAnimation(parent: controller, curve: Curves.easeIn);
}

class SampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
          child: GestureDetector(
            child: RotationTransition(
                turns: curve,
                child: FlutterLogo(
                  size: 200.0,
                )),
            onDoubleTap: () {
              if (controller.isCompleted) {
                controller.reverse();
              } else {
                controller.forward();
              }
            },
        ),
    ));
  }
}

八、ListView 與 Adapter

1. 在Flutter中,替代ListView的是啥?

Flutter中,等效於ListView的是...ListView

對於AndroidListView,我們會創建一個適配器(adapter)傳給ListView,然後ListView渲染這個適配器返回的每一行數據。而我們必須確保每行數據最後都被我們回收,否則就可能會導致顯示錯亂和內存問題。

而因爲Flutter的控件是不可變的,我們給ListView傳入一個集合的控件,然後Flutter就會自己確保滑動的流暢、快速了。

import 'package:flutter/material.dart';

void main() {
  runApp(SampleApp());
}

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Sample App"),
      ),
      body: ListView(children: _getListData()),
    );
  }

  _getListData() {
    List<Widget> widgets = [];
    for (int i = 0; i < 100; i++) {
      widgets.add(Padding(padding: EdgeInsets.all(10.0), child: Text("Row $i")));
    }
    return widgets;
  }
} 

2. 我怎麼知道列表的哪個條目被點擊了呢?
Android中,ListView中包含了查找點擊了哪個條目的onItemClickListener監聽器,而在Flutter中,則是通過傳入列表的控件來處理觸摸動作。

import 'package:flutter/material.dart';

void main() {
  runApp(SampleApp());
}

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Sample App"),
      ),
      body: ListView(children: _getListData()),
    );
  }

  _getListData() {
    List<Widget> widgets = [];
    for (int i = 0; i < 100; i++) {
      widgets.add(GestureDetector(
        child: Padding(
            padding: EdgeInsets.all(10.0),
            child: Text("Row $i")),
        onTap: () {  // 給列表中的每個控件添加一個點擊事件監聽器
          print('row tapped');
        },
      ));
    }
    return widgets;
  }
}

3. 如何動態更新ListView?

Android中是通過更新適配器,然後調用notifyDataSetChanged來處理。

而在Flutter中,如果你是在setState()方法中更新控件集,那麼你會很快看到你的數據並沒有被更新,這是因爲當setState()被調用時,Flutter渲染引擎會在控件樹中搜索是否存在發生改變的東西,而當它找到了你的ListView,它會進行==檢查,然後判定這兩個ListView是相同的,因而不會發生任何改變,也就不會請求更新。

一個簡單更新ListView的方法就是,在setState()方法中創建一個新的List,然後將舊集合的數據拷貝過來。這一解決方案是簡單,但是不推薦在數據量大的時候使用,如下示例:

import 'package:flutter/material.dart';

void main() {
  runApp(SampleApp());
}

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  List widgets = [];

  @override
  void initState() {
    super.initState();
    for (int i = 0; i < 100; i++) {
      widgets.add(getRow(i));
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Sample App"),
      ),
      body: ListView(children: widgets),
    );
  }

  Widget getRow(int i) {
    return GestureDetector(
      child: Padding(
          padding: EdgeInsets.all(10.0),
          child: Text("Row $i")),
      onTap: () {
        setState(() {
          widgets = List.from(widgets);
          widgets.add(getRow(widgets.length + 1));
          print('row $i');
        });
      },
    );
  }
}

一個高效且有效的方式是通過ListView.Builder來建立ListView,這種方式在你的數據量巨大或者需要動態改變數據的情況下非常有用,這實質上跟Android中的RecyclerView等價了,因爲它會自動回收列表數據:

import 'package:flutter/material.dart';

void main() {
  runApp(SampleApp());
}

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  List widgets = [];

  @override
  void initState() {
    super.initState();
    for (int i = 0; i < 100; i++) {
      widgets.add(getRow(i));
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Sample App"),
        ),
        body: ListView.builder(
            itemCount: widgets.length,
            itemBuilder: (BuildContext context, int position) {
              return getRow(position);
            }));
  }

  Widget getRow(int i) {
    return GestureDetector(
      child: Padding(
          padding: EdgeInsets.all(10.0),
          child: Text("Row $i")),
      onTap: () {
        setState(() {
          widgets.add(getRow(widgets.length + 1));
          print('row $i');
        });
      },
    );
  }
}

與創建ListView不同的是,創建ListView.Build需要傳入兩個主要參數,一個是初始列表的長度,一個是itemBuild函數(構建列表條目的函數)。

itemBuildAndroid中的adapter#getView()方法類似,它給你一個位置,然後需要返回你需要在對應位置上渲染的行。

最後注意到, onTab函數已經不需要在創新創建數據列表了,取而代之的是通過.add()來添加到其中。

九、文字處理

1. 如何給文字控件設置字體?

Android SDK(Android O),可以創建一個Font資源,然後作爲FontFamily參數傳入TextView。而在Flutter中,則是將字體文件放到一個文件夾中,然後在pubspec.yaml中引用即可,跟引入圖片是類似的。

fonts:
   - family: MyCustomFont
     fonts:
       - asset: fonts/MyCustomFont.ttf
       - style: italic

然後在Text控件中使用:

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text("Sample App"),
    ),
    body: Center(
      child: Text(
        'This is a custom font text',
        style: TextStyle(fontFamily: 'MyCustomFont'),
      ),
    ),
  );
}

2. 如何給Text控件定義風格?
除了字體,我們還可以定義Text控件的其他風格屬性,Text控件的風格參數中包含一個TextStyle對象,我們可以定義它的很多參數,例如:

  • color
  • decoration
  • decorationColor
  • decorationStyle
  • fontFamily
  • fontSize
  • fontStyle
  • fontWeight
  • hashCode
  • height
  • inherit
  • letterSpacing
  • textBaseline
  • wordSpacing

十、表單輸入(Form input)

有關表單的更多信息請查看Flutter cookbook 中的這裏Retrieve the value of a text field

1. 對應於輸入框中的“hint”的是啥?
Flutter中,可以通過給Text控件傳入一個InputDecoration對象來顯示“hint”或者佔位字符,如下示例:

body: Center(
  child: TextField(
    decoration: InputDecoration(hintText: "This is a hint"),
  )
)

2. 如何展示驗證錯誤信息?

和顯示hint一樣,傳一個InputDecoration對象給Text控件的構造函數即可。

但是你肯定不想一開始就顯示錯誤,而是在出現錯誤時才顯示,因此發生錯誤時傳入一個新的InputDecoration對象即可。

import 'package:flutter/material.dart';

void main() {
  runApp(SampleApp());
}

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  String _errorText;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Sample App"),
      ),
      body: Center(
        child: TextField(
          onSubmitted: (String text) {
            setState(() {
              if (!isEmail(text)) {
                _errorText = 'Error: This is not an email';
              } else {
                _errorText = null;
              }
            });
          },
          decoration: InputDecoration(hintText: "This is a hint", errorText: _getErrorText()),
        ),
      ),
    );
  }

  _getErrorText() {
    return _errorText;
  }

  bool isEmail(String em) {
    String emailRegexp =
        r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';

    RegExp regExp = RegExp(emailRegexp);

    return regExp.hasMatch(em);
  }
}

十一、Flutter Plugins

1. 如何訪問GPS傳感器?

使用geolocator社區插件

2. 如何訪問相機?
使用這個image_picker

3. 如何登錄Facebook?

使用 flutter_facebook_login

4. 如何使用Firebase ?
大部分Firebase函數轉換自 first party plugins,以下是第一批集成的插件,由Flutter團隊維護:

4. 如何構建自定義的 Native integration(本地集成)

如果沒有找到我們想要的插件,那麼可以查看the developing packages and plugins,學習如何構建我們自己的插件。

Flutter的插件架構,類似於EventBus:發出消息給接收器處理,接收器處理後將結果返回過來。在這裏,接收器代碼運行在本地層,也就是AndroidIOS.

5. 如何在Flutter應用中使用NDK?

如果你已經在Android應用中使用了NDK,並且想要讓Flutter也能使用到這些類庫,那麼就需要構建自定義插件了。

首先讓自定義的插件能與Android應用交互,即在Android應用中通過JNI調用native函數,一旦拿到拿到結果了,就回送給Flutter,然後渲染結果。

目前不支持直接從Flutter中調用native代碼。

十二、 主題(themes)

1. 如何給應用定製主題?

Flutter自帶Material Design風格的主題,不像Android可以在XML文件中聲明主題,然後在AndroidManifest.xml中使用。在Flutter中是在頂層控件中聲明主題的。

如果想要在應用中充分使用Material組件,那麼可以使用MaterialApp作爲應用的入口。MaterialApp包含有大量的實現了Material Design風格的通用控件,它建立在WidgetsApp之上,只是添加了具有Material特性的功能。

同樣也可以使用WidgetsApp作爲應用控件,它提供了一些相同的功能,但不如MaterialApp豐富。

想要在任意子組件上自定義顏色和風格的話,那麼給MaterialApp傳入一個ThemeData對象,例如,在下面示例中,初始樣本顯示爲藍色,而文字選擇後顯示爲紅色。

class SampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        textSelectionColor: Colors.red
      ),
      home: SampleAppPage(),
    );
  }
}

十三、數據庫與本地存儲

1. 怎樣訪問Shared Preference?

Flutter中,可以通過Shared_Preferences plugin來實現這些功能,這個插件包含了Shared PreferencesNSUserDefaultsios平臺)

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        body: Center(
          child: RaisedButton(
            onPressed: _incrementCounter,
            child: Text('Increment Counter'),
          ),
        ),
      ),
    ),
  );
}

_incrementCounter() async {
  SharedPreferences prefs = await SharedPreferences.getInstance();
  int counter = (prefs.getInt('counter') ?? 0) + 1;
  print('Pressed $counter times.');
  prefs.setInt('counter', counter);
}

2. 如何訪問SQLite數據庫?

使用SQFlite插件

十五、通知

1. 如何設置推送通知?

Android中可以使用Firebase Cloud Messaging給應用設置推送通知。

Flutter中,通過Firebase_Messaging可以使用到這一功能,更多關於使用Firebase Cloud Messaging API的信息,請查看firebase_messaging插件文檔。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章