Prevent page to rebuild in flutter - flutter

I am using widget Image in flutter and it contains ternary or conditional statement.. here is the code:
#override
Widget build(BuildContext context) {
print("build widget");
return Image(
height: 100,
width: 100,
image: AssetImage('flutter.png'),
frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
if (wasSynchronouslyLoaded)
return child;
else {
return AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
child: frame != null ? child : widget.placeholder,
);
}
},
);
}
So, it depends on frame value, when the value changes.. the widget will be rebuilt from the start. In my case, print("build widget"); will be printed 2 times. Actually I am wrapping my widget with ValueListenableBuilder, so that it will be built only for widget that changes, it seems that I can not wrap the code abode using ValueListenableBuilder. So, is there a way to prevent the code above to build 2 times ?

Related

Flutter: How to dry-render a widget and get its size?

I'm trying to get the size of a widget. A common approach is to give a GlobalKey() to a widget, and retrieve its size after it has been laid out and shown to the user. Given the constraint (MediaQuery.of(context).size.width), is there a way to pre-render a widget and get its size (specifically height) before building the real one?
Edit: I want the height so badly because I need to pass the height to SliverAppBar. I can build the content first, and use setState to resize it after it get laid out. But this also mean that screen will flicker for once.
You can try offstage widget. So the layout will look like
Offstage(
offstage: true,
child: Container (
key: widgetKey
)
)
You can use Offstage. The trick is to use Offstage to render, then rebuild the moment you get the size using the postframecallback.
class _wrapperState extends State<wrapper> with WidgetsBindingObserver
{
Size? widgetSize;
GlobalKey widgetKey = GlobalKey();
late Widget sourceWidget;
#override
void initState()
{
WidgetsBinding.instance.addObserver(this);
// widget.child is your main widget you're trying to get the size.
sourceWidget = widget.child;
super.initState();
}
#override
Widget build(BuildContext context)
{
WidgetsBinding.instance.addPostFrameCallback((_)
{
if (widgetSize == null)
{
setState(()
{
widgetSize = widgetKey.currentContext!.size;
});
}
});
// Assign a key to your widget so that we can get its size later.
// Also this key will prevent the widget to get built twice.
Widget finalWidget = KeyedSubtree(
key: widgetKey,
child: sourceWidget
);
// If we don't have the size, then do the offstage.
// Postframe will kick off right after this.
if (widgetSize == null)
{
return Offstage(
child: Material(
body: Stack(
children: [
Positioned(
top: 0,
left: 0,
child: finalWidget,
)
],
),
),
);
}
// Now we do have our size.
// Do your main build now.
return MyComplexWidget();
}
}

flutter get widget width and height inside init state

I have a widget with layout builder:
#override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
return Transform.translate(
offset: Offset(100,20),
child: Container(
width: 50,
height: 50
);
},
);
}
I want to get the BoxConstraint of the layout builder inside initState:
double height;
double width;
void initState() {
super.initState();
// get constraint
height = constraint.maxHeight;
width = constraint.maxWidth;
}
How can I achieve such a thing?

Is it possible to extend a StatefulWidget that provides an extra parameter on its build method?

