【问题标题】:Accessing a method of state class using its stateful widget?使用状态小部件访问状态类的方法?
【发布时间】:2019-10-14 23:52:29
【问题描述】:

我在状态类中有一个方法,但我需要使用它的小部件类引用在外部访问该方法,

class TestFormState extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _testState();
  }
}

class _testFormState extends State<TestFormState> {
  int count = 1;

  @override
  Widget build(BuildContext context) {
    return Center(
        child: Container(
        color: Colors.green,
            child: Text("Count : $count"),
        ),
    );
  }

  clickIncrease(){
    setState(() { count += 1; });
  }
}

我需要在另一个小部件中访问上述小部件的 clickIncrease,如下面的代码,

class TutorialHome extends StatelessWidget {

    TestFormState test;

  @override
  Widget build(BuildContext context) {
    // Scaffold is a layout for the major Material Components.
    return Scaffold(
      body: Column(
         children: <Widget>[
            test = TestFormState(),
            FlatButton(
               child: Text("Increase"),
               onPressed: (){
                  test.state.clickIncrease(); // This kind of thing I need to do
               },
            ),
         ]
      ),
    );
  }

我写上面的代码只是为了演示这个问题。

【问题讨论】:

标签: flutter dart


【解决方案1】:

我认为有更好的方法可以轻松管理您的应用状态,并且我同意使用provider 可能是有效的。

为应用内的所有小部件提供模型。我们正在使用 ChangeNotifierProvider 因为这是一种简单的重建方法 模型更改时的小部件。我们也可以只使用 Provider,但是 那么我们就得自己听 Counter 了。

阅读 Provider 的文档以了解所有可用的提供程序。

在构建器中初始化模型。这样,提供者可以拥有 计数器的生命周期,确保在不需要时调用dispose 没有了。

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => Counter(),
      child: MyApp(),
    ),
  );
}

最简单的模型,只有一个字段。 ChangeNotifier 是一个 在flutter:foundation 上课。 Counter 不依赖于 Provider。

class Counter with ChangeNotifier {
  int count = 1;

  void clickIncrease() {
    count += 1;
    notifyListeners();
  }
}

Consumer 查找祖先 Provider 小部件并检索其 模型(计数器,在这种情况下)。然后它使用该模型来构建 小部件,并且会在模型更新时触发重建。

您可以在可以访问上下文的任何地方访问您的提供程序。 一种方法是使用Provider&lt;Counter&gt;.of(context)

提供程序包还定义了上下文本身的扩展方法。 您可以在任何构建方法中调用context.watch&lt;Counter&gt;() 小部件访问 Counter 的当前状态,并要求 Flutter 随时重建您的小部件计数器更改。

您不能在外部构建方法中使用context.watch(),因为那样 通常会导致细微的错误。相反,您应该使用 context.read&lt;Counter&gt;(),获取当前状态但不获取 向 Flutter 询问未来的重建。

因为我们处于回调中,每当用户点击时都会调用该回调 FloatingActionButton,我们这里不在构建方法中。我们 应该使用context.read()

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Scaffold is a layout for the major Material Components.
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Demo Home Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('Count:'),
            Consumer<Counter>(
              builder: (context, counter, child) => Text(
                '${counter.value}',
                style: Theme.of(context).textTheme.headline4,
              ),
            ),
          ],
        ),
      ),
      // I've change the button to `FloatingActionButton` for better ui experience.
      floatingActionButton: FloatingActionButton(
        // Here is the implementation that you are looking for.
        onPressed: () {
          var counter = context.read<Counter>();
          counter.increment();
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

完整代码:

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

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => Counter(),
      child: MyApp(),
    ),
  );
}

class Counter with ChangeNotifier {
  int count = 1;

  void clickIncrease() {
    count += 1;
    notifyListeners();
  }
}

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

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Demo Home Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('Count:'),
            Consumer<Counter>(
              builder: (context, counter, child) => Text(
                '${counter.count}',
                style: Theme.of(context).textTheme.headline4,
              ),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          var counter = context.read<Counter>();
          counter.clickIncrease();
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

实际应用:

有关提供程序包(Provider 来自哪里)的更多信息,请参阅package documentation

有关 Flutter 中状态管理的更多信息以及其他方法的列表,请访问State management page at flutter.dev

【讨论】:

    【解决方案2】:

    我有一个技巧,但我不知道这是不是一个坏习惯。

    class TestFormState extends StatefulWidget {
    
      _TestFormState _testFormState;
    
      @override
      State<StatefulWidget> createState() {
        _testFormState = _TestFormState();
        return _testFormState;
      }
    }
    
    class _TestFormState extends State<TestFormState> {
      int count = 1;
    
      @override
      Widget build(BuildContext context) {
        return Center(
          child: Container(
            color: Colors.green,
            child: Text("Count : $count"),
          ),
        );
      }
    
      clickIncrease(){
        setState(() { count += 1; });
      }
    }
    

    现在,您可以在这里访问它:

    class TutorialHome extends StatelessWidget {
    
      TestFormState test;
    
      @override
      Widget build(BuildContext context) {
        // Scaffold is a layout for the major Material Components.
        return Scaffold(
          body: Column(
              children: <Widget>[
                TextButton(
                  child: Text("Increase"),
                  onPressed: () {
                    test._testFormState
                        .clickIncrease(); // This is accessable
                  },
                ),
              ]
          ),
        );
      }
    }
    

    我建议看看ValueNotifier

    【讨论】:

    • 像魅力一样工作,但我收到警告说不应在 createState() 中添加任何逻辑。
    • @charles 是的,因为我在回答中说这是不好的做法,请考虑使用状态管理解决方案或使用 ValueNotifier
    • 是的,这就是我最终所做的。谢谢。
    • 这是不好的做法,因为您正在破坏状态类的封装。一旦你向公众公开了你的类的内部工作,那么它就变成了公共 API,从私有区域使用的任何东西都必须保持原样,并且不能在不破坏依赖于这些方法或属性的代码的情况下进行重构.在宠物项目中不是那么重要,但是在需要在生产中增长的项目中,您可以针对破坏现有功能的代码采取的每一项预防措施都是至关重要的。错误和死服务会扼杀初创公司并使其收入匮乏。
    • 然而,当你想快速连接一些东西,检查它是否有效,然后作为一个负责任的程序员,你将努力通过公共 API 公开所需的功能时,这个技巧很有用。跨度>
    【解决方案3】:

    有一个内置方法findAncestorStateOfType 可以找到Parent MyApp 类的Ancestor _MyAppState 类。

    这是代码

    class MyApp extends StatefulWidget {
        const MyApp({Key? key}) : super(key: key);
        static void setLocale(BuildContext context, Locale locale) {
            _MyAppState? state = context.findAncestorStateOfType<_MyAppState>();
            state!.setLocale(locale);
        }
    
        @override
        _MyAppState createState() => _MyAppState();
    }
    
    // ignore: use_key_in_widget_constructors
    class _MyAppState extends State<MyApp> {
        // const MyApp({Key? key}) : super(key: key)
        late Locale _locale;
    
        void setLocale(Locale value) {
            setState(() {
                _locale = value;
            });
        }
    }
    

    【讨论】:

    • 一个好的答案将始终包括解释为什么这会解决问题,以便 OP 和任何未来的读者可以从中学习。
    猜你喜欢
    • 2021-05-19
    • 2019-01-14
    • 2020-04-26
    • 2017-03-19
    • 2021-12-24
    • 2016-06-20
    • 1970-01-01
    • 2021-06-10
    • 2019-09-21
    相关资源
    最近更新 更多