Search code examples
androidflutterdartmemorydispose

Flutter) App doesn't dispose before hot reloading


I study flutter nowadays. But I met one problem about dispose.

First I see you my code. It seems long, but it is not difficult code.

main.dart

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: FirstScreen(),
    );
  }
}

class FirstScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        // After hot reload, title will be changed to 'First Screen (After Hot Reload)'
        title: Text('First Screen (Before Hot Reload)', style: TextStyle(fontWeight: FontWeight.bold)),
      ),
      body: SafeArea(
        child: Center(
          child: ElevatedButton(
            onPressed: () {
              Navigator.push(context, MaterialPageRoute(builder: (_) => SecondScreen()));
            },
            child: Text('Go To Second Screen', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 24)),
          ),
        ),
      ),
    );
  }
}


class SecondScreen extends StatefulWidget {

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

class _SecondScreenState extends State<SecondScreen> with TickerProviderStateMixin {
  List<ScrollController> scrollControllers;
  List<AnimationController> animationControllers;
  List<FocusNode> focusNodes;

  @override
  void initState() {
    super.initState();
    scrollControllers = List.generate(10000, (_) => ScrollController());
    animationControllers = List.generate(10000, (_) => AnimationController(vsync: this, duration: Duration(microseconds: 300)));
    focusNodes = List.generate(10000, (_) => FocusNode());
  }

  @override
  void dispose() {
    scrollControllers.forEach((element) { element.dispose();});
    animationControllers.forEach((element) { element.dispose();});
    focusNodes.forEach((element) { element.dispose();});
    print('No Get with dispose');
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        // After hot reload, title will be changed to 'Second Screen (After Hot Reload)'
        title: Text('Second Screen (Before Hot Reload)', style: TextStyle(fontWeight: FontWeight.bold)),
      ),
      body: SafeArea(
        child: Center(child: Text('Second Screen')),
      ),
    );
  }
}

I build this code and watch memory usage of this simple app.

My app memory usage

I run this app with this step.

  1. I run app with debug mode.
  2. I go to second screen. (Then, this app will create 10,000 ScrollController, AnimationController, and FocusNode in initState() method)
  3. I go back to first screen. (I think this app should dispose ScrollController, AnimationController, and FocusNode in dispose() method. But it doesn't.)
  4. I change the code little. Just AppBar's title text. And reload. Then, similar to the beginning of the app, the memory usage has changed.
  5. I go to second screen. It creates things like step 2.
  6. I go back to first screen. At this time, it disposes correctly.

Question Here is my question. Why this app doesn't dispose scrollControllers, animationConrollers, focusNodes at step 3? However, after hot reload this app disposes correctly when it returns to first screen (step 6). It is weird point to me. Why at first build app doesn't dispose, but after hot reload app disposes correctly?


p.s.) I use IntelliJ.

'flutter --version' result

Flutter 1.22.5 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 7891006299 (7 weeks ago) • 2020-12-10 11:54:40 -0800
Engine • revision ae90085a84
Tools • Dart 2.10.4

'flutter doctor' result

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 1.22.5, on Microsoft Windows [Version 10.0.19042.746], locale ko-KR)
[!] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
    ✗ Android license status unknown.
      Run `flutter doctor --android-licenses` to accept the SDK licenses.
      See https://flutter.dev/docs/get-started/install/windows#android-setup for more details.
[!] Android Studio (not installed)
[!] VS Code (version 1.52.1)
    ✗ Flutter extension not installed; install from
      https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter
[✓] Connected device (1 available)

Solution

  • If you have tasks that require special handling on a hot reload, you can provide a reassemble hook (similar to dispose) that runs the code you need:

    // in your State widget:
    
    @override
    void reassemble {
      debugPrint('Captain, we are going down!!!');
      super.reassemble(); // must call
    }
    

    See more at https://api.flutter.dev/flutter/widgets/State/reassemble.html. The docs say this works for debug builds only, because of course, you're not hot reloading on a production build. :)