Brian Oh
Brian Oh

Reputation: 10720

Flutter - Custom Widget - How to obtain a value from

This is a simple example of the problem that I have. Given the following example, how can I obtain the value of "counter" from outside the Class?

class Counter extends StatefulWidget {
  @override
  _CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int counter = 0;
  void increaseCount() {
    setState(() => this.counter++);
    print("New count = $counter");
  }

  Widget build(context) {
    return new RaisedButton(
      onPressed: increaseCount,
      child: new Text('Tap To Add'),
    );
  }
}

Upvotes: 8

Views: 7816

Answers (4)

Brian Oh
Brian Oh

Reputation: 10720

This is an update of my previous answer to better illustrate what I want to achieve. I am currently using this concept in my code using a Checkbox to store its own value.

This is simply an example (proof-of-concept) to demonstrate what I want to achieve. I simply want the Widget to store its value and to give it to me when I want it (ie. to update data). The alternative is for the main program to store the value, but I don't think that is ideal.

The following appears to work. It seems top me like a lot to do to simply obtain the value of the counter. I hope that there is an easier way. Code is below:

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  final String sTitle = 'Flutter Counter-With-State Demo';
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: sTitle,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: sTitle),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  CounterWithState _counterWithState;
  FloatingActionButton _fab;
  int _iCounterOriginal = 99;
  RaisedButton _raisedButton;
  SizedBox _sizedBox;
  final _scaffoldKey = GlobalKey<ScaffoldState>();

  @override
  void initState() {
    super.initState();
    _fab = FloatingActionButton(
      onPressed: _showSnackbar,
      tooltip: 'Press to show Counter',
      child: Icon(Icons.info),
    );
    _raisedButton = RaisedButton(
        child: const Text('Update'),
        color: Theme.of(context).accentColor,
        elevation: 4.0,
        splashColor: Colors.blueGrey,
        onPressed: () {
          _iCounterOriginal = _counterWithState.iCounter;
          _counterWithState = null;
          _getCounterWithState(context);
          setState(() {});
        });
    _sizedBox = SizedBox(height: _raisedButton.height);
  }

  fnCounterChanged(int iCounter) {
    setState(() {});
  }

  _showSnackbar() {
    _scaffoldKey.currentState.showSnackBar(SnackBar(
        backgroundColor: Colors.blue,
        content:
            Text("Current value of Counter is ${_counterWithState.iCounter}")));
  }

  @override
  Widget build(BuildContext context) {
    _counterWithState = _counterWithState != null
        ? _counterWithState
        : _getCounterWithState(context);
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        title: Text(widget.title),
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            _counterWithState.getCounterWidget(),
            _getUpdateButton(context),
          ],
        ),
      ),
      floatingActionButton: _fab,
    );
  }

  Widget _getUpdateButton(BuildContext context) {
    return _counterWithState == null ||
            _counterWithState.iCounter == _iCounterOriginal
        ? _sizedBox
        : _raisedButton;
  }

  CounterWithState _getCounterWithState(context) {
    if (_counterWithState == null)
      _counterWithState = CounterWithState(
          iCounter: _iCounterOriginal,
          isAllowedChange: true,
          fnNotifyChange: fnCounterChanged);
    return _counterWithState;
  }
}
import 'package:flutter/material.dart';

class CounterWithState {
  int _iCounter;
  final Function fnNotifyChange;
  CounterWithStateInternal _counterWithStateInternal;

  fnDataChanged(int iNewCounter) {
    _iCounter = iNewCounter;
    if (fnNotifyChange != null) fnNotifyChange(iNewCounter);
  }

  CounterWithStateInternal getCounterWidget() {
    return _counterWithStateInternal;
  }

  CounterWithState(
      {@required iCounter,
      @required bool isAllowedChange,
      this.fnNotifyChange}) {
    _iCounter = iCounter;
    _counterWithStateInternal = CounterWithStateInternal(
        GlobalKey(), this._iCounter, isAllowedChange, fnDataChanged);
  }
  get iCounter => _iCounter;
}

class CounterWithStateInternal extends StatefulWidget {
  final int iCounter;
  final bool tfAllowChange;
  final Function fnDataChanged;
  CounterWithStateInternal(
      Key key, this.iCounter, this.tfAllowChange, this.fnDataChanged)
      : super(key: key);
  @override
  CounterWithStateMain createState() => CounterWithStateMain();
}

class CounterWithStateMain extends State<CounterWithStateInternal> {
  int _iCounter;
  int _iOriginalCounter;

  @override
  initState() {
    super.initState();
    _iCounter = widget.iCounter;
    _iOriginalCounter = widget.iCounter;
  }

  void incrementCounter(int iValue) {
    if (widget.tfAllowChange) {
      setState(() => this._iCounter += iValue);
      widget.fnDataChanged(_iCounter);
    }
  }

