Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter - calling setState() from other widget

Tags:

Is it possible to call setState() of particular widget (embedded in other widgets) from other widgets onPressed() method so only that widget is redrawn?

I want to click on the button and see the state of "MyTextWidget" to change. The rest of the layout is same, nothing changes there so it should not be rewritten.

This is my code:

import 'package:flutter/material.dart'; void main() => runApp(new MyApp());  class MyApp extends StatelessWidget {   @override   Widget build(BuildContext context) {     return new MaterialApp(       title: 'Timer',       theme: new ThemeData(         primaryColor: Colors.grey.shade800,       ),       home: new MyHomePage(),     );   } }  class MyHomePage extends StatelessWidget {    int _seconds = 1;    @override   Widget build(BuildContext context) {     return Scaffold(       appBar: AppBar(         title: Text("title"),       ),       body: Column(         mainAxisAlignment: MainAxisAlignment.spaceAround,         children: <Widget>[           MyTextWidget(), //just update this widget           Divider(),           Row(             mainAxisAlignment: MainAxisAlignment.spaceAround,             children: <Widget>[               IconButton(                 icon: Icon(Icons.add_circle),                 onPressed: _addPressed,                 iconSize: 150.0,               ),               IconButton(                 icon: Icon(Icons.remove_circle),                 onPressed: ()=> print("to be implemented"),                 iconSize: 150.0,               ),             ],           )         ],       ),     );   }    void _addPressed() {     //somehow call _updateSeconds()   } } 

And this is statefull MyTextWidget which I want to update.

class MyTextWidget extends StatefulWidget{   @override   _MyTextWidgetState createState() => _MyTextWidgetState(); }  class _MyTextWidgetState extends State<MyTextWidget> {    int secondsToDisplay = 0;    void _updateSeconds(int newSeconds) {     setState(() {       secondsToDisplay = newSeconds;     });   }    @override   Widget build(BuildContext context) {     return Text(       secondsToDisplay.toString(),       textScaleFactor: 5.0,     );   } } 

It seems like something quite simple what I want to achieve but I'm not able to figure it out. Imagine if "MyTextWidget" was buried in huge layout tree and every time I want to update it I would need to redraw whole tree again.

like image 253
Andrej Avatar asked Oct 11 '18 13:10

Andrej


People also ask

How do you call a function in a widget from another widget in Flutter?

If you only want to call a function without any arguments, you can use the VoidCallback type instead of defining your own callback type. Calling a method of child widget from a parent widget is discouraged in Flutter. Instead, Flutter encourages you to pass down the state of a child as constructor parameters.

How do I use setState widget?

When you change the state of a Stateful Widget, use setState() to cause a rebuild of the widget and its descendants. You don't need to call setState() in the constructor or initState() of the widget, because build() will be run afterward anyway. Also don't call setState() in synchronous code inside build().


2 Answers

This is a possible solution using streams:

import 'dart:async';  import 'package:flutter/material.dart'; void main() => runApp(new MyApp());  class MyApp extends StatelessWidget {   @override   Widget build(BuildContext context) {     return new MaterialApp(       title: 'Timer',       theme: new ThemeData(         primaryColor: Colors.grey.shade800,       ),       home: new MyHomePage(),     );   } }  class MyHomePage extends StatelessWidget {    StreamController<int> _controller = StreamController<int>();    int _seconds = 1;    @override   Widget build(BuildContext context) {     return Scaffold(       appBar: AppBar(         title: Text("title"),       ),       body: Column(         mainAxisAlignment: MainAxisAlignment.spaceAround,         children: <Widget>[           MyTextWidget(stream: _controller.stream), //just update this widget           Divider(),           Row(             mainAxisAlignment: MainAxisAlignment.spaceAround,             children: <Widget>[               IconButton(                 icon: Icon(Icons.add_circle),                 onPressed: _addPressed,                 iconSize: 150.0,               ),               IconButton(                 icon: Icon(Icons.remove_circle),                 onPressed: ()=> _controller.add(_seconds++),                 iconSize: 150.0,               ),             ],           )         ],       ),     );   }    void _addPressed() {     //somehow call _updateSeconds()   } }  class MyTextWidget extends StatefulWidget{    final Stream<int> stream;    MyTextWidget({this.stream});    @override   _MyTextWidgetState createState() => _MyTextWidgetState(); }  class _MyTextWidgetState extends State<MyTextWidget> {    int secondsToDisplay = 0;    void _updateSeconds(int newSeconds) {     setState(() {       secondsToDisplay = newSeconds;     });   }    @override   void initState() {     super.initState();     widget.stream.listen((seconds) {       _updateSeconds(seconds);     });   }    @override   Widget build(BuildContext context) {     return Text(       secondsToDisplay.toString(),       textScaleFactor: 5.0,     );   } } 
like image 54
chemamolins Avatar answered Oct 23 '22 03:10

chemamolins


There are different ways on how to achieve this. The really simple one is with InheritedWidget widget, like that:

import 'package:flutter/material.dart';  void main() => runApp(new MyApp());  class MyApp extends StatelessWidget {   @override   Widget build(BuildContext context) {     return new MaterialApp(       title: 'Timer',       theme: new ThemeData(         primaryColor: Colors.grey.shade800,       ),       home: new MyHomePage(),     );   } }  class MyHomePage extends StatefulWidget {   @override   MyHomePageState createState() {     return new MyHomePageState();   } }  class MyHomePageState extends State<MyHomePage> {   int _seconds = 1;    @override   Widget build(BuildContext context) {     return new MyInheritedWidget(       secondsToDisplay: _seconds,       child: Scaffold(          appBar: AppBar(           title: Text("title"),         ),         body: Column(           mainAxisAlignment: MainAxisAlignment.spaceAround,           children: <Widget>[             MyTextWidget(), //just update this widget             Divider(),             Row(               mainAxisAlignment: MainAxisAlignment.spaceAround,               children: <Widget>[                 IconButton(                   icon: Icon(Icons.add_circle),                   onPressed: _addPressed,                   iconSize: 150.0,                 ),                 IconButton(                   icon: Icon(Icons.remove_circle),                   onPressed: () => print("to be implemented"),                   iconSize: 150.0,                 ),               ],             )           ],         ),       ),     );   }    void _addPressed() {     setState(() {       _seconds++;     });   } }  class MyTextWidget extends StatelessWidget {   @override   Widget build(BuildContext context) {     final MyInheritedWidget inheritedWidget = MyInheritedWidget.of(context);     return Text(inheritedWidget.secondsToDisplay.toString(),       textScaleFactor: 5.0,     );   } }  class MyInheritedWidget extends InheritedWidget {   final int secondsToDisplay;    MyInheritedWidget({     Key key,     @required this.secondsToDisplay,     @required Widget child,   }) : super(key: key, child: child);    static MyInheritedWidget of(BuildContext context) {     return context.inheritFromWidgetOfExactType(MyInheritedWidget);   }    @override   bool updateShouldNotify(MyInheritedWidget oldWidget) =>       secondsToDisplay != oldWidget.secondsToDisplay; } 
like image 40
olexa.le Avatar answered Oct 23 '22 02:10

olexa.le