How to use flutter provider in a statefulWidget?

前端 未结 3 1341
礼貌的吻别
礼貌的吻别 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:42

    When using a Provider you don’t need to use a StatefulWidget (as of a tutorial by the Flutter team State management

    You may use the following tutorial to see how to fetch data with a provider and a StatelessWidget: Flutter StateManagement with Provider

    0 讨论(0)
  • 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<VideosProvider>(
          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<PlaylistScreen> {
      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<VideosProvider>(context);
        _playlistList = videosState.playlist;
        _stage = videosState.stage;
        _errorMessage = videosState.errorMessage;
      }
    
      void actionSearch() {
        String text = _searchTextCtrl.value.text;
        Provider.of<VideosProvider>(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: <Widget>[
                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<FormProvider>(
          builder: (_) {
            return FormProvider(id: ...); // Passing Provider to child widget
          },
          child: FormWidget(), // So Provider.of<FormProvider>(context) can be read here
        );
      }
    }
    
    
    class FormWidget extends StatefulWidget {
      @override
      _FormWidgetState createState() => _FormWidgetState();
    }
    
    class _FormWidgetState extends State<FormWidget> {
      final _formKey = GlobalKey<FormState>();
    
      // No need to override initState like your code
    
      @override
      Widget build(BuildContext context) {
        var formState = Provider.of<FormProvider>(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<VideosProvider>(
          builder: (_) {
            return VideosProvider(); // execute construct method and fetchVideosList asynchronously
          },
          child: Playlist(),
        );
      }
    }
    
    
    class Playlist extends StatefulWidget {
      @override
      _PlaylistState createState() => _PlaylistState();
    }
    
    class _PlaylistState extends State<Playlist> {
      final _formKey = GlobalKey<FormState>();
    
      @override
      void initState() {
        super.initState();
        // We *moved* this to build method
        // videosState = Provider.of<VideosProvider>(context);
    
        // We *moved* this to constructor method in provider
        // videosState.fetchVideosList();
      }
    
      @override
      Widget build(BuildContext context) {
        // Moved from initState
        var videosState = Provider.of<VideosProvider>(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
      }
    }
    
    0 讨论(0)
  • 2021-02-10 08:54

    When using Provider for state management you don't need to use StatefullWidget, so how can you call a method of the ChangeNotifier on start of the app?

    You can simply do that in the constructor of the ChangeNotifier, so that when you point out VideosProvider() to the ChangeNotifierProvider Builder the constructor will get called the first time the provider constructs the VideosProvider, so:

    PlayList.dart:

    class Playlist extends StatelessWidget {
    
    @override
    Widget build(BuildContext context) {
      final videosState = Provider.of<VideosProvider>(context);
      var videos = videosState.playlist;
      return Scaffold(
        appBar: AppBar(
          title: Text('My Videos'),
        ),
        body: RefreshIndicator(
          child: Container(
            width: double.infinity,
            height: double.infinity,
            child: videos.length
                ? ListView.builder(
                    itemBuilder: (BuildContext context, index) {
                      return _videoListItem(context, index, videos, videosState);
                    },
                    itemCount: videos.length,
                  )
                : Center(
                    child: CircularProgressIndicator(),
                  ),
          ),
          onRefresh: () => null,
        ),
      );
    

    } }

    VideosProvider.dart:

    class VideosProvider with ChangeNotifier {
    
      VideosProvider(){
        fetchVideosList();
      }
    
      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;
     }
    

    }

    0 讨论(0)
提交回复
热议问题