Updating a Stateless Widget from a Stateful Widget

58
October 21, 2021, at 12:40 PM

Main.dart:

import 'package:flutter/material.dart';
import 'package:mypackage/widgets/home_widget.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "My App",
      theme: ThemeData(primarySwatch: Colors.blueGrey),
      home: Home(),
    );
  }
}

Home.dart:

import 'package:flutter/material.dart';
import 'package:mypackage/widgets/secondary_page.dart';
import 'package:mypackage/widgets/home_view.dart';
import 'package:mypackage/widgets/settings_page.dart';
class Home extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _HomeState();
  }
}
class _HomeState extends State<Home> {
  int currentPageIndex = 0;
  final List<Widget> pageWidgets = [HomeView(), SecondaryPage(), SettingsPage()];
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("My App"),
          centerTitle: true,
        ),
        body: pageWidgets[currentPageIndex],
        bottomNavigationBar: BottomNavigationBar(
          onTap: onTabTapped,
          currentIndex: currentPageIndex,
          items: [
            BottomNavigationBarItem(
                icon: new Icon(Icons.home), label: "Tab One"),
            BottomNavigationBarItem(
                icon: new Icon(Icons.account_balance), label: "Tab Two"),
            BottomNavigationBarItem(
                icon: new Icon(Icons.settings), label: "Tab Three"),
          ],
        ),
        floatingActionButton: FloatingActionButton(onPressed: () {
          // Update data from here
          HomeView().updateDisplay();
        }));
  }
  // Changes the index when the tapped page has loaded
  void onTabTapped(int index) {
    setState(() {
      currentPageIndex = index;
    });
  }
}

HomeView.dart:

