Flutter: Are RenderObjects recreated during lifetime of a StatefulWidget? - flutter

I have a setup where a StatefulWidget creates a custom (leaf) RenderBox. Is there are possibility that the RenderBox is disposed and recreated during the lifetime of the State?
Background. I want to keep certain results of heavy calculations/animation controllers in the RenderObject, that's why it is important for me that the RenderObject lives as long as the State of the enclosing widget.
import 'package:flutter/widgets.dart';
class SquaredCircle extends StatefulWidget {
const SquaredCircle({Key key, this.radius}) : super(key: key);
final double radius;
#override
State<StatefulWidget> createState() => _SquaredCircleState();
}
class _SquaredCircleState extends State<SquaredCircle> {
int _squareness = 42;
#override
Widget build(BuildContext context) {
final foo = widget.radius * _squareness;
return _SquaredCircleRenderObjectWidget(foo);
}
}
class _SquaredCircleRenderObjectWidget extends LeafRenderObjectWidget {
_SquaredCircleRenderObjectWidget(this.foo);
final double foo;
#override
_RenderSquaredCircle createRenderObject(BuildContext context) {
return _RenderSquaredCircle(foo: foo);
}
#override
void updateRenderObject(BuildContext context, _RenderSquaredCircle renderObject) {
renderObject..foo = foo;
}
}
class _RenderSquaredCircle extends RenderBox {
_RenderSquaredCircle({double foo})
: assert(foo != null),
_foo = foo;
double get foo => _foo;
double _foo;
set foo(double value) {
assert(value != null);
if (_foo == value) return;
_foo = value;
markNeedsPaint();
}
// ...
}

A RenderObject will not be disposed until you replace it by another RenderObject of a different type, or remove the associated widget from the widget tree.

Related

Flutter assign generic Future<T> from function callback

Why does not generic Future<T> assign work? i Get this error: A value of type 'Future<T>?' can't be assigned to a variable of type 'Future<T>?'.
typedef SimpleFutureFunction<T> = Widget Function(void Function(Future<T>? newFuture) onFuture);
class SimpleFuture<T> extends StatefulWidget {
const SimpleFuture({Key? key, required this.simple, this.future}) : super(key: key);
final SimpleFutureFunction simple;
final Future<T>? future;
#override
State<SimpleFuture> createState() => _SimpleFutureState();
}
class _SimpleFutureState<T> extends State<SimpleFuture<T>> {
Future<T>? _future;
#override
void initState() {
super.initState();
_future = widget.future;
}
#override
Widget build(BuildContext context) {
return widget.simple.call(<T>(Future<T>? newFuture) {
setState(() {
_future = newFuture;
});
});
}
}

How to use stateful widget parameters in state class at construction without adding the widget to the tree?

I stumped into a problem where I need to use a StatefulWidget parameter in its state class when it's constructed, but I couldn't find a way to do it since using widget.[whatever variable name] in the state's class constructor returns an unexpected null value, and the initState function only runs when the widget is being drawn to the screen.
For example:
import 'package:flutter/material.dart';
class Test extends StatefulWidget {
final String text;
Test(this.text);
final state = _TestState();
#override
_TestState createState() => state;
}
class _TestState extends State<Test> {
String? changingText;
void updateChangingText(String moreText){
changingText = changingText! + moreText;
}
#override
void initState() {
super.initState();
changingText = widget.text;
}
#override
Widget build(BuildContext context) {
return Text(changingText!);
}
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
var w = Test('test');
w.state.updateChangingText(' text');
return MaterialApp(home: Scaffold(body:
Test('test text')
));
}
}
void main() {
runApp(App());
}
This doesn't work since changingText is being updated before initState gives it its initial value since it only runs when Text is being drawn to the screen and this:
import 'package:flutter/material.dart';
class Test extends StatefulWidget {
final String text;
Test(this.text);
final state = _TestState();
#override
_TestState createState() => state;
}
class _TestState extends State<Test> {
String? changingText;
void updateChangingText(String moreText){
changingText = changingText! + moreText;
}
_TestState(){
changingText = widget.text;
}
#override
Widget build(BuildContext context) {
return Text(changingText!);
}
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
var w = Test('test');
w.state.updateChangingText(' text');
return MaterialApp(home: Scaffold(body:
Test('test text')
));
}
}
void main() {
runApp(App());
}
doesn't work either since you can't use widget.[whatever] in state class constructors (for some reason).
So what can I do to use widget parameters in the state class before the widget is drawn to the screen?
Thanks in advance for the help
You should use the initState method present in the State for this instead of the constructor
#override
void initState() {
changingText = widget.text;
super.initState();
}