  Widget build(context) {
    return Column(children: <Widget>[
      Text("Value of original counter = $_iOriginalCounter"),
      _getSizedBox(),
      Text("Value of counter = $_iCounter"),
      _getSizedBox(),
      RaisedButton(
        onPressed: (() => incrementCounter(1)),
        child: Text('Tap To Add'),
      ),
      _getSizedBox(),
      RaisedButton(
        onPressed: (() => incrementCounter(-1)),
        child: Text('Tap To Subtract'),
      ),
      _getSizedBox(),
    ]);
  }

  SizedBox _getSizedBox() {
    return SizedBox(height: 20.0);
  }
}

Upvotes: 0

Brian Oh
Brian Oh

Reputation: 10720

This is simply an example (proof-of-concept) to demonstrate what I want to achieve. I simply want the Widget to store its value and to give it to me when I want it (ie. to update data). The alternative is for the main program to store the value, but I don't think that is ideal.

The following appears to work. It seems top me like a lot to do to simply obtain the value of the counter. I hope that there is an easier way. Code is below:

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

void main() => runApp(MyApp());

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

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  CounterWithState _counterWithState =
      CounterWithState(iCounter: 0, isAllowedChange: true);
  FloatingActionButton _fab;
  final _scaffoldKey = GlobalKey<ScaffoldState>();
  @override
  void initState() {
    super.initState();
    _fab = FloatingActionButton(
      onPressed: _showSnackbar,
      tooltip: 'Press to show Counter',
      child: Icon(Icons.info),
    );
  }

  _showSnackbar() {
    _scaffoldKey.currentState.showSnackBar(SnackBar(
        backgroundColor: Colors.blue,
        content:
            Text("Current value of Counter is ${_counterWithState.iCounter}")));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            _counterWithState.getCounterWidget(),
          ],
        ),
      ),
      floatingActionButton: _fab,
    );
  }
}
import 'package:flutter/material.dart';

class CounterWithState {
  int _iCounter;
  CounterWithStateInternal _counterWithStateInternal;

  fnDataChanged(int iNewCounter) {
    _iCounter = iNewCounter;
    debugPrint("CounterWithState: New value = $_iCounter");
  }

  CounterWithStateInternal getCounterWidget() {
    return _counterWithStateInternal;
  }

  CounterWithState({@required iCounter, @required bool isAllowedChange}) {
    _iCounter = iCounter;
    _counterWithStateInternal = CounterWithStateInternal(
        GlobalKey(), this._iCounter, isAllowedChange, fnDataChanged);
  }
  get iCounter => _iCounter;
}

class CounterWithStateInternal extends StatefulWidget {
  final int iCounter;
  final bool tfAllowChange;
  final Function fnDataChanged;
  CounterWithStateInternal(
      Key key, this.iCounter, this.tfAllowChange, this.fnDataChanged)
      : super(key: key);
  @override
  CounterWithStateMain createState() => CounterWithStateMain();
}

class CounterWithStateMain extends State<CounterWithStateInternal> {
  int _iCounter = 0;

  @override
  initState() {
    super.initState();
    _iCounter = widget.iCounter;
  }

  void increaseCount() {
    if (widget.tfAllowChange) {
      setState(() => this._iCounter++);
      widget.fnDataChanged(_iCounter);
      print("CounterWithStateMain: New count = $_iCounter");
    }
  }

  Widget build(context) {
    return Column(children: <Widget>[
      Text("Value of counter = $_iCounter"),
      SizedBox(
        height: 20.0,
      ),
      RaisedButton(
        onPressed: increaseCount,
        child: Text('Tap To Add'),
      )
    ]);
  }
}

Upvotes: 0

JSAN L.
JSAN L.

Reputation: 492

You could use a GlobalKey like this:

class Counter extends StatefulWidget {
  @override
  final globalKey = GlobalKey<_CounterState>();

  @override
  _CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int counter = 0;
  void increaseCount() {
    setState(() => counter++);
    print("New count = $counter");
  }

  @override
  Widget build(context) {
    return RaisedButton(
      onPressed: increaseCount,
      child: Text('Tap To Add'),
    );
  }
}

and access the counter like this:

Counter counter = Counter();
int count = counter.globalKey.currentState.counter;

A word of caution: This is not recommended.

You're transitioning from a simple, contained state to a state which is shared between several widgets. There are several better ways to address this problem. For further information and better ways to deal with the problem visit https://flutter.dev/docs/development/data-and-backend/state-mgmt .

Upvotes: 0

Hosar
Hosar

Reputation: 5292

In Flutter what you normally do is to pass a callback function, in that function you can pass the value you need, e.g.

class Counter extends StatefulWidget {
  // you can use a callback function
  final ValueSetter<int> callback;

  Counter({this.callback});

  @override
  _CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int counter = 0;
  void increaseCount() {
    setState(() => this.counter++);
    print("New count = $counter");
    // Here you can pass the value
    widget.callback(this.counter);
  }

  Widget build(context) {
    return new RaisedButton(
      onPressed: increaseCount,
      child: new Text('Tap To Add'),
    );
  }
}

And when calling your widget, you do as follow:

Counter(callback: (counter){
      // Here you can take some actions on counter
    });

This is the most simple way I know, or you can use some other patterns like bloc or something else.
Hope this help.

Upvotes: 11

Related Questions