I would like to create a BaseScreen Widget like this to reuse in my app:
class BaseScreen extends StatelessWidget {
final Widget child;
BaseScreen({this.child});
#override
Widget build(BuildContext context) {
var safePadding = MediaQuery.of(context).padding.top +
MediaQuery.of(context).padding.bottom;
return Scaffold(
body: LayoutBuilder(
builder: (context, constraint) {
return SingleChildScrollView(
child: SafeArea(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraint.maxHeight - safePadding),
child: IntrinsicHeight(
child: child,
),
),
),
);
},
),
);
}
}
But the problem I see is that I would also like to reuse the constraint property that LayoutBuilder provides in the child of this class.
Currently, I need to create yet a new LayoutBuilder in the child, and that just sounds like more processing for the engine, and more boilerplate code.
If I could extend somehow this Widget so that in the child I could then have this:
#override
Widget build(BuildContext context, BoxConstraints constraints) {
}
That would be great. I know Flutter encourages composition over inheritance as well, so if I can solve it in another way, I'd also appreciate that.
Thank you!
TL;DR : No, use InheritedWidget to pass variables/data to child widgets, read more about it in here and here
Why not?
In Dart language it is only possible to add optional/named non-conflicting parameters to overridden methods.
For example:
class SuperClass {
void someMethod(String parameter1) {}
}
class SubClass1 extends SuperClass {
// adding optional parameter
#override
void someMethod(String paremeter1, [String paremter2]) {}
}
class SubClass2 extends SuperClass {
// adding optional named parameter
#override
void someMethod(String paremeter1, {String paremter2}) {}
}
Note: Dart does not support method overloading which means is a compile error to have two methods with same name but different parameters.
Now if you add BoxConstraints constraints in your build() method like this
#override
Widget build(BuildContext context, [BoxConstraints constraint]){
/// Your code
}
It will compile but who is going to give you that [constraint] parameter?
As developers we never call the build() method ourselves, the flutter framework calls that method for us.
Reason for that: Calling the build() method ourselves would be difficult because it requires context, and providing correct context value is something that only flutter framework does correctly. Most new developers pass around the context variable but it's not guaranteed if that will always work, because the place of the widget in the widget tree determines what is the correct context value for that widget. And during writing code, there is no easy way to figure out what is the exact place of the widget in the widget tree. Even if somehow we could figure out the place, what is the value of context for that place? Because flutter provides that value, how that value is created is for another post.
Solutions
There are two easy and very common solutions in flutter for passing data/variables to child widgets,
Using WidgetBuilder variants
Using InheritedWidget (Recommended)
Solution 1. Using WidgetBuilder variants
WidgetBuilder is a function that takes BuildContext and returns a Widget, sounds familiar?, it's the type definition of the build() method. But we already have build() method available, what's the point of WidgetBuilder?. The most common use case is for scoping the BuildContext.
For example:
If you click on "Show snackbar" it will not work and instead throw and error saying "Scaffold.of() called with a context that does not contain a Scaffold."
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FlatButton(
onPressed: () {
/// This will not work
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text('Hello')));
},
child: Text('Show snackbar'),
),
)
);
}
You might think, there is clearly a Scaffold widget present, but it says there is no scaffold? This is because the following line is using context provided by a widget above the Scaffold widget (the build() method).
Scaffold.of(context).showSnackBar(SnackBar(content: Text('Hello')));
If you wrap the FlatButton with the Builder widget, it will work try it.
Like many flutter widgets you could create a WidgetBuilder variant that provides additional parameters while building the widget like FutureBuilder's AsyncWidgetBuilder or like LayoutBuilder's LayoutWidgetBuilder
For example:
class BaseScreen extends StatelessWidget {
/// Instead of [child], a builder is used here
final LayoutWidgetBuilder builder;
const BaseScreen({this.builder});
#override
Widget build(BuildContext context) {
var safePadding = MediaQuery.of(context).padding.top +
MediaQuery.of(context).padding.bottom;
return Scaffold(
body: LayoutBuilder(
builder: (context, constraint) {
return SingleChildScrollView(
child: SafeArea(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraint.maxHeight - safePadding,
),
/// Here we forward the [constraint] to [builder],
/// so that it can forward it to child widget
child: builder(context, constraint),
),
),
);
},
),
);
}
}
And this is how you use it (Just like LayoutBuilder, but the child gets the parent widget's LayoutBuilder's constraint and only one LayoutBuilder is required
#override
Widget build(BuildContext context) {
return BaseScreen(
builder: (context, constraint) {
// TODO: use the constraints as you wish
return Container(
color: Colors.blue,
height: constraint.minHeight,
);
},
);
}
Solution 2. Using InheritedWidget (Recommended)
Sample InheritedWidget
/// [InheritedWidget]s are very efficient, in fact they are used throughout
/// flutter's source code. Even the `MediaQuery.of(context)` and `Theme.of(context)`
/// is actually an [InheritedWidget]
class InheritedConstraint extends InheritedWidget {
const InheritedConstraint({
Key key,
#required this.constraint,
#required Widget child,
}) : assert(constraint != null),
assert(child != null),
super(key: key, child: child);
final BoxConstraints constraint;
static InheritedConstraint of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<InheritedConstraint>();
}
#override
bool updateShouldNotify(covariant InheritedConstraint old) =>
constraint != old.constraint;
}
extension $InheritedConstraint on BuildContext {
/// Get the constraints provided by parent widget
BoxConstraints get constraints => InheritedConstraint.of(this).constraint;
}
Your child widget can access the BoxConstraints provided by this inherited widget like this
class ChildUsingInheritedWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
/// Get the constrains provided by parent widget
final constraint = context.constraints;
// TODO: use the constraints as you wish
return Container(
color: Colors.green,
height: constraint.minHeight,
);
}
}
And this is how you use connect these two widgets
In your BaseScreen wrap the child with InheritedConstraint
class BaseScreen extends StatelessWidget {
final Widget child;
const BaseScreen({this.child});
#override
Widget build(BuildContext context) {
var safePadding = MediaQuery.of(context).padding.top +
MediaQuery.of(context).padding.bottom;
return Scaffold(
body: LayoutBuilder(
builder: (context, constraint) {
return SingleChildScrollView(
child: SafeArea(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraint.maxHeight - safePadding,
),
child:
InheritedConstraint(constraint: constraint, child: child),
),
),
);
},
),
);
}
}
And you can use the BaseScreen anywhere you like
For example:
#override
Widget build(BuildContext context) {
return BaseScreen(child: ChildUsingInheritedWidget());
}
See this working DartPad example: https://dartpad.dev/9e35ba5c2dd938a267f0a1a0daf814a7
Note: I noticed this line in your example code:
var safePadding = MediaQuery.of(context).padding.top +
MediaQuery.of(context).padding.bottom;
If you are trying to get the padding provided by SafeArea() widget, then that line will not give you correct padding, because it's using wrong context it should use a context that is below SafeArea() to do that, use the Builder widget.
Example:
class BaseScreen extends StatelessWidget {
final Widget child;
const BaseScreen({this.child});
#override
Widget build(BuildContext context) {
return Scaffold(
body: LayoutBuilder(
builder: (context, constraint) {
return SingleChildScrollView(
child: SafeArea(
child: Builder(
builder: (context) {
var safePadding = MediaQuery.of(context).padding.top +
MediaQuery.of(context).padding.bottom;
return ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraint.maxHeight - safePadding,
),
child: child,
);
},
),
),
);
},
),
);
}
}
Of course you can. It will look something like this
abstract class BoxConstraintsWidget extends StatelessWidget {
Widget build(BuildContext context, BoxConstraints constraints);
#override
Widget build(BuildContext context) {
return build(context, BoxConstraints());
}
}
Then override it like
class BoxConstraintsWidgetChild extends BoxConstraintsWidget{
#override
Widget build(BuildContext context, BoxConstraints constraints) {
return someStuff;
}
}
There is one tiny problem though - Widget build(BuildContext context) is an inner framework method and you cannot force it to be called with more that one parameter(of course if you do not want to rewrite the complete flutter by yourself). The thing is you may use the approach above but add this BoxConstraints constraints as some getter in your base class with the default implementation and override it in its child if you want to. It will look like this:
abstract class BoxConstraintsWidget extends StatelessWidget {
BoxConstraints get constraints => BoxConstraints();
Widget build(BuildContext context, BoxConstraints constraints);
#override
Widget build(BuildContext context) {
return build(context, constraints);
}
}
And use it as it is or override it as
class BoxConstraintsWidgetChild extends BoxConstraintsWidget{
#override
BoxConstraints get constraints => MyBoxConstraints();
#override
Widget build(BuildContext context, BoxConstraints constraints) {
//here you will have you copy of constraints that = MyBoxConstraints()
//without overriding method you would have had a parent constraints that = BoxConstraints()
return someStuff;
}
}
This is only one approach and it maybe a little redundant one but you may experiment. You may use it without an inheritance.
Also you may experiment with custom Builders for your widget that will work like ListView.builder(), LayoutBuilder() or FutureBuilder(). I would recommend you to investigate how do they work.
Also you can create a custom constructor for your child widget that receives BoxConstraints as a parameter and stores it is the widget to be user either in a State or StatelessWidget builders.
There are many more ways to do it most of which will be a different implementations of simple composition so yeah... experiment))
Hope it helps.