The value of local variable isn't used

I am new to flutter and I was following a tutorial when this error popped up. The ancestorRenderObjectOfType has deprecated and been replaced by findAncestorRenderObjectOfType so dart is throwing me errors.
What the tutor did in his video of old dart:
static T of<T extends BlocBase>(BuildContext context) {
final type = _typeOf<BlocProvider<T>>();
BlocProvider<T> provider = context
.context.ancestorRenderObjectOfType(type);
return provider.bloc;
}
static Type _typeOf() => T;
}
What I did in my code
static T of<T extends BlocBase>(BuildContext context) {
final type = _typeOf<BlocProvider<T>>();
BlocProvider<T> provider = context
.findAncestorRenderObjectOfType();
return provider.bloc;
}
static Type _typeOf<T>() => T;
}
If I put
BlocProvider<T> provider = context
.findAncestorRenderObjectOfType(type);
I get an error saying
Too many positional arguements. 0 expected, but 1 found.
The ENTIRE code
abstract class BlocBase {
void dispose();
}
//Genric Bloc provider
class BlocProvider<T extends BlocBase> extends StatefulWidget {
BlocProvider({
Key key,
#required this.child,
#required this.bloc,
}) : super(key: key);
final T bloc;
final Widget child;
#override
_BlocProviderState<T> createState() => _BlocProviderState<T>();
static T of<T extends BlocBase>(BuildContext context) {
final type = _typeOf<BlocProvider<T>>();
BlocProvider<T> provider = context
.findAncestorRenderObjectOfType(); //context.ancestorRenderObjectOfType(type); GOTCHA
return provider.bloc;
}
static Type _typeOf<T>() => T;
}
class _BlocProviderState<T> extends State<BlocProvider<BlocBase>> {
#override
void dispose() {
widget.bloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return widget.child;
}
}
Thank you!
findAncestorRenderObjectOfType doesnt take the type as argument instead it is a generic method where you can provide the type while calling the method. So your code will be as below:
static T of<T>(BuildContext context) {
BlocProvider<T> provider = context
.findAncestorRenderObjectOfType<BlocProvider<T>>();
return provider.bloc;
}

Mount a new state in Flutter

I'm new to Flutter. I have made a stateful widget that has multiple options for states, and I have no clue how to switch between them, or if it's even possible. Basically I have:
class SWidget extends StatefulWidget {
State<StatefulWidget> createState(){
return _State1();
}
}
class _State1 extends State<SWidget> {
...
}
class _State2 extends State<SWidget> {
...
}
I want SWidget to switch from _State1 to _State2
Sorry if I don't understand your question but state is often tied to UI. If you need new state then chances are you're wanting to display something different. In that case, you'd want to distinguish the two states (and their UI components) by placing them in their own stateful widgets. You can then switch between the two widgets in a stateful or stateless widget:
class SWidget extends StatelessWidget {
SWidget(this.condition);
final bool condition;
#override
Widget build(BuildContext context) {
return condition ? Foo() : Bar();
}
}
class Foo extends StatefulWidget {
#override
_FooState createState() => _FooState();
}
class _FooState extends State<Foo> {
var _foo = 'foo';
#override
Widget build(BuildContext context) {
return Text(_foo);
}
}
class Bar extends StatefulWidget {
#override
_BarState createState() => _BarState();
}
class _BarState extends State<Bar> {
var _bar = 'bar';
#override
Widget build(BuildContext context) {
return Text(_bar);
}
}
I don't think I've ever seen anyone actually switch the state object out from underneath a widget. You can though easily change a widget's state by calling setState and toggling between values that way too:
class SWidget extends StatefulWidget {
#override
_SWidgetState createState() => _SWidgetState();
}
class _SWidgetState extends State<SWidget> {
var _value = true;
#override
Widget build(BuildContext context) {
return Switch(
value: _value,
onChanged: (value) => setState(() => _value = value),
);
}
}

Passing data to StatefulWidget and accessing it in it's state in Flutter

