16

I have a method in state class, but I need to access that method in outside using its widget class reference,

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

and I need to access the above widget`s clickIncrease in another widget, like below code,

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

I wrote above code just for demostrate the issue.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Sachintha Udara
  • 635
  • 1
  • 7
  • 22
  • 1
    You should use a state management like Provider https://github.com/google/flutter-provide. Here is a video https://www.youtube.com/watch?v=vFxk_KJCqgk – Ciprian May 30 '19 at 04:18
  • 1
    Finally, I found a simple answer in this question, https://stackoverflow.com/a/54647947/5922001 – Sachintha Udara May 30 '19 at 05:54

4 Answers4

5

I have a trick, but I don't know if it is a bad practice or not.

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

Now, you can access it here :

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

I suggest taking a look at ValueNotifier

Ismaeel Sherif
  • 555
  • 7
  • 15
  • 1
    Works like a charm, but I get a warning saying that no logic should be added in createState(). – charles Dec 20 '21 at 22:41
  • @charles Yes, because I said in the answer that this is bad practice, consider a state management solution or use a ValueNotifier – Ismaeel Sherif Dec 21 '21 at 09:52
  • Yes, that's what I ended up doing. Thx. – charles Dec 22 '21 at 11:45
  • It's bad practice because you are breaking the encapsulation of the state class. Once you exposed to the public the inner workings of your class, then it becomes public API, whatever gets used from the private zone has to be maintained as it is and can't be refactored without breaking the code that depends on those methods or properties. Not so crucial in pet projects, however in projects that need to grow in production, every precaution you can take against code that breaks existing functionalities is vital. Bugs and dead services kill startups by chocking and starving them of revenue. – Adrian Moisa Dec 22 '21 at 13:32
  • However, this trick is useful when you want to quickly wire something up, check if it works and then as a responsible programmer you will do the effort to expose the needed functionality trough the public API. – Adrian Moisa Dec 22 '21 at 13:35
2

I think there is a better way to manage your app state in an easy way and I agree that using provider could be effective.

Provide the model to all widgets within the app. We're using ChangeNotifierProvider because that's a simple way to rebuild widgets when a model changes. We could also just use Provider, but then we would have to listen to Counter ourselves.

Read Provider's docs to learn about all the available providers.

Initialize the model in the builder. That way, Provider can own Counter's lifecycle, making sure to call dispose when not needed anymore.

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

Simplest possible model, with just one field. ChangeNotifier is a class in flutter:foundation. Counter does not depend on Provider.

class Counter with ChangeNotifier {
  int count = 1;

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

Consumer looks for an ancestor Provider widget and retrieves its model (Counter, in this case). Then it uses that model to build widgets, and will trigger rebuilds if the model is updated.

You can access your providers anywhere you have access to the context. One way is to use Provider<Counter>.of(context).

The provider package also defines extension methods on context itself. You can call context.watch<Counter>() in a build method of any widget to access the current state of Counter, and to ask Flutter to rebuild your widget anytime Counter changes.

You can't use context.watch() outside build methods, because that often leads to subtle bugs. Instead, you should use context.read<Counter>(), which gets the current state but doesn't ask Flutter for future rebuilds.

Since we're in a callback that will be called whenever the user taps the FloatingActionButton, we are not in the build method here. We should use 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),
      ),
    );
  }
}

Complete code:

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

Actual app:

enter image description here

For more information on the provider package (where Provider comes from), please see the package documentation.

For more information on state management in Flutter, and a list of other approaches, head over to the State management page at flutter.dev.

MαπμQμαπkγVπ.0
  • 5,887
  • 1
  • 27
  • 65
1

There is a built in method findAncestorStateOfType to find Ancestor _MyAppState class of the Parent MyApp class.

Here is the Code

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;
        });
    }
}
Tyler2P
  • 2,324
  • 26
  • 22
  • 31
  • 1
    A good answer will always include an explanation why this would solve the issue, so that the OP and any future readers can learn from it. – Tyler2P Dec 11 '21 at 10:22
-1
class TestForm extends StatelessWidget {
  final int _count;
  TestForm(int count) : _count = count;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        color: Colors.green,
        child: Text('Count : $_count'),
      ),
    );
  }
}
class TutorialHome extends StatefulWidget {
  @override
  State<TutorialHome> createState() => _TutorialHomeState();
}

class _TutorialHomeState extends State<TutorialHome> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          TestForm(_count), // <---
          TextButton(
            child: Text("Increase"),
            onPressed: () => setState(() => _count++),
          ),
        ],
      ),
    );
  }
}
Snow
  • 417
  • 5
  • 9