Controll widget with flutter bloc

Project
Hi,
I recently discovered flutter bloc and now I'm trying to understand how exactly this works. My goal is to separate logic from widget classes in order to easily manage my projects.
Problem
I'm stuck with something that is very simple using classic setState, but I was trying to achieve this with bloc.
Here is an old widget of mine
Widget build(BuildContext context) {
return AnimatedOpacity(
duration: Duration(milliseconds: 200),
opacity: _opacity,
curve: Curves.easeInOut,
child: Text(
_currentTitle,
style: TitleTextStyle,
),
);
}
Is it possible to control _opacity and _currentTitle from a bloc? Something like this:
List<String> titles = ['title1', 'title2', ....];
int myIndex;
#override
Stream<SomeBlocState> mapEventToState(SomeEvent event,)
async* {
....
if (event is SomeSpecificEvent)
setWidgetTitle(titles[myIndex]);
....
}
I am trying to avoid buiding different state for each possible title, that would be a mess
Thanks
You can try and make the title widget build in a StreamBuilder instead. That will give you a clean separation of responsibilities. So that the BLoC doesn't know of any state changes or widget specific things.
Widget build(BuildContext context) {
return AnimatedOpacity(
duration: Duration(milliseconds: 200),
opacity: _opacity,
curve: Curves.easeInOut,
child: StreamBuilder<String>(
stream: myBloc.title,
builder: (_, snap) {
return Text(
snap?.data ?? 'Some default',
style: TitleTextStyle,
);
}
),
);
}
And you create some logic in your BLoC that emits the title as needed
List<String> titles = ['title1', 'title2', ....];
int myIndex;
final _myObservableTitles = PublishSubject();
Stream<String> get title => _myObservableTitles.stream;
....
if (event is SomeSpecificEvent)
_myObservableTitles.add(titles[myIndex]);
....

if condition to run Widget at Container flutter

if condition when post.text == 1
i setup Widget named exteranews
Widget exteranews(BuildContext context) {
double siz11 = 15.0 * MediaQuery.of(context).size.width / 414.0;
}
how to use if with that Container
Container(
if (checkothers) child: exteranews(context),
),
i set String checkothers = post.text;
i read i have to setup Widget to use run exteranews() but how to use if condition with it
You may use ? syntax
please make sure to put Container() so it will display nothing
its easier to image Container() equals to <div></div> in html
Widget extraNews(BuildContext context) {
double siz11 = 15.0 * MediaQuery.of(context).size.width / 414.0;
return Container();
}
class BaseScreen extends StatelessWidget {
///
#override
Widget build(BuildContext context) {
return Container(
child: checkothers == true ? extraNews(context) : Container(),
);
}
}