Flutter学习指南:编写第一个应用

Flutter

Google 推出的移动端跨渠道开发框架,运用的编程语言是 Dart。从 React Native 到 Flutter,开发者对跨渠道解决方案的探究从未停止,究竟,它能够让咱们节省移动端一半的人力。本篇文章中,咱们就经过编写一个简略的 Flutter 来了解他的开发流程。

这儿咱们要开发的 demo 很简略,只是在屏幕中心放一个按钮,点击的时分,模拟摇两个骰子并弹窗显现成果。咱们撸起袖子开干吧。

创立项目

咱们这儿假定读者已经装置好 Flutter,并且运用装置了 Flutter 插件的 Android Studio 进行开发。

下面咱们开端创立项目:

  1. 挑选 File > New > New Flutter project…
  2. 在接下来弹出的挑选面板里,挑选 Flutter Application
  3. 这儿填运用的基本信息。Project name 咱们就写 flutter_demo 好了。这儿要注意的是,Project name 有必要是一个合法的 Dart 包名(小写+下划线,能够有数字)。填好以后点击 next,然后 finish。

榜首次创立项目时,因为要下载 gradle,时间会略微长一些。

编写代码(1)

在上一末节里咱们所创立的项目,已经有了一些代码,感兴趣的读者能够跑到自己手机上看一看,相关的代码在 lib/main.dart 里边。

为了体会从头开发一个运用的进程,这儿咱们先把 lib/main.dart 里的内容都删去。

首先,创立一个 main 函数。跟其他语言相同,main 函数是运用的进口:

void main() {
}

下面咱们编写一个 Widget 作为咱们的 app。在 Flutter 里,一切的东西都是 Widget

import 'package:flutter/material.dart';
void main() { 
// 创立一个 MyApp
 runApp(MyApp());
}
/// 这个 widget 效果这个运用的顶层 widget.
///
/// 这个 widget 是无状况的,所以咱们承继的是 [StatelessWidget].
/// 对应的,有状况的 widget 能够承继 [StatefulWidget]
class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) { 
  // 创立内容
  }
}

现在咱们进入正题,完成一个按钮,在点击的时分弹框显现成果:

@override
Widget build(BuildContext context) {
 // 咱们想运用 material 风格的运用,所以这儿用 MaterialApp
 return MaterialApp(
  // 移动设备运用这个 title 来表明咱们的运用。具体一点说,在 Android 设备里,咱们点击
  // recent 按钮翻开最近运用列表的时分,显现的便是这个 title。
  title: 'Our first Flutter app',
  // 运用的“主页” 
  home: Scaffold(
   appBar: AppBar(
    title: Text('Flutter rolling demo'), 
   ), 
   // 咱们知道,Flutter 里一切的东西都是 widget。为了把按钮放在屏幕的中央,
   // 这儿运用了 Center(它是一个 widget)。
   body: Center( 
    child: RaisedButton( 
    // 用户点击时分调用
     onPressed: _onPressed, 
     child: Text('roll'),
     ),
    ),
   ),
  );
}
void _onPressed() {
 // TODO
}

装置、调试(1)

现在,点击 Run,把咱们的榜首个 Flutter 运用跑起来吧。没有意外的话,你会看到下面这个页面:

Flutter学习指南:编写第一个应用

image.png

假如你遇到了什么困难,能够检查 tag first_app_step1 的代码:

git clone https://github.com/Jekton/flutter_demo.git
cd flutter_demo
git checkout first_app_step1

因为是榜首次写 Flutter 运用,咱们对上面的代码是否能够按照预期履行还不是那么有信心,所以咱们先打个 log 确认一下,点击按钮后是不是真的会履行 onPress

打 log 能够运用 Dart 供给的 print,但在日志比较多的时分,print 的输出或许会被 Android 丢掉,这个时分 debugPrint 会是更好的挑选。对应的日志信息能够在 Dart Console 里检查(View -> Tool Windows -> Run 或者 Mac 上运用 Command+4 翻开)。

void _onPressed() {
 debugPrint('_onPressed');
}

保存后(会自动 Hot Reload),咱们再次点击按钮,在我的设备上,打印出了下面这样的信息:

I/flutter (11297): _onPressed
V/AudioManager(11297): playSoundEffect  effectType: 0
V/AudioManager(11297): querySoundEffectsEnabled...

这儿的榜首行,便是咱们打的。现在咱们有足够的自信说,点击按钮后,会履行 _onPressed 办法了。

编写代码(2)

软件开发通常是一个螺旋式上升的进程,不或许经过一次编码、调试就完结。现在,咱们开端第二轮迭代。

接下来要做的,便是在 _onPressed 里边弹一个框:

// context 这儿运用的是 MyApp.build 的参数
void _onPressed(BuildContext context) {
 debugPrint('_onPressed');
 showDialog(
  context: context,
  builder: (_) {
   return AlertDialog(
    content: Text('AlertDialog'),
    );
   }
  );
}

遗憾的是,这一次并不那么顺畅。Dialog 没有弹出来,而且报了下面这问题:

I/flutter (11297): Navigator operation requested with a context that does not include a Navigator.
I/flutter (11297): The context used to push or pop routes from the Navigator must be that of a widget that is a
I/flutter (11297): descendant of a Navigator widget.

原因在于,stateless 的 widget 只能用于显现信息,不能有其他动作。所以,该让 StatefulWidget 上场了。

class RollingButton extends StatefulWidget {
  // StatefulWidget 需求完成这个办法,回来一个 State
  @override
  State createState() {
    return _RollingState();
  }
}
// 或许看起来有点厌恶,这儿的泛型参数居然是 RollingButton
class _RollingState extends State<RollingButton> {
  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      child: Text('Roll'),
      onPressed: _onPressed,
    );
  }
  void _onPressed() {
    debugPrint('_RollingState._onPressed');
    showDialog(
        // 榜首个 context 是参数名,第二个 context 是 State 的成员变量
        context: context,
        builder: (_) {
          return AlertDialog(
            content: Text('AlertDialog'),
          );
        }
    );
  }
}

