如果我使用提供程序,如何在应用程序启动时调用方法?

Posted

技术标签:

【中文标题】如果我使用提供程序,如何在应用程序启动时调用方法?【英文标题】:How to call method at App start if I am using provider? 【发布时间】:2019-12-10 19:34:51 【问题描述】:

我想在服务器启动时完成get 请求以获取我的应用程序的数据。我阅读了几个主题,这些主题描述了如何在构建小部件后运行方法。但它们都是描述provider 不使用时的情况。而且我不确定在小部件中执行此请求是否是个好主意。

我尝试了几种方法,但都没有成功。 这是我的代码:

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"),
            ],
          )
        ]),
      ],
    );
  

【问题讨论】:

【参考方案1】:

您可以将TenderApiData 存储为MyApp 的成员,在MyApp 构造函数中进行启动调用并将现有实例传递给后代。 下面是它的外观:

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()),
    );
  

其他类将保持不变。

另一种选择是将TenderApiData 作为构造函数参数传递给MyApp,使其更易于测试。

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);

// ...

【讨论】:

谢谢!你的解决方案有什么好处吗?或者关于构造函数的另一个答案是完全等价的? 虽然我一直在思考这个问题,但我想出了另一个更干净的解决方案。最好将TenderApiData 实例作为参数传递给MyApp。它将使代码更具可测试性。如果您有TenderApiData 的单个实例,另一种解决方案也可以使用。如果您多次创建它 - 每次创建它都会调用。此外,恕我直言,它增加了测试和调试的复杂性。 谢谢!但是如果我需要打电话怎么办: await _tenderApiData.getApiKey(); _tenderApiData.getRegionsList(); (在第一次强制之前无法运行第二种方法)。构造函数可以是async 吗? 不,构造函数必须是同步的。它可以启动期货,但不能await 获得结果。但是,可以在期货上使用 .then 安排链式调用 您能否在答案中提及它?【参考方案2】:

您可以在TenderApiData 上添加一个构造函数来触发自定义逻辑:

class TenderApiData with ChangeNotifier 
  TenderApiData() 
    // TODO: call `getApiKey`
  

【讨论】:

但是如果我需要第一个方法完成怎么办。构造函数中不能调用 await 方法。 没有什么能阻止你使用异步方法。 但是在构造函数中不可能调用await,因为构造函数不能是异步的。 方法可以异步调用await。有什么问题?【参考方案3】:

您可以使用FutureProvider 值。

单独的方法 api 到服务(my_service.dart):

class MyService 
  Future<String> 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) 
      return json.decode(response.body)['access_token'];
    
  

然后从 MyApp 调用

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return FutureProvider<String>.value(
      value: MyService().getApiKey(),
      child: HomePage(),
    );
  

在主页中:

class HomePage extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    String token = Provider.of<String>(context);
    return Scaffold();
  

【讨论】:

【参考方案4】:

您也可以只使用布尔标志来运行该方法只运行一次

提供者

import 'screen.abstract.dart';

class MyProvider with ChangeNotifier 
  _hasInitialized = false;

  // This will only be run once, when called
  fetchApiOnce() 
    if (_hasInitialized) 
      return;
    
    _hasInitialized = true;

    /* DO STUFF */
  

【讨论】:

以上是关于如果我使用提供程序,如何在应用程序启动时调用方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何阻止我的 MFC 应用程序在启动时调用 OnFileNew()?

防止在首次启动 iOS 时调用 AFNetworking 可达性状态

从后台被杀死的应用程序中恢复 - 重新启动时调用哪些方法?

javascript在启动时调用托管bean的方法,而不是在命令按钮单击时调用它们

Java:如何创建一个仅在其实例不存在时才启动并在实例存在时调用该实例的应用程序?

如何在应用启动期间触发方法? (安卓)