import 'package:flutter/material.dart';
import 'package:mypackage/models/mydisplay.dart';
import 'package:mypackage/network/mydata.dart';
class HomeView extends StatelessWidget {
  final List<MyDisplay> dataList = [
    new MyDisplay("Sample Display",
        "SampleFile", 0),
  ];
  Future<void> updateDisplay() async {
    MyData myData = new MyData();
    dataList.forEach((d) async {
      d.changePercentage = await myData
          .getDataPercentage("assets/data/" + d.fileName + ".xml");
      print(d.dataName +
          " change percentage: " +
          d.changePercentage.toString() +
          "%");
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: new ListView.builder(
          itemCount: dataList.length,
          itemBuilder: (BuildContext context, int index) =>
              buildCard(context, index)),
    );
  }
  Widget buildCard(BuildContext context, int index) {
    return Container(
        child: Card(
            color: Colors.teal.shade200,
            child: Padding(
                padding: const EdgeInsets.all(25),
                child: Column(children: <Widget>[
                  Text(dataList[index].dataName,
                      style: new TextStyle(
                          fontSize: 20, fontWeight: FontWeight.bold)),
                  Text(dataList[index].changePercentage.toString() + "%")
                ]))));
  }
}

When I call the updateDisplay() method from the floatingActionButton it works as expected, the data gets printed to the console, however it doesn't seem to update the actual UI, the content of the cards, they simply remain at their initial set value.

To clarify: how do I update the content of the cards inside the listview (inside a stateless widget) from a stateful widget?

I am sorry if this is a duplicate question, I have looked and am continuing looking for answers to the question but I simply can't get it working.

Answer 1

You need to use a stateful widget since you cannot update stateless widgets. Check this link out for more information.The next problem, calling it, can be fixed by using globalKeys. The only weird thing about this is you have to keep both the Home stateful widget and HomeView stateful widget in the same dart file. . Try this code to make it work.

HOME VIEW

import 'package:flutter/material.dart';
import 'package:mypackage/widgets/secondary_page.dart';
import 'package:mypackage/widgets/settings_page.dart';
import 'package:mypackage/models/mydisplay.dart';
import 'package:mypackage/network/mydata.dart';
class Home extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _HomeState();
  }
}
class _HomeState extends State<Home> {
  int currentPageIndex = 0;
  final List<Widget> pageWidgets = [HomeView(), SecondaryPage(), SettingsPage()];
  GlobalKey<_HomeViewState> _myKey = GlobalKey();// We declare a key here
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("My App"),
          centerTitle: true,
        ),
        body: pageWidgets[currentPageIndex],
        bottomNavigationBar: BottomNavigationBar(
          onTap: onTabTapped,
          currentIndex: currentPageIndex,
          items: [
            BottomNavigationBarItem(
                icon: new Icon(Icons.home), label: "Tab One"),
            BottomNavigationBarItem(
                icon: new Icon(Icons.account_balance), label: "Tab Two"),
            BottomNavigationBarItem(
                icon: new Icon(Icons.settings), label: "Tab Three"),
          ],
        ),
        floatingActionButton: FloatingActionButton(onPressed: () {
          // Update data from here
          _myKey.currentState.updateDisplay();//This is how we call the function
        }));
  }
  // Changes the index when the tapped page has loaded
  void onTabTapped(int index) {
    setState(() {
      currentPageIndex = index;
    });
  }
}
class HomeView extends StatefulWidget {
  Function updateDisplay;
  HomeView({Key key}): super(key: key);//This key is what we use
  @override
  _HomeViewState createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
  final List<MyDisplay> dataList = [
    new MyDisplay("Sample Display",
        "SampleFile", 0),
  ];
@override
  void initState(){
    super.initState();
    widget.updateDisplay = () async {
      MyData myData = new MyData();
      dataList.forEach((d) async {
        d.changePercentage = await myData
            .getDataPercentage("assets/data/" + d.fileName + ".xml");
        print(d.dataName +
            " change percentage: " +
            d.changePercentage.toString() +
            "%");
      });
      setState((){});//This line rebuilds the scaffold
    };
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: new ListView.builder(
          itemCount: dataList.length,
          itemBuilder: (BuildContext context, int index) =>
              buildCard(context, index)),
    );
  }
  Widget buildCard(BuildContext context, int index) {
    return Container(
        child: Card(
            color: Colors.teal.shade200,
            child: Padding(
                padding: const EdgeInsets.all(25),
                child: Column(children: <Widget>[
                  Text(dataList[index].dataName,
                      style: new TextStyle(
                          fontSize: 20, fontWeight: FontWeight.bold)),
                  Text(dataList[index].changePercentage.toString() + "%")
                ]))));
  }
}

We use setState((){}) to rebuild the widget. Just keep in mind one think. Wherever you use the updateDisplay method, dont make it so it runs in a loop after rebuilding. For eg. You use it in a future builder inside the child. THat way it will keep rebuilding

EDIT

We added a key to HomeView and now we can use that key like _myKey.currentState.yourFunction. Hope I helped

Answer 2

While I understand what you wish to achieve, I believe that will be going against "the Flutter way". You can achieve your desired result using a key, but I believe it will be easier and more idiomatic to lift the state up the widget tree. You can read about it in the docs here.

So in your case, you might change it to:

class Home extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _HomeState();
  }
}
class _HomeState extends State<Home> {
  int currentPageIndex = 0;
  // Added this line
  List<MyDisplay> dataList = [
    new MyDisplay("Sample Display",
        "SampleFile", 0),
  ];
  final List<Widget> pageWidgets; 
  // moved the update function from HomeView to here
  Future<void> updateDisplay() async {
    MyData myData = new MyData();
    dataList.forEach((d) async {
      d.changePercentage = await myData
          .getDataPercentage("assets/data/" + d.fileName + ".xml");
      print(d.dataName +
          " change percentage: " +
          d.changePercentage.toString() +
          "%");
    });
    setState((){});
  }
  @override
  Widget build(BuildContext context) {
    pageWidgets = [HomeView(dataList: dataList), SecondaryPage(), SettingsPage()]
    return Scaffold(
        appBar: AppBar(
          title: Text("My App"),
          centerTitle: true,
        ),
        body: pageWidgets[currentPageIndex],
        bottomNavigationBar: BottomNavigationBar(
          onTap: onTabTapped,
          currentIndex: currentPageIndex,
          items: [
            BottomNavigationBarItem(
                icon: new Icon(Icons.home), label: "Tab One"),
            BottomNavigationBarItem(
                icon: new Icon(Icons.account_balance), label: "Tab Two"),
            BottomNavigationBarItem(
                icon: new Icon(Icons.settings), label: "Tab Three"),
          ],
        ),
        floatingActionButton: FloatingActionButton(onPressed: () {
          updateDisplay();
        }));
  }
  // Changes the index when the tapped page has loaded
  void onTabTapped(int index) {
    setState(() {
      currentPageIndex = index;
    });
  }
}

