Flutter :how to call function in statelesswidget?

Viewed 7029

I want to show a list of data with list view and JSON. This code in a file with stateless widget. And this page opened after login. When I try in stateful widget, the code RUN normally. In stateless widget, when I debug it, the code didn't call function getData(). But directly went to

Widget build(BuildContext context) {    
     return new Scaffold( ... 

Here is complete code :

class frmClass extends StatelessWidget{
  List<dynamic> dta;
  get setState => null;

  Future<String> getData() async {
      var uri ='https://xxxx.com/class';
      var map = new Map<String, dynamic>();
      map["username"] = "abc";
      map["password"] = "1234";

      http.Response response = await http.post(
        uri,
        body: jsonEncode(map),
      );

      Map<String,dynamic> mp = jsonDecode(utf8.decode(response.bodyBytes));
      this.setState(() {
        dta = mp["data"];
        debugPrint(dta.toString());
      });

    }

  @override
  void initState(){
    this.getData();
  }

  @override
  Widget build(BuildContext context) {

     return new Scaffold(
       appBar: new AppBar(
           backgroundColor:Colors.transparent,
           elevation: 0.0,
           iconTheme: new IconThemeData(color: Color(0xFF18D191))),
       body: new ListView.builder(
           itemCount: dta == null ? 0 : dta.length,
           itemBuilder: (BuildContext context, int index){
             return new Card(
               child: new Text(dta[index]["className"]),
             );
           }
       ),
     );
  }
}

How can I fix it?

4 Answers

You can use a FutureBuilder to call getData() into build() method of StatelessWidget:

https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html

But, this will fire getData() every time your statelessWidget is rebuild.

Another way is to use reactive programing architecture (like Bloc, rxdart, etc..).

Depends on what do you want, fire getData() every time or just once (or when your conditions are true/false).

This is happening because stateless widgets don't have initState that's why the below code will never get called. Stateless widgets are more sort of rendering flat UI with no lifecycle methods if you want to use Stateless widget then pass data in the class constructor and use it where ever you want

 @override 
  void initState(){
    this.getData();
  }

if you want to call it from a stateless widget? Well, that’s possible too. Use a stateful widget as a your root widget that you can provide a callback function too to execute your startup logic. See example below. Create a StatefulWrapper that takes in a function to call and a child to display.

/// Wrapper for stateful functionality to provide onInit calls in stateles widget
class StatefulWrapper extends StatefulWidget {
  final Function onInit;
  final Widget child;
  const StatefulWrapper({@required this.onInit, @required this.child});
  @override
  _StatefulWrapperState createState() => _StatefulWrapperState();
}
class _StatefulWrapperState extends State<StatefulWrapper> {
@override
  void initState() {
    if(widget.onInit != null) {
      widget.onInit();
    }
    super.initState();
  }
  @override
  Widget build(BuildContext context) {
    return widget.child;
  }
}

Wrap your stateles widget’s UI in a StatefulWrapper and pass on the onInit logic you would like to run

class StartupCaller extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StatefulWrapper(
      onInit: () {
        _getThingsOnStartup().then((value) {
          print('Async done');
        });
      },
      child: Container(),
    );
  }
  Future _getThingsOnStartup() async {
    await Future.delayed(Duration(seconds: 2));
  }
}

and that’s it. The function will only be called once when the stateful widget is initialized.

Related