Mary
Mary

Reputation: 20495

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

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

Upvotes: 2

Views: 4385

Answers (2)

Mary
Mary

Reputation: 20495

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.

Upvotes: 1

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 657957

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.

Upvotes: 1

Related Questions