🌊

【Flutter】ホットリロードとは何なんですか

2022/10/10に公開約4,000字

ホットリロードてなんやと思ったので調べてみた。公式を大いに引用。
公式リファレンス・ホットリロード
AndroidStudio IconReference

ホットリロードとは

Flutter’s hot reload feature helps you quickly and easily experiment, build UIs, add features, and fix bugs. Hot reload works by injecting updated source code files into the running Dart Virtual Machine (VM). After the VM updates classes with the new versions of fields and functions, the Flutter framework automatically rebuilds the widget tree, allowing you to quickly view the effects of your changes.

つまり

更新されたコード実行中のDart仮想マシン(VM)に挿入し、VMがウィジェット ツリーを自動的に再構築し、画面を更新する

こと

ホットリロード、ホットリスタート、フルリスタートの違い

  • ホット リロード(hot reload):Widgetの再構築を行う→buildメソッドを実行するのみで状態を維持しつつmain()メソッドやinitState()メソッドは実行されない

  • ホット リスタート(hot restart):Flutterアプリの状態を失い(Dart部分)、再起動する。main()メソッドやinitState()メソッドも再実行される。
    (flutter runとやっていることはあまり変わらないのかな?と思う。flutter run の方がやる範囲は少し大きそうだけど。ここはまだ認識甘い)

  • フル リスタート(full restart):完全に再起動(stop → run)する。のでiOS、Android、Webアプリを再起動すると言うこと。つまりFlutter部分以外のネイティブコード(Java/ Kotlin/ObjC/Swift)も再コンパイルするので比較的時間を要する。

どういう時にどんなリスタートをするべきか

ホットリロード > ホットリスタート > フルリスタートの順で「軽めの再構築」らしいね。以下ではどういう時にどんなリスタートをするべきか考える
サンプルコードは一番下に置いとく。

1. ネイティブコードが変更された時は

  • ネイティブコード(Kotlin、Java、Swift、Objective-Cなど)を変更した場合は、フルリスタート・完全な再起動(アプリを停止(stop)して再起動(run)する)を実行して、変更が有効になることを確認する必要がある。

上記のフルリスタートの仕組みによる。ホットリロード、ホットリスタートではそこまでの変更を見ない。特に外部APIを使用するときにXcode(の権限系とか?)を編集するとネイティブコードが書き変わったりする。そこで正常なはずなのに例外などが出た場合は、フルリスタートすると治ることが多い.(AndroidStudioの赤四角でApp stopして → Debug run/run)

2. カウンターの値をもとに戻したい時は

  • カウンターの値をもとに戻したい場合は、ホットリスタート(flutterアプリ部分を再起動し、状態も一度破棄)を実行して、状態(ここでは_counter)をリセットする。(hot restartボタン押す)
    main()関数やinitState()からやり直すのでその中にある_counter変数も一回破棄されるわけだ

  • AndroidStudio の hot restart を押すと以下

状態が変わった。

3. カウンターの値を戻さず描画の確認をしたい時は

  • カウンターの値を戻さず描画の確認をしたい時は場合は、ホットリロード(buildメソッドを実行するだけなので状態は保持)を実行して、_counter状態を保持しつつ、再描画する。(hot relord おす or command + sで保存)

  • AndroidStudio で command + s を押すと以下

状態は変わらんかった。

保存時にホットリスタートできるから状態を維持しつつ画面がどんどん円滑に再描画できるのでコスパが良く、UIの確認も超楽なのがいい点よね

Sample Code

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

class DefaultCodePage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;
  
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.bodyLarge,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}
GitHubで編集を提案

Discussion

ログインするとコメントできます