Search code examples
flutterflutter-provider

How to call method at App start if I am using provider?


I want to complete get request to server to get data for my app at it start. I read several topics that describe how to run method after building widgets. But all of them are describe situations when provider is not using. And I am not sure that it's good idea to do this request inside widget.

I tried several approaches but did not get success. Here is my code:

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ChangeNotifierProvider<TenderApiData>(
          builder: (_) => TenderApiData(), child: HomePage()),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(appBar: AppBar(), body: MainContainer());
  }

}

class TenderApiData with ChangeNotifier {
  String access_token;
  List<Map<String, String>> id_names;

  String access_token_url =  "https://...";

  getApiKey() async { // I need to call this method at app start up 
    var response = await http
        .post(access_token_url, headers: {"Accept": "application/json"});
    if (response.statusCode == 200) {
      access_token = json.decode(response.body)['access_token'];
      notifyListeners();
    }
  }

}

class MyTestWidget extends StatefulWidget {
  MyTestWidgetState createState() => MyTestWidgetState();

}

class MyTestWidgetState extends State<MyTestWidget> {

  bool isKeyGetted = false;

  // before I used this when I extracted data on click event. 
  // I am not sure that it's needed now
  @override
  void didChangeDependencies() { 
    if (!isKeyGetted) {
      Provider.of<TenderApiData>(context).getApiKey();
      isKeyGetted = !isKeyGetted;
    }
    super.didChangeDependencies();
  }

  @override
  Widget build(BuildContext context) {

    if (!isKeyGetted) {
      Provider.of<TenderApiData>(context).getApiKey();
      isKeyGetted = !isKeyGetted;
    }

    var result = Provider.of<TenderApiData>(context).access_token;
    var test = Provider.of<TenderApiData>(context).id_names;
    return Column(
      children: <Widget>[
        RaisedButton(
          onPressed: Provider.of<TenderApiData>(context).getRegionsList,
          child: Text("get regions"),
        ),
      ],
    );
  }


}

class MainContainer extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Table(
      children: [
        TableRow(children: [
          Row(
            children: <Widget>[
              Container(child: MyTestWidget()),
              Container(child: Text("Regions"),),

              Expanded(child: SelectRegions(),  )
            ],
          )
        ]),
        TableRow(children: [
          Row(
            children: <Widget>[
              Text("Label"),
              Text("Value"),
            ],
          )
        ]),
      ],
    );
  }
}

Solution

  • You can store TenderApiData as member of MyApp, make a startup call in MyApp constructor and pass existing instance to descendants.
    Here is how it will look:

    class MyApp extends StatelessWidget {
    
      final TenderApiData _tenderApiData = TenderApiData();
    
      MyApp() {
        _tenderApiData.getApiKey();
      };
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: ChangeNotifierProvider<TenderApiData>(
              builder: (_) => _tenderApiData, child: HomePage()),
        );
      }
    }
    

    Other classes will stay unchanged.

    Another option would be to pass TenderApiData as constructor parameter into MyApp, making it more testable.

    void main() {
      final TenderApiData tenderApiData = TenderApiData();
      tenderApiData.getApiKey(); // call it here or in MyApp constructor - now it can be mocked and tested
      runApp(MyApp(tenderApiData));
    }
    
    class MyApp extends StatelessWidget {
    
      final TenderApiData _tenderApiData;
    
      MyApp(this._tenderApiData);
    
    // ...