Alain Zelink
Alain Zelink

Reputation: 899

Flutter misunderstanding - refresh and stateful widget

I'm stuck and I think I'm completely lost in Flutter's logic. I want to do the following :

class HomeScreen extends StatefulWidget {
  static String id = 'home_screen';

  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {

@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
   
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.refresh),
            onPressed: () {
              ** I WANT TO CALL REFRESH ON THE EVENT LIST**
            },
          ),
        ],
      ),
      body: Column(
        children: <Widget>[
          EventList(),
...

class EventList extends StatefulWidget {
  @override
  _EventListState createState() => _EventListState();
}

class _EventListState extends State<EventList> {
  List<Event> eventList = [];

  @override
  void initState() {
    super.initState();
    getEventList();
  }

  Future<Null> getEventList() async {
   
    // Fill eventList from a web service ...

    setState(() {
  
    });
  }

  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: Container(
         child: (eventList == null || eventList.length == 0)
            ? Center(
                child: CircularProgressIndicator()
            : ListView.builder(
                itemCount: (eventList == null) ? 0 : eventList.length,
                itemBuilder: (BuildContext context, int index) {
...

This logic works to make the first fetch of data using GET on a web service.

How can I call a refresh() to get new data on the HomeScreen and ask EventList to refresh (call getEventList() again) and rebuild ?

Thank you

Upvotes: 0

Views: 1673

Answers (3)

chunhunghan
chunhunghan

Reputation: 54367

You can copy paste run full code below
Working demo simulate 3 seconds network delay and show random number
Step 1: call setState in IconButton's onPressed

IconButton(
  icon: Icon(Icons.refresh),
  onPressed: () {
    setState(() {});
  },
),

Step 2 : In didUpdateWidget of _EventListState use addPostFrameCallback to call getEventList()

@override
  void didUpdateWidget(EventList oldWidget) {
    super.didUpdateWidget(oldWidget);
    WidgetsBinding.instance.addPostFrameCallback((_) {
      getEventList();
    });
  }

working demo

enter image description here

Execution sequence when click refresh button

didUpdateWidget
build
getEventList
build for clear EventList
build for add EventList

full code

import 'package:flutter/material.dart';
import 'dart:math';

class Event {
  String title;

  Event({this.title});
}

class HomeScreen extends StatefulWidget {
  static String id = 'home_screen';

  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          actions: <Widget>[
            IconButton(
              icon: Icon(Icons.refresh),
              onPressed: () {
                setState(() {});
              },
            ),
          ],
        ),
        body: Column(children: <Widget>[
          EventList(),
        ]));
  }
}

class EventList extends StatefulWidget {
  @override
  _EventListState createState() => _EventListState();
}

class _EventListState extends State<EventList> {
  List<Event> eventList = [];

  @override
  void initState() {
    super.initState();
    getEventList();
  }

  @override
  void didUpdateWidget(EventList oldWidget) {
    super.didUpdateWidget(oldWidget);
    WidgetsBinding.instance.addPostFrameCallback((_) {
      getEventList();
    });
  }

  Future<Null> getEventList() async {
    Random random = new Random();

    setState(() {
      eventList.clear();
    });

    await Future.delayed(Duration(seconds: 3), () {});

    List<Event> newEventList = List<Event>.generate(
        3, (index) => Event(title: random.nextInt(100).toString()));

    setState(() {
      eventList.addAll(newEventList);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Expanded(
        child: Container(
            child: (eventList == null || eventList.length == 0)
                ? Center(child: CircularProgressIndicator())
                : ListView.builder(
                    itemCount: (eventList == null) ? 0 : eventList.length,
                    itemBuilder: (BuildContext context, int index) {
                      return ListTile(title: Text(eventList[index].title));
                    })));
  }
}

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: HomeScreen(),
    );
  }
}

Upvotes: 1

Gaspard Merten
Gaspard Merten

Reputation: 1233

There are many ways to solve your issue.

  1. The first one would be to call setState in your MainState class after fetching the data. This will cause all children widgets to rebuild. But to do so you will also need to save the data inside your MainState class and pass it to your EventList widget as an argument. (Note that your EventList widget does not need to be Stateful anymore! Since it should not call the setState method any longer).

  2. The second option, if you really want your function to be inside your EventListState class, is to create a controller that you instantiate in your MainState class. But that is quite a lot of work for a task as simple as this one.

  3. The third one is to create a Service which would be a separated class exposing a Stream on which you will push your data whenever it is needed. I know this may sound complicated so for this one here is a theoretical example:

    class MyEventService {
      final StreamController<List<Event>> stateStreamController = StreamController<List<Event>>.broadcast();
    
      Stream<ActionState> get stateStream => stateStreamController.stream;
    
      Future<void> refresh() { fetchFromServer... then stateStreamController.push(data);
    }
    class MainState {
       build(..) => Scaffold(... Button(onPushed: myEventServiceInstance.refresh)
     }
    
    class EventList {
       build(..) => StreamBuilder(stream: myEventServiceInstance.stream...)
    
    }
    
    
    
    

I hope this helped you, do not hesitate to ask if you want a more complex sample!

Upvotes: 1

Yacine Benali
Yacine Benali

Reputation: 91

in flutter you cant call a child function from a parent what I would recommend is combining them in a single stateful widget or put the scaffold in the eventList widget so the appbar and the ListView are in the same statfull Widget

class HomeScreen extends StatefulWidget {
  static String id = 'home_screen';

  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  List<Event> eventList = [];

  @override
  void initState() {
    super.initState();
    getEventList();
  }

    Future<Null> getEventList() async {
   
    // Fill eventList from a web service ...

    setState(() {
  
    });
  }

@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
   
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.refresh),
            onPressed: () => getEventList(),
          ),
        ],
      ),
      body: Column(
        children: <Widget>[
          buildEventList(),
        ]
      ),);

  }




  Widget buildEventList()(BuildContext context) {
    return Expanded(
      child: Container(
         child: (eventList == null || eventList.length == 0)
            ? Center(
                child: CircularProgressIndicator()
            : ListView.builder(
                itemCount: (eventList == null) ? 0 : eventList.length,
                itemBuilder: (BuildContext context, int index) {
..
                }),
    }

  }

Upvotes: 0

Related Questions