I have 2 screens in my Flutter app: a list of records and a screen for creating and editing records.
If I pass an object to the second screen that means I am going to edit this and if I pass null it means that I am creating a new item. The editing screen is a Stateful widget and I am not sure how to use this approach https://flutter.io/cookbook/navigation/passing-data/ for my case.
class RecordPage extends StatefulWidget {
final Record recordObject;
RecordPage({Key key, #required this.recordObject}) : super(key: key);
#override
_RecordPageState createState() => new _RecordPageState();
}
class _RecordPageState extends State<RecordPage> {
#override
Widget build(BuildContext context) {
//.....
}
}
How can I access recordObject inside _RecordPageState?
To use recordObject in _RecordPageState, you have to just write widget.objectname like below
class _RecordPageState extends State<RecordPage> {
#override
Widget build(BuildContext context) {
.....
widget.recordObject
.....
}
}
Full Example
You don't need to pass parameters to State using it's constructor.
You can easily access these using widget.myField.
class MyRecord extends StatefulWidget {
final String recordName;
const MyRecord(this.recordName);
#override
MyRecordState createState() => MyRecordState();
}
class MyRecordState extends State<MyRecord> {
#override
Widget build(BuildContext context) {
return Text(widget.recordName); // Here you direct access using widget
}
}
Pass your data when you Navigate screen :
Navigator.of(context).push(MaterialPageRoute(builder: (context) => MyRecord("WonderWorld")));
class RecordPage extends StatefulWidget {
final Record recordObject;
RecordPage({Key key, #required this.recordObject}) : super(key: key);
#override
_RecordPageState createState() => new _RecordPageState(recordObject);
}
class _RecordPageState extends State<RecordPage> {
Record recordObject
_RecordPageState(this. recordObject); //constructor
#override
Widget build(BuildContext context) {. //closure has access
//.....
}
}
example as below:
class nhaphangle extends StatefulWidget {
final String username;
final List<String> dshangle;// = ["1","2"];
const nhaphangle({ Key key, #required this.username,#required this.dshangle }) : super(key: key);
#override
_nhaphangleState createState() => _nhaphangleState();
}
class _nhaphangleState extends State<nhaphangle> {
TextEditingController mspController = TextEditingController();
TextEditingController soluongController = TextEditingController();
final scrollDirection = Axis.vertical;
DateTime Ngaysx = DateTime.now();
ScrollController _scrollController = new ScrollController();
ApiService _apiService;
List<String> titles = [];
#override
void initState() {
super.initState();
_apiService = ApiService();
titles = widget.dshangle; //here var is call and set to
}
I have to Navigate back to any one of the screens in the list pages but when I did that my onTap function stops working and navigation stops.
class MyBar extends StatefulWidget {
MyBar({this.pageNumber});
final pageNumber;
static const String id = 'mybar_screen';
#override
_MyBarState createState() => _MyBarState();
}
class _MyBarState extends State<MyBar> {
final List pages = [
NotificationScreen(),
AppointmentScreen(),
RequestBloodScreen(),
ProfileScreen(),
];
#override
Widget build(BuildContext context) {
var _selectedItemIndex = widget.pageNumber;
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
elevation: 0,
backgroundColor: Colors.white,
unselectedItemColor: Colors.grey.shade700,
selectedItemColor: Color(kAppColor),
selectedIconTheme: IconThemeData(color: Color(kAppColor)),
currentIndex: _selectedItemIndex,
type: BottomNavigationBarType.fixed,
onTap: (int index) {
setState(() {
_selectedItemIndex = index;
});
},
You should use a Pub/Sub mechanism.
I prefer to use Rx in many situations and languages. For Dart/Flutter this is the package: https://pub.dev/packages/rxdart
For example, you can use a BehaviorSubject to emit data from widget A, pass the stream to widget B which listens for changes and applies them inside the setState.
Widget A:
// initialize subject and put it into the Widget B
BehaviorSubject<LiveOutput> subject = BehaviorSubject();
late WidgetB widgetB = WidgetB(deviceOutput: subject);
// when you have to emit new data
subject.add(deviceOutput);
Widget B:
// add stream at class level
class WidgetB extends StatefulWidget {
final ValueStream<LiveOutput> deviceOutput;
const WidgetB({Key? key, required this.deviceOutput}) : super(key: key);
#override
State<WidgetB> createState() => _WidgetBState();
}
// listen for changes
#override
void initState() {
super.initState();
widget.deviceOutput.listen((event) {
print("new live output");
setState(() {
// do whatever you want
});
});
}
In my app, often instead of using stateful widgets, I use mainly ChangeNotifierProvider<T> in main.dart, some model class
class FooModel extends ChangeNotifier {
var _foo = false;
void changeFooState() {
_foo = true;
notifyListeners();
}
bool getFoo () => _foo;
}
and
var foo = context.read<FooModel>();
# or
var foo = context.watch<FooModel>();
in my stateless widgets. IMO this gives me more precise control over the rebuilding upon runtime state change, compared to stateful widgets.
The recipe can be found in the official docs, the concept is called "lifting state up".