要完成一个 stateful 的 widget,能够承继 StatefulWidget 并在 createState 办法中回来一个 State。除了这一部分,代码跟咱们之前写的并没有太大的区别。

剩下的,便是替换 MyApp 里边运用的按钮,修正后的代码如下:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Our first Flutter app',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter rolling demo'),
        ),
        body: Center(
          child: RollingButton(),
        ),
      ),
    );
  }
}

再次运转,点击按钮后,咱们将看到梦寐以求的 dialog。

Flutter学习指南:编写第一个应用

image.png

假如你遇到了麻烦,能够检查 tag first_app_step2 的代码。

最终,咱们来完成“roll”:

import 'dart:math';
class _RollingState extends State<RollingButton> {
  final _random = Random();
  // ...
  List<int> _roll() {
    final roll1 = _random.nextInt(6) + 1;
    final roll2 = _random.nextInt(6) + 1;
    return [roll1, roll1];
  }
  void _onPressed() {
    debugPrint('_RollingState._onPressed');
    final rollResults = _roll();
    showDialog(
        // 榜首个 context 是参数名,第二个 context 是 State 的成员变量
        context: context,
        builder: (_) {
          return AlertDialog(
            content: Text('Roll result: (${rollResults[0]}, ${rollResults[1]})'),
          );
        }
    );
  }
}

装置、调试(2)

仍是相同,重新运转后,咱们就能够看到每次点击按钮的成果随机地出现 [1, 6] 中的数……慢着,怎样弹出的消息里的两个号码总是相同的!好吧,肯定是哪里出错了。

这次,咱们不选用打 log 的办法,改用 debugger 来调试。

1. 在 final rollResults = _roll() 这一行打个断点 2. 然后点击 Debug main.dart 开端调试 3. 点击 APP 里的 Roll 按钮

现在,运用停在了咱们所打的断点处:

Flutter学习指南:编写第一个应用

image.png

接下来:

1. Step Into 进入 _roll 办法 2. 进入 _roll 后,Step Over 一行一行履行。

Flutter学习指南:编写第一个应用

image.png

这儿咱们看到,两次 random 的确发生了不同的成果。咱们继续:

  1. 仍是 Step Over,这个时分 _roll 就回来了
  2. 切换到 Variables 这个选项卡,检查 rollResults 的值

Flutter学习指南:编写第一个应用

image.png

能够发现,两个成果居然变成相同的了。再往回检查一下代码,咱们写的是 return [roll1, roll1]。修正后一个为 roll2,程序就能够按预期的正常履行了。

最终的代码,能够看 tag first_app_done。

调试总结

本篇文章其实介绍了两种调试办法:打 log 和 debugger。尽管现在 Flutter 供给的 log 工具比较粗陋,能够预期未来还会进一步完善。

运用打 log 的方法,好处在于不会对履行流程发生较大的影响,在多线程环境尤为有用。它的速度也比较快,不需求咱们去单步履行。不足之处在于,假如原先没有对应的 log,咱们只能修正代码重新运转,才能检查相应的状况。关于线上的运用,咱们也只能够经过剖析 log 来定位问题。

debugger 跟打 log 方法是互补的。运用 debugger 时,咱们能够随意检查咱们需求知道的变量的值,一步一步近距离观察代码的运转状况。害处当然便是太慢了。在什么时分运用什么办法,需求一些经历;但有时分就全凭个人喜好了,没有好坏之分。

打包

编写完运用后,就得打包 apk 分发给用户运用了。在这一末节,咱们来看看怎样给 Flutter 项目打包。

在项目的根目录,有一个 android 文件夹,下面咱们将主要对这个目录的文件进行修正。

  1. 检查 AndroidManifest.xml。这是一个按模板生成的文件,有些东西或许需求修正一下
  2. build.gradle,这儿面也或许有你需求修正的地方。对咱们的运用来说,目前都先保持原样
  3. 假如有需求,更新 res/mipmap 里的运用发动图标,这儿咱们不改
  4. 签名,前面略微复杂一些,下面详细展开一下。
  5. 生成签名的 key(假如你已经有了,越过这一步),为了让读者也能够编译,这儿我把 key 也放到了项目中。
keytool -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key仿制代码
  1. 增加一个 android/key.properties,内容如下:
storePassword=123456
keyPassword=123456
keyAlias=keystore
File=../key.jks
  1. 更新 build.gradle 里的签名配置
def keystorePropertiesFile = rootProject.file("key.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android {
  // ...
  signingConfigs {
    release {
      keyAlias keystoreProperties['keyAlias']
      keyPassword keystoreProperties['keyPassword']
      storeFile file(keystoreProperties['storeFile'])
      storePassword keystoreProperties['storePassword']
     }
   }
  buildTypes {
    release {
      signingConfig signingConfigs.release
      minifyEnabled true
      useProguard true
      // proguard 文件咱们鄙人一步增加
      proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
     }
   }
}

签名信息配置结束后,下面进行第5步。

  1. 增加 android/app/proguard-rules.pro:
#Flutter Wrapper
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.**  { *; }
-keep class io.flutter.util.**  { *; }
-keep class io.flutter.view.**  { *; }
-keep class io.flutter.**  { *; }
-keep class io.flutter.plugins.**  { *; }
  1. 编译 apk。在项目的根目录,履行 flutter build apk, 编译后的运用在 build/app/outputs/apk/release/app-release.apk。
  2. 仍是在根目录下,履行 flutter install 就能够装置这个 apk 了。