Then for your HomeView widget:

class HomeView extends StatelessWidget {
  HomeView({this.dataList});
  final List<MyDisplay> dataList;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: new ListView.builder(
          itemCount: dataList.length,
          itemBuilder: (BuildContext context, int index) =>
              buildCard(context, index)),
    );
  }
  Widget buildCard(BuildContext context, int index) {
    return Container(
        child: Card(
            color: Colors.teal.shade200,
            child: Padding(
                padding: const EdgeInsets.all(25),
                child: Column(children: <Widget>[
                  Text(dataList[index].dataName,
                      style: new TextStyle(
                          fontSize: 20, fontWeight: FontWeight.bold)),
                  Text(dataList[index].changePercentage.toString() + "%")
                ],
              ),
            ),
          ),
        );
  }
}

I haven't tested if this works. I probably will a little later on.

Answer 3

You need to call setState(() {}); after HomeView().updateDisplay();

Answer 4

update your home.dart as follows.

import 'package:flutter/material.dart';
import 'package:mypackage/widgets/secondary_page.dart';
import 'package:mypackage/widgets/home_view.dart';
import 'package:mypackage/widgets/settings_page.dart';
class Home extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _HomeState();
  }
}
class _HomeState extends State<Home> {
  int currentPageIndex = 0;
  Widget _homeView = HomeView();
  final List<Widget> pageWidgets = [_homeView, SecondaryPage(), SettingsPage()];
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("My App"),
          centerTitle: true,
        ),
        body: pageWidgets[currentPageIndex],
        bottomNavigationBar: BottomNavigationBar(
          onTap: onTabTapped,
          currentIndex: currentPageIndex,
          items: [
            BottomNavigationBarItem(
                icon: new Icon(Icons.home), label: "Tab One"),
            BottomNavigationBarItem(
                icon: new Icon(Icons.account_balance), label: "Tab Two"),
            BottomNavigationBarItem(
                icon: new Icon(Icons.settings), label: "Tab Three"),
          ],
        ),
        floatingActionButton: FloatingActionButton(onPressed: () {
          // Update data from here
          _homeView.updateDisplay();
        }));
  }
  // Changes the index when the tapped page has loaded
  void onTabTapped(int index) {
    setState(() {
      currentPageIndex = index;
    });
  }
}

home_view.dart as follows

import 'package:flutter/material.dart';
import 'package:mypackage/models/mydisplay.dart';
import 'package:mypackage/network/mydata.dart';
class HomeView extends StatelessWidget {
  final List<MyDisplay> dataList = [
    new MyDisplay("Sample Display",
        "SampleFile", 0),
  ];
  Future<void> updateDisplay() async {
    MyData myData = new MyData();
    dataList.forEach((d) async {
      d.changePercentage = await myData
          .getDataPercentage("assets/data/" + d.fileName + ".xml");
      print(d.dataName +
          " change percentage: " +
          d.changePercentage.toString() +
          "%");
    });
     setState((){});
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: new ListView.builder(
          itemCount: dataList.length,
          itemBuilder: (BuildContext context, int index) =>
              buildCard(context, index)),
    );
  }
  Widget buildCard(BuildContext context, int index) {
    return Container(
        child: Card(
            color: Colors.teal.shade200,
            child: Padding(
                padding: const EdgeInsets.all(25),
                child: Column(children: <Widget>[
                  Text(dataList[index].dataName,
                      style: new TextStyle(
                          fontSize: 20, fontWeight: FontWeight.bold)),
                  Text(dataList[index].changePercentage.toString() + "%")
                ]))));
  }
}
Answer 5

You need to transform your HomeView into a StatefulWidget.

After that you need to modify updateDisplay into this:

Future<void> updateDisplay() async {
    MyData myData = new MyData();
    dataList.forEach((d) async {
      d.changePercentage = await myData
          .getDataPercentage("assets/data/" + d.fileName + ".xml");
      print(d.dataName +
          " change percentage: " +
          d.changePercentage.toString() +
          "%");
    });
    
    setState(() {});
  }

By calling setState (() {}); you invoke the build method inside the StatefulWidget.

EDIT

Change your HomeView like this:

class HomeView extends StatefulWidget {
  final List<MyDisplay> dataList = [
    new MyDisplay("Sample Display",
        "SampleFile", 0),
  ];
  Future<void> updateDisplay() async {
    MyData myData = new MyData();
    dataList.forEach((d) async {
      d.changePercentage = await myData
          .getDataPercentage("assets/data/" + d.fileName + ".xml");
      print(d.dataName +
          " change percentage: " +
          d.changePercentage.toString() +
          "%");
    });
  }
  @override
  _HomeViewState createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: new ListView.builder(
          itemCount: widget.dataList.length,
          itemBuilder: (BuildContext context, int index) =>
              buildCard(context, index)),
    );
  }
  Widget buildCard(BuildContext context, int index) {
    return Container(
        child: Card(
            color: Colors.teal.shade200,
            child: Padding(
                padding: const EdgeInsets.all(25),
                child: Column(children: <Widget>[
                  Text(widget.dataList[index].dataName,
                      style: new TextStyle(
                          fontSize: 20, fontWeight: FontWeight.bold)),
                  Text(widget.dataList[index].changePercentage.toString() + "%")
                ]))));
  }
}

And your Home like this:

class Home extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _HomeState();
  }
}
class _HomeState extends State<Home> {
  int currentPageIndex = 0;
  HomeView _homeView = HomeView();
  List<Widget> pageWidgets = [_homeView, SecondaryPage(), SettingsPage()];
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("My App"),
          centerTitle: true,
        ),
        body: pageWidgets[currentPageIndex],
        bottomNavigationBar: BottomNavigationBar(
          onTap: onTabTapped,
          currentIndex: currentPageIndex,
          items: [
            BottomNavigationBarItem(
                icon: new Icon(Icons.home), label: "Tab One"),
            BottomNavigationBarItem(
                icon: new Icon(Icons.account_balance), label: "Tab Two"),
            BottomNavigationBarItem(
                icon: new Icon(Icons.settings), label: "Tab Three"),
          ],
        ),
        floatingActionButton: FloatingActionButton(onPressed: () {
          // Update data from here
          _homeView.updateDisplay();
        }));
  }
  // Changes the index when the tapped page has loaded
  void onTabTapped(int index) {
    setState(() {
      currentPageIndex = index;
    });
  }
}
READ ALSO
How to rearrange objects in arraylist based on card type [duplicate]

How to rearrange objects in arraylist based on card type [duplicate]

I have a scenario where I have to rearrange the elements of bank name in a particular orderThis list can be in any order [Wellsfargo, Bank of America, Chase, Citi, American Express] and may not have one or more of the banks in the list

65
useSWRInfinite - getKey function always gets pageIndex as 1

useSWRInfinite - getKey function always gets pageIndex as 1

I am using the SWRInfinite example as defined here (https://githubcom/vercel/swr/discussions/732) and here (https://swr

61
How to get a value on a page using jquery and echo it onto the page using php

How to get a value on a page using jquery and echo it onto the page using php

Problem: I need to get the number 65 in strong tagI tried the following code, but it shows Uncaught ReferenceError

63
UPDATE unknown column in on clause

UPDATE unknown column in on clause

column sales_mode value : A|B -> 1|2

54