How to rebuild Flutter widgets when device size changes, in release mode for Flutter View in Android app?

3,033

Solution 1

This is a bit of a timing difference between release and debug mode. Probably because in release mode the app is starting quicker and the system takes some time to provide the size.

So in release mode you at first get 0, 0 and after a short while it is updated to the actual size. All you need to do is to ensure that the code doesn't cause an exception when the size is 0, 0. For example return an empty Container or similar.

Solution 2

I ended up resolving this by retrieving the size only in a StatefulWidget. This way I am notified when the device size changes, and all its child StatelessWidgets get redrawn. I also did a check, if the device size in the StatefulWidget is 0x0, just return a Container. For some reason if I return RandomContainer(), it doesn't get rebuilt later on when the device size has changed.

Share:
3,033
Mary
Author by

Mary

Updated on December 08, 2022

Comments

  • Mary
    Mary over 1 year

    Update: I tried this with a regular 100% Flutter app and was not able to replicate it. In a Flutter View of an Android app, though, I logged the size and there's a log where it's 0x0. So it seems the below question is only applicable in this case.


    I have a widget that finds the size of the device and builds a widget accordingly. I was using LayoutBuilder (and constraints.biggest) for this initially in the build() function, and also tried using MediaQuery.of(context). The widget is a StatelessWidget. I don't think it should be a Stateful one since I don't change the state of it (though device size changes) and in debug mode the widgets draw correctly.

    Debug: debug Release: release

    The build() code is essentially:

    final size = MediaQuery.of(context).size;
    return Stack(
        children: [
            Container(width: 200),
            Container(width: size.width - _padding),
            Container(width: size.width - _morePadding),
        ],
    );
    

    Update: The full build code for a Flutter-View-in-Android-app is to:

    1. Pull down this example: https://github.com/flutter/flutter/tree/master/examples/flutter_view
    2. Replace main.dart with (I know it's messy):

    import 'dart:async';
    import 'dart:ui' as ui;
    
    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    
    void main() {
      runApp(FlutterView());
    }
    
    class FlutterView extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter View',
          theme: ThemeData(
            primarySwatch: Colors.grey,
          ),
          home: RandomContainer(),
        );
      }
    }
    
    class RandomContainer extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        final Size size = MediaQuery.of(context).size;
        print('maryx $size');
        return MyHomePage(
          title: 'Flutter Demo Home Page',
          width: size.width,
          height: size.height,
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      const MyHomePage({
        Key key,
        this.title,
        this.width,
        this.height,
      }) : super(key: key);
    
      final String title;
      final double width;
      final double height;
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      static const String _channel = 'increment';
      static const String _pong = 'pong';
      static const String _emptyMessage = '';
      static const BasicMessageChannel<String> platform =
          BasicMessageChannel<String>(_channel, StringCodec());
    
      int _counter = 0;
    
      Widget _image = Container();
    
      @override
      void initState() {
        super.initState();
        platform.setMessageHandler(_handlePlatformIncrement);
        _buildImage();
      }
    
      Future<String> _handlePlatformIncrement(String message) async {
        setState(() {
          _counter++;
        });
        return _emptyMessage;
      }
    
      void _sendFlutterIncrement() {
        platform.send(_pong);
      }
    
      Widget _buildWidgets() {
        final size = MediaQuery.of(context).size;
        return Stack(
          children: [
            Center(
              child: Container(
                width: size.width - 50.0,
                height: 100.0,
                color: Colors.pink[900],
              ),
            ),
            Center(
              child: _image,
            ),
            Center(
                child: Container(
              width: 100.0,
              height: 50.0,
              color: Colors.pink[200],
            )),
          ],
        );
      }
    
      Future<void> _buildImage() async {
        final recorder = ui.PictureRecorder();
        final canvas = ui.Canvas(recorder);
    
        final rrect = ui.RRect.fromRectAndRadius(
            ui.Rect.fromLTWH(0.0, 0.0, widget.width, 100.0),
            Radius.circular(widget.width / 2));
        canvas.drawRRect(rrect, ui.Paint()..color = Colors.pink[500]);
    
        // Save drawing into a png.
        final picture = recorder.endRecording();
        final image = picture.toImage(widget.width.toInt(), 100);
        final pngBytes = await image.toByteData(format: ui.ImageByteFormat.png);
    
        // See https://github.com/flutter/flutter/issues/6246
        if (!mounted) return;
    
        // Save png as an Image widget.
        setState(() {
          _image = Image.memory(
            pngBytes.buffer.asUint8List(),
            height: 100,
            width: widget.width,
            fit: BoxFit.cover,
          );
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Expanded(
                child: Center(
                    child: Text(
                        'Platform button tapped $_counter time${_counter == 1 ? '' : 's'}.',
                        style: const TextStyle(fontSize: 17.0))),
              ),
              _buildWidgets(),
              Container(
                padding: const EdgeInsets.only(bottom: 15.0, left: 5.0),
                child: Row(
                  children: <Widget>[
                    Image.asset('assets/flutter-mark-square-64.png', scale: 1.5),
                    const Text('Flutter', style: TextStyle(fontSize: 30.0)),
                  ],
                ),
              ),
            ],
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _sendFlutterIncrement,
            child: const Icon(Icons.add),
          ),
        );
      }
    }
    

    In debug mode, the widget draws correctly, but in release mode, the widget is sized 0x0 because at the moment that it's being drawn, the device size is 0x0. This seems to be related: https://github.com/flutter/flutter/issues/11697

    How do I tell the widget to redraw once its size changes? Supposedly both LayoutBuilder and MediaQuery should be telling the widget to redraw, and when I add print statements, the device size IS changing:

    12-18 12:01:31.084  1587  1752 I flutter : device: Size(0.0, 0.0)
    12-18 12:01:31.087  1587  1752 I flutter : length 200.0 // hardcoded widget, used as a control (does not depend on device size)
    12-18 12:01:31.088  1587  1752 I flutter : length 0.0 // widget based on device size
    12-18 12:01:31.089  1587  1752 I flutter : length 0.0 // widget based on device size
    12-18 12:01:31.563  1587  1752 I flutter : device: Size(600.0, 400.0)
    

    and I would've expected the middle 3 lines to repeat (redraw) but they don't.

    For comparison, this is what it looks like in debug mode. It completely bypasses the 0x0 device size:

    12-18 12:10:44.506  1897  2063 I flutter : device: Size(600.0, 400.0)
    12-18 12:10:44.593  1897  2063 I flutter : length 200.0
    12-18 12:10:44.627  1897  2063 I flutter : length 563.3333333333334
    12-18 12:10:44.631  1897  2063 I flutter : length 333.3333333333334
    
  • Mary
    Mary over 5 years
    Right, in release mode, the actual device size is updated in a short moment. However, my widget doesn't seem to redraw after this happens - it maintains a Container with width 0. I'm trying to figure out why this is, and if there is a way to manually re-trigger a redraw?
  • Günter Zöchbauer
    Günter Zöchbauer over 5 years
    There shouldn't be a need for explicit redraw. Perhaps you could try to create a complete runnable minimal example.
  • Mary
    Mary over 5 years
    I couldn't replicate the issue with a 100% Flutter app - when I logged the size, it was always the size of the device. However, I had been doing this in a Flutter View of an Android app, so I've updated my question, adding my code to the sample Flutter View app at github.com/flutter/flutter/tree/master/examples/flutter_view (aware it's not pretty).