How to use flutter provider in a statefulWidget?

前端 未结 3 1344
礼貌的吻别
礼貌的吻别 2021-02-10 07:59

I am using flutter_provider for state management. I want to load some items on page(statefulwidget) load from Api. I am showing a loader on page start and want to show the items

3条回答
  •  再見小時候
    2021-02-10 08:47

    Does this case can/should be handled with a statelessWidget?

    The answer is : No, it does not

    I am heavy user of StatefulWidget + Provider. I always use this pattern for displaying a Form which contains fields, that available for future edit or input.

    Updated : February 9 2020

    Regarding to Maks comment, I shared better way to manage provider using didChangeDependencies.

    You may check to this github repository

    main.dart

    1. First Step

    Initiate PlayListScreen inside ChangeNotifierProvider

    class PlaylistScreenProvider extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return ChangeNotifierProvider(
          create: (_) {
            return VideosProvider();
          },
          child: PlaylistScreen(),
        );
      }
    }
    
    class MainScreen extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Screen'),
          ),
          body: Center(
            child: RaisedButton(
              child: Text("Go To StatefulWidget Screen"),
              onPressed: () {
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (_) {
                      return PlaylistScreenProvider();
                    },
                  ),
                );
              },
            ),
          ),
        );
      }
    }
    
    
    1. Second Step

    Make PlaylistScreen as Stateful Widget to hold TextEditingContoller and other values.

    playlistScreen.dart

    class PlaylistScreen extends StatefulWidget {
      @override
      _PlaylistScreenState createState() => _PlaylistScreenState();
    }
    
    class _PlaylistScreenState extends State {
      List _playlistList;
      String _errorMessage;
      Stage _stage;
    
      final _searchTextCtrl = TextEditingController();
    
      @override
      void dispose() {
        super.dispose();
        _searchTextCtrl.dispose();
      }
    
      @override
      void didChangeDependencies() {
        super.didChangeDependencies();
        final videosState = Provider.of(context);
        _playlistList = videosState.playlist;
        _stage = videosState.stage;
        _errorMessage = videosState.errorMessage;
      }
    
      void actionSearch() {
        String text = _searchTextCtrl.value.text;
        Provider.of(context, listen: false)
            .updateCurrentVideoId(text);
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('My Videos'),
          ),
          body: Padding(
            padding: EdgeInsets.symmetric(horizontal: 16.0),
            child: Column(
              children: [
                Container(
                  child: RaisedButton.icon(
                    icon: Icon(Icons.search),
                    label: Text("Filter"),
                    onPressed: () {
                      actionSearch();
                    },
                  ),
                ),
                Container(
                  child: TextField(
                    controller: _searchTextCtrl,
                    onSubmitted: (value) {
                      actionSearch();
                    },
                    decoration: InputDecoration(
                      border: OutlineInputBorder(),
                      labelText: 'Please input 1 or 2',
                    ),
                  ),
                ),
                Flexible(
                  child: _stage == Stage.DONE
                      ? PlaylistTree(_playlistList)
                      : _stage == Stage.ERROR
                          ? Center(child: Text("$_errorMessage"))
                          : Center(
                              child: CircularProgressIndicator(),
                            ),
                )
              ],
            ),
          ),
        );
      }
    }
    
    class PlaylistTree extends StatelessWidget {
      PlaylistTree(this.playlistList);
    
      final List playlistList;
      @override
      Widget build(BuildContext context) {
        return ListView.builder(
          itemCount: playlistList.length,
          itemBuilder: (context, index) {
            var data = playlistList[index];
            return Container(
              child: Text("${data['id']} - ${data['first_name']}"),
            );
          },
        );
      }
    }
    
    1. Last Step

    make provider to handle Business Logic

    videosProvider.dart

    enum Stage { ERROR, LOADING, DONE }
    
    class VideosProvider with ChangeNotifier {
      String errorMessage = "Network Error";
      Stage stage;
      List _playlist;
      int _currentVideoId;
    
      VideosProvider() {
        this.stage = Stage.LOADING;
        initScreen();
      }
    
      void initScreen() async {
        try {
          await fetchVideosList();
          stage = Stage.DONE;
        } catch (e) {
          stage = Stage.ERROR;
        }
        notifyListeners();
      }
    
      List get playlist => _playlist;
    
      void setPlayList(videosList) {
        _playlist = videosList;
      }
    
      void validateInput(String valueText) {
        if (valueText == ""){
          this._currentVideoId = null;
          return;
        }
        try {
          int valueInt = int.parse(valueText);
          if (valueInt == 1 || valueInt == 2){
            this._currentVideoId = valueInt;
          }
          else {
            this.errorMessage = "Use only 1 and 2";
            throw 1;
          }
        } on FormatException catch (e) {
          this.errorMessage = "Must be a number";
          throw 1;
        }
      }
    
      void updateCurrentVideoId(String value) async {
        this.stage = Stage.LOADING;
        notifyListeners();
    
        try {
          validateInput(value);
          await fetchVideosList();
          stage = Stage.DONE;
        } on SocketException catch (e) {
          this.errorMessage = "Network Error";
          stage = Stage.ERROR;
        } catch (e) {
          stage = Stage.ERROR;
        }
        notifyListeners();
      }
    
      Future fetchVideosList() async {
        String url;
        if (_currentVideoId != null) {
          url = "https://reqres.in/api/users?page=$_currentVideoId";
        } else {
          url = "https://reqres.in/api/users";
        }
        http.Response response = await http.get(url);
        var videosList = json.decode(response.body)["data"];
        setPlayList(videosList);
      }
    }
    
    

    Old answer : Aug 19 2019

    In my case :

    form_screen.dart

    class Form extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return ChangeNotifierProvider(
          builder: (_) {
            return FormProvider(id: ...); // Passing Provider to child widget
          },
          child: FormWidget(), // So Provider.of(context) can be read here
        );
      }
    }
    
    
    class FormWidget extends StatefulWidget {
      @override
      _FormWidgetState createState() => _FormWidgetState();
    }
    
    class _FormWidgetState extends State {
      final _formKey = GlobalKey();
    
      // No need to override initState like your code
    
      @override
      Widget build(BuildContext context) {
        var formState = Provider.of(context) // access any provided data
        return Form(
          key: _formKey,
          child: ....
        );
      }
    }
    

    FormProvider as a class, need to update their latest value from API. So, initially, it will request to some URL and updates corresponding values.

    form_provider.dart

    class FormProvider with ChangeNotifier {
      DocumentModel document;
      int id;
    
      FormProvider({@required int id}) {
        this.id = id;
        initFormFields(); // will perform network request
      }
    
      void initFormFields() async {
        Map results = initializeDataFromApi(id: id);
        try {
          document = DocumentModel.fromJson(results['data']);
        } catch (e) {
          // Handle Exceptions
        }
        notifyListeners(); // triggers FormWidget to re-execute build method for second time
      }
    

    In your case :

    PlayList.dart

    class PlaylistScreen extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return ChangeNotifierProvider(
          builder: (_) {
            return VideosProvider(); // execute construct method and fetchVideosList asynchronously
          },
          child: Playlist(),
        );
      }
    }
    
    
    class Playlist extends StatefulWidget {
      @override
      _PlaylistState createState() => _PlaylistState();
    }
    
    class _PlaylistState extends State {
      final _formKey = GlobalKey();
    
      @override
      void initState() {
        super.initState();
        // We *moved* this to build method
        // videosState = Provider.of(context);
    
        // We *moved* this to constructor method in provider
        // videosState.fetchVideosList();
      }
    
      @override
      Widget build(BuildContext context) {
        // Moved from initState
        var videosState = Provider.of(context);
    
        return Scaffold(
          appBar: AppBar(
            title: Text('My Videos'),
          ),
          body: RefreshIndicator(
      }
    }
    

    provider.dart

    class VideosProvider with ChangeNotifier {
    
      VideosProvider() {
        // *moved* from Playlist.initState()
        fetchVideosList(); // will perform network request
      }
    
      List _playlist;
      int _currentVideoId;  
      get playlist => _playlist;
    
      void setPlayList(videosList) {
        _playlist = videosList;
      }
    
      Future fetchVideosList() async {
        http.Response response =
            await http.get("http://192.168.1.22:3000/videos-list/");
    
        print(json.decode(response.body));
        videos = json.decode(response.body)["data"];
        setPlayList(videos);
        // return videos; // no need to return
    
        // We need to notify Playlist widget to rebuild itself for second time
        notifyListeners(); // mandatory
      }
    }
    

提交回复
热议问题