リファレンス
Reference

ホットリロードの仕方

URLをコピーする Twitterでシェアする Facebookでシェアする

Hot Reload と Hot Restart を使って開発効率を上げていきましょう。

概要

Hot Reload と Hot Restart はプログラムの変更を即座に画面に反映させる機能であります。

Android と iOS のネイティブ開発言語にはこれらの機能はありません。

そのため、文章の一部変更など微々たる変更を加えただけでも、アプリを 0 からビルドする必要があり、長い待ち時間が発生してしまいます。

この 2 つの機能をマスターできれば、開発時間を大幅に削減することができるので、理解をしてどんどん活用していきましょう。

使用方法

ここでは、Android Studio を用いて、Hot Reload と Hot Restart の実行方法を説明していきます。

アプリのビルド方法によってそれぞれの実行方法は異なりますが、効果は全く同じです。

ターミナルで flutter run でビルドする場合

Android Studio のターミナルから flutter run でアプリをビルドすると、ターミナル上には以下のログが表示されます。

このログに Hot Reload や Hot Restart のショートカットキーが記載されています。

そこで、マウスでターミナルをクリックしてフォーカスした状態で、キーボードの r を押すと、Hot Reload が実行されます。

同様に、R (shift + r) を押すと、 Hot Restart が実行されます。

Android Studio の GUI からビルドする場合

アプリを Android Studio の GUI (Graphic User Interface) ツールバーから起動した場合、同じツールバーに Hot Reload と Hot Restart ボタンを押せば実行できます。

ツールバーとは、以下の部分を指します。

稲妻のアイコンを押すと、Hot Reload が実行されます。

再生のアイコンを押すと、Hot Restart が実行されます。

また、Android Studio では、 スクリプトを保存する度に自動的に Hot Restart が実行されるように設定されています。

そのため、このアイコンを押さなくとも、保存する度に Hot Restart が実行されるということです。

両者の違い

Hot Reload と Hot Restart は共にコード変更を直ちに反映させられますが、その間には大きな違いがあります。

再ビルドの波及範囲

Hot Reload は Widget ツリーにある Widget の build() 関数のみを再実行して変化を反映させます。 その一方で、 Hot Restart は dart の main() 関数を再実行します。すべての .dart ファイルに定義されたグローバル変数とクラスにある static 変数をデフォルトの状態にします。

状態の保持

Hot Reload は Widget を生成する build() 関数のみを再実行するので、Widget の状態 (State) は保持されます。したがって、Hot Reload を "Stateful Reload" と考えてもいいでしょう。

しかし、Hot Restart はアプリ全体をゼロからビルドするので、各 Widget の状態を保持することはできません。

グローバル変数及び static 変数の更新

以下のように定義されたグローバル変数やあるクラスに定義されている static 変数に変更を加えた際に、Hot Restart を用いて変更を反映させる必要があります。

import 'package:flutter/material.dart';

const String globalString = 'グローバル変数';

class MyClass {
  static const staticField = 'static変数';
}

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

...

実例でその違いを理解しましょう

ここで、簡単な Flutter アプリを使って、Hot Reload と Hot Restart はそれぞれどの部分のコードが実行されるかを見て、両者の違いを理解していきましょう。

コードは以下のように、どの関数が実行されているかが分かるように、print() を使用しています。

void main() {
  print('in main()');
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('in MyApp build()');
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

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

  @override
  _MyHomePageState createState() {
    print('in MyHomePage createState()');
    return _MyHomePageState();
  }
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter;

  @override
  void initState() {
    print('in _MyHomePageState initState()');
    _counter = 0;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    print('in _MyHomePageState build()');
    return Scaffold(
      body: Center(
        child: Text(
          _counter.toString(),
        ),
      ),
    );
  }
}

以上のコードでアプリをビルドすると、次のログが返されます。

Running Gradle task 'assembleDebug'...                                  
Running Gradle task 'assembleDebug'... Done                        27.2s
✓ Built build/app/outputs/flutter-apk/app-debug.apk.
Installing build/app/outputs/flutter-apk/app.apk...                 6.6s
I/flutter (19765): in main()
I/flutter (19765): in MyApp build()
Waiting for Pixel 4a to report its views...                         11ms
I/flutter (19765): in MyHomePage createState()                          
I/flutter (19765): in _MyHomePageState initState()                      
I/flutter (19765): in _MyHomePageState build()                          
Syncing files to device Pixel 4a...                                276ms

この状態で、 Hot Reload すると、以下のログが返されます。

前述のように、Hot Reload は各 Widget の build 関数のみを再実行します。そして、main()及び状態に関わる関数は実行されません。

Performing hot reload...                                               
I/flutter (19765): in MyApp build()
I/flutter (19765): in _MyHomePageState build()                                                                                                                                                                                                                           
Reloaded 0 of 529 libraries in 147ms.

また、 Hot Restart をした場合、ログは以下のようになります。

Hot Reload と違って、 dart の main()関数から実行されて、Widget の build()に加えて initState()のような状態関連の関数も再び実行されています。

つまり、アプリの状態は完全にデフォルトの状態にされています。

Performing hot restart...                                               
Restarted application in 1,168ms.
I/flutter (19765): in main()
I/flutter (19765): in MyApp build()
I/flutter (19765): in MyHomePage createState()
I/flutter (19765): in _MyHomePageState initState()
I/flutter (19765): in _MyHomePageState build()

ビルド時間の比較

Hot Reload と Hot Restart の一番の利点は変化の反映時間を短縮できるところなので、どれくらい短縮できるかをここで比較してみました。

比較対象は、以下の 3 項目になります。

  1. アプリを 0 からビルドする
  2. Hot Reload
  3. Hot Restart

テストコードは、前の例で使用したものを使いました。

ビルド方法 1回目 2回目 3回目
0 からビルド 34s 33s 34s
Hot Reload 0.14s 0.14s 0.16s
Hot Restart 1.2s 1.1s 1.1s

検証マシン:MacBook Pro, CPU : Intel Core i5, RAM : 16GB, macOS: Catalina 10.15.7

ビルドのスピードに関して、 Hot Reload と Hot Restart の方が圧倒的に早いことが一目瞭然です。

今回のテストコードのように単純なことしか処理しないアプリでも、 0 からビルドした場合には 30 秒程度かかります。

そのため、より複雑なアプリになればなるほど、Hot Reload と Hot Restart 機能の恩恵がどんどん大きくなります。

参考リンク