15

Context

I have two stateless widgets (pages): HomePage and DetailsPage. Obviously the application starts and launches the HomePage. There is a button the user can press to navigate to the DetailsPage with a Navigator.pop() button to navigate back to the HomePage.

I know when the DetailsPage is done being used with the .whenComplete() method. It is at this point I want to rebuild the HomePage widget.

Code

This is the minimum reproduction of my behavior.

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: HomePage());
  }
}
home.dart
import 'package:example/details.dart';
import 'package:flutter/material.dart';

class HomePage extends StatelessWidget {
  static const name = 'Home Page';
  const HomePage() : super();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: MaterialButton(
          color: Colors.blue,
          textColor: Colors.white,
          child: Text(name),
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: DetailsPage.builder),
            ).whenComplete(() => print('Rebuild now.'));
          },
        ),
      ),
    );
  }
}
details.dart
import 'package:flutter/material.dart';

class DetailsPage extends StatelessWidget {
  static const name = 'Details Page';
  static WidgetBuilder builder = (BuildContext _) => DetailsPage();
  const DetailsPage();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(name),
            MaterialButton(
              color: Colors.blue,
              textColor: Colors.white,
              child: Text('Go Back'),
              onPressed: () => Navigator.pop(context),
            ),
          ],
        ),
      ),
    );
  }
}

Question

How can I invoke a rebuild of this stateless widget (HomePage) at the .whenComplete() method callback?

Apealed
  • 1,415
  • 8
  • 29
  • 1
    What's the point of rebuilding this stateless widget ? – pasty Jan 22 '21 at 15:03
  • 1
    Have you looked into this: https://stackoverflow.com/questions/49804891/force-flutter-navigator-to-reload-state-when-popping – Adelina Jan 22 '21 at 15:04
  • This example is only for a minimum example. In my actually app I am making a get request to a web server in the build function (`FutureBuilder`) and I know that the web service data update _after_ I leave the second page, so I want that data to be reflected in the first page. – Apealed Jan 22 '21 at 15:09
  • I don't think that example will work, Nuts. It requires a `StatefulWidget` and my actual implementation doesn't have a state to update. Unless I am able to arbitrarily call `setState` without actually updating a state value (can I?). – Apealed Jan 22 '21 at 15:12
  • After toying around with the concept, I learned that you can invoke and "empty" `setState` like: `setState(() {});` and it will rebuild your stateless widget. But, Unfortunately, It doesn't really answer my question for a `StatelessWidget`. (Although, I will probably use this as a my actually solution in my app.) – Apealed Jan 22 '21 at 15:20
  • 4
    I'm not sure why this question is being down voted? It meets all requirements of a quality Stack Overflow question. – Apealed Jan 25 '21 at 14:58
  • Any solution to this with `StatelessWidget`? I have the same use case. The state being changed is on the server and I want the first page to be refreshed with updated data. – Roslan Amir Nov 07 '21 at 15:26
  • @RoslanAmir I have not found a true (or "safe") way to invoke a rebuild of a stateless widget, unfortunately. Some alternatives I have found include dispatching a dependency change in whatever state management solution you have. For example if you use the "bloc" pattern, you can emit an identical state and rebuild like that. – Apealed Nov 08 '21 at 13:55
  • However, I don't think the above comment I made is a solution to the original question I have asked. – Apealed Nov 08 '21 at 13:55
  • Have you looked at the function `markNeedsBuild()`? I couldn't find anything about how to invoke it. Supposedly you can mark a StatelessWidget with this to force a rebuild. – Roslan Amir Nov 08 '21 at 14:12

1 Answers1

2

You can force rebuild the widget tree as follows:

class RebuildController   {
  final GlobalKey rebuildKey = GlobalKey();
  
  void rebuild() {
    void rebuild(Element el) {
      el.markNeedsBuild();
      el.visitChildren(rebuild);
    }
    (rebuildKey.currentContext as Element).visitChildren(rebuild);
  }

}

class RebuildWrapper extends StatelessWidget  {
  
  final RebuildController controller;
  final Widget child;

  const RebuildWrapper({Key? key, required this.controller, required this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) => Container(
    key: controller.rebuildKey,
    child: child,
  );

}

In your case,

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  final RebuildController controller = RebuildController();

  MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: RebuildWrapper(
        controller: controller,
        child: HomePage(
          rebuildController: controller,
        ),
      ),
    );
  }
}

class HomePage extends StatelessWidget {

  static const name = 'Home Page';
  final RebuildController rebuildController;

  const HomePage({Key? key, required this.rebuildController}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print('Hello there!');
    return Scaffold(
      body: Center(
        child: MaterialButton(
          color: Colors.blue,
          textColor: Colors.white,
          child: const Text(name),
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: DetailsPage.builder),
            ).whenComplete(rebuildController.rebuild);
          },
        ),
      ),
    );
  }
  
}

class DetailsPage extends StatelessWidget {
  static const name = 'Details Page';
  static WidgetBuilder builder = (BuildContext _) => const DetailsPage();

  const DetailsPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(name),
            MaterialButton(
              color: Colors.blue,
              textColor: Colors.white,
              child: const Text('Go Back'),
              onPressed: () => Navigator.pop(context),
            ),
          ],
        ),
      ),
    );
  }
}

class RebuildController   {
  final GlobalKey rebuildKey = GlobalKey();
  
  void rebuild() {
    void rebuild(Element el) {
      el.markNeedsBuild();
      el.visitChildren(rebuild);
    }
    (rebuildKey.currentContext as Element).visitChildren(rebuild);
  }

}

class RebuildWrapper extends StatelessWidget  {
  
  final RebuildController controller;
  final Widget child;

  const RebuildWrapper({Key? key, required this.controller, required this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) => Container(
    key: controller.rebuildKey,
    child: child,
  );

}

But it is unnatural to force rebuild stateless widgets as they are not supposed to be rebuilt. You should use stateful widget or other state management solutions so that your HomePage will only be updated on meaningful state change.

Source - this answer

saw
  • 318
  • 2
  • 6
  • 13
  • 1
    This makes sense. I agree with you. There really is not a solution to this question without using genuine state management. Thanks for hack though! This is really interesting – Apealed Dec 21 '21 at 16:19
  • My pleasure! I was thinking about RepaintBoundary but it doesn't rebuild the widget which is the behavior you wanted. – saw Dec 22 '21 at 02:41