Updating a Stateless Widget from a Stateful Widget

Issue

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.

Solution

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

Answered By – Siddharth Agrawal

Answer Checked By – Gilberto Lyons (FlutterFixes Admin)

Leave a Reply

Your email address will not be published.