How to use SizeChangedLayoutNotifier? - flutter

I got a Wrap inside my flexibleSpace in my SliverAppBar. Now when the size of my Wrap changes (dynamically) I need to get a notification, so I can change the height of my flexibleSpace in my SliverAppBar accordingly.
I read the docs to the SizeChangedLayoutNotifier but I don't really understand how to use it. I wrapped my Wrap as a child in a SizeChangedLayoutNotifier but for me it is very unclear how to catch the notification with the NotificationListener<SizeChangedLayoutNotification>. I couldn't find any code example.
I would really appreciate your help :) Thanks!

I finally figured it out and will post the solution here for others, having the same problem.
I put my Wrap inside like this:
new NotificationListener<SizeChangedLayoutNotification>(
onNotification: gotNotification,
child: SizeChangedLayoutNotifier(
key: _filterBarChangeKey,
child: Wrap( // my WRAP stuff)
)
);
and having my callback like this:
bool gotNotification(SizeChangedLayoutNotification notification) {
// change height here
_filterBarChangeKey = GlobalKey();
}
I also found another solution from here not using SizeChangedLayoutNotification at all to solve my problem. For me this was even better. I just wrapped my Widget inside an MeaserSize widget which provides an onSizeChanged callback.
typedef void OnWidgetSizeChange(Size size);
class MeasureSize extends StatefulWidget {
final Widget child;
final OnWidgetSizeChange onChange;
const MeasureSize({
Key key,
#required this.onChange,
#required this.child,
}) : super(key: key);
#override
_MeasureSizeState createState() => _MeasureSizeState();
}
class _MeasureSizeState extends State<MeasureSize> {
var widgetKey = GlobalKey();
#override
Widget build(BuildContext context) {
WidgetsBinding.instance
.addPostFrameCallback((_) => widget.onChange(widgetKey.currentContext.size));
return Container(
key: widgetKey,
child: widget.child,
);
}
}

Related

Flutter: Is it possible to know if you're currently off stage?

I have a number of pages in my app wrapped in Offstage widgets. Each page makes use of the provider package to render based on state updates (e.g. the user does something, we make a network call and display the result).
As the pages are wrapped in Offstage widgets, the build() methods (and subsequent network calls) are called even if it's not the current page.
Is there a way inside the build() method to know if the widget is currently off stage (and if so, skip any expensive logic)?
I'm assuming I can work something with global state etc, but I was wondering if there was anything built-in in relation to the Offstage widget itself, similar to mounted
You can try finding the parent OffStage widget and see if the offstage property is true or false
#override
Widget build(BuildContext context) {
final offstageParent = context.findAncestorWidgetOfExactType<Offstage>();
if (offstageParent != null && offstageParent.offstage == false) {
// widget is currently offstage.
print('offstaged child');
} else {
// widget is not offstage
print('non-offstaged child');
}
return const Text('Example Widget');
}
I made a custom-made mechanism for the goal you wanna achieve:
First, I am declaring a new Map<String, bool> in a separate file alone that will hold the offStage bool value with the key of each class widget.
Map<String, bool> offStageMap = {};
then in the implementation of the StatefulWidget where the offstage widget is in:
class ExampleWidget extends StatefulWidget {
ExampleWidget({super.key}) {
widgetMapKey = runtimeType.toString();
}
late final String widgetMapKey;
#override
State<ExampleWidget> createState() => _ExampleWidgetState();
}
class _ExampleWidgetState extends State<ExampleWidget> {
final bool defaultIsOffStaged = false;
bool? localStateIsOffStages;
#override
void initState() {
offStageMap[widget.widgetMapKey] ??= defaultIsOffStaged;
super.initState();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
bool previousIsOffStaged = offStageMap[widget.widgetMapKey]!;
setState(() {
localStateIsOffStages =
offStageMap[widget.widgetMapKey] = !previousIsOffStaged;
});
},
child: Offstage(
offstage: localStateIsOffStages ?? offStageMap[widget.widgetMapKey]!,
child: Container(),
),
);
}
} },
child: Offstage(
offstage: localStateIsOffStages ?? offStageMap[widget.widgetMapKey]!,
child: Container(),
),
);
}
}
let me explain what this is about.
first I declared a defaultIsOffStaged where it should be the initial offStage value when nothing is saved in that map.
when that widget is inserted in the widget tree (initState() called), the widget.widgetMapKey of the ExampleWidget widget will be saved in that map with the value of the default one which is defaultIsOffStaged.
offStageMap[widget.widgetMapKey] ??= defaultIsOffStaged;
in the offstage property o the OffStage widget, in this line:
offstage: localStateIsOffStages ?? offStageMap[widget.widgetMapKey]!,
the nullable localStateIsOffStages will be null for the first time since it has no value yet, so offStageMap[widget.widgetMapKey]! which equals to defaultIsOffStaged will be the bool value of offstage.
until now what we have, is a map containing the key that belongs only to the ExampleWidget which is its widget.widgetMapKey with its offStage value, right?
now from all places in your app, you can get the offStage value of that widget with its widgetMapKey like this:
print(offStageMap[ExampleWidget().widgetMapKey]); // true
now let's say you want to change the offstage property of that widget, in my code I used a simple example of GestureDetector, so when we tap in the Text("toggle offstage") area, it toggles offStage, here is what happens:
we got the existing value in the map:
bool previousIsOffStaged = offStageMap[widget.widgetMapKey]!;
then assign the opposite of it, to that widget key in the map, and the localStateIsOffStages bool variable which was nullable, now it has a value.
and as normal so the state updates I wrapped it in a SetState(() {})
now the widget's offstage will be toggled, and every time the widget key in the map will be updated with that new value.
the localStateIsOffStages I declared just to hold the local state when this is happening while the StatefulWidget state updates.
after the StatefulWidget is disposed of (when you pop the route as an example) and open that route again, the initState() will execute but since we have now an entry in the map, it's not null so nothing will happen inside initState().
the localStateIsOffStages will be null, so the offStage property of the Offstage widget will be the value from the map, which is the previous value before the widget is disposed.
that's it, from other places you can check for the offstage value of that specific widget like this:
print(offStageMap[ExampleWidget().widgetMapKey])
you can do it for all your widget pages, so you will have a map containing the offStage values of them all.
I take it one step up, and made those methods that I guess they will help:
this will return a List with the pages where the value is true.
List<String> offstagedPages() {
List<String> isOffStagedPages = [];
offStageMap.forEach((runtimeType, isOffStaged) {
if (isOffStaged) {
isOffStagedPages.add(runtimeType);
}
});
return isOffStagedPages;
}
this will return a true if a page is off staged and false if not:
bool isPageWidgetOffStaged(String runtimeType) {
if (offStageMap.containsKey(runtimeType)) {
return offStageMap[runtimeType]!;
}
return false;
}
Hope this helps a little.
Maybe it's not applicable to you, but you might be able to solve it by simply not using Offstage. Consider this app:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({super.key});
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool showFirst = true;
void switchPage() {
setState(() {
showFirst = !showFirst;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Stack(children: [
Offstage(offstage: !showFirst,child: A("first", switchPage)),
Offstage(offstage: showFirst,child: A("second", switchPage)),
]))));
}
}
class A extends StatelessWidget {
final String t;
final Function onTap;
const A(this.t, this.onTap, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('$t is building');
return TextButton(onPressed: ()=> onTap(), child: Text(t));
}
}
You will notice by the prints that both pages are build. But if you rewrite it like this without Offstage, only the visible one is build:
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Stack(children: [
if (showFirst) A("first", switchPage),
if (!showFirst) A("second", switchPage),
]))));
}
If you want to just keep state alive your pages , you can use https://api.flutter.dev/flutter/widgets/AutomaticKeepAliveClientMixin-mixin.html , you may check this blog for example usage, https://medium.com/manabie/flutter-simple-cheatsheet-4370a68f98b3
If you are using Navigator, you can just extends NavigatorObserver. Then you will get didpush and didpop, use state to manage elementlifecycle, you will get page onPause and onResume fun.

Is there a way to specify function parameters and return value as const in dart?

I wrote an extension function to add SizedBox between every child in Column or Row to add space between child, instead of putting SizedBox between every child, for which I didn't find any other way around.
Column(
children: [
// widgets
].setSpace(height: 10),
)
Row(
children: [
// widgets
].setSpace(width: 10),
)
So here List<Widget> setSpace({double? height, double? width}) takes height or width for the SizedBox used between child. But since height and width are not const I cannot use const SizedBox. So is there any way in dart to say that both the parameters and the return type will ALWAYS be cosnt? like const List<Widget> setSpace({const double? height, const double? width}) like C/C++?
I don't think that's possible, mostly because const can be applied only on constructors and fields, not on generic functions.
Maybe you can achieve that by creating your own widget that adds the SizedBox in its build method, and create a const constructor.
EDIT: here's a piece of code of mine of a custom widget with a const constructor.
class UnlockPage extends StatefulWidget {
final String pcData;
const UnlockPage({Key? key, required this.pcData}) : super(key: key);
#override
Widget build(BuildContext context) {
[...]
}
}
EDIT 2: here's a piece of code tested in DartPad. I don't think it can get better than this.
class SpacedColumn extends StatelessWidget {
final double height;
final List<Widget> children;
const SpacedColumn({required this.height, required this.children});
#override
Widget build(BuildContext context) {
var actualChildren = <Widget>[];
for (var child in children) {
actualChildren.add(child);
actualChildren.add(SizedBox(height: height));
}
return Column(
children: actualChildren,
);
}
}
You can't. As you pass a value this one can be different from one call to others.
Notice that const as not the same signification on Flutter than on other languages.
With Flutter it indicates to the rendering engine that the widget or the method is always the same and that the rendering engine is not obliged to rebuild this Widget when rebuilding the screen.
The keyword that act as const in other languages is final
In Dart language const doesn't mean the same as in other languages. You should use final if you don't want to change the values later.

Does keyword late work as I expect when constructing Widgets

I have a convenience StatelessWidget that returns the appropriate widget for one of three display size breakpoints:
/// Return the most appropriate widget for the current display size.
///
/// If a widget for current display size is not available, chose the closest smaller variant.
/// A [mobile] size widget is required.
class SizeAppropriate extends StatelessWidget {
// ignore: prefer_const_constructors_in_immutables
SizeAppropriate(
this.context,
{
required this.mobile,
this.tablet,
this.desktop,
Key? key
}
) : super(key: key);
final BuildContext context;
late final Widget mobile;
late final Widget? tablet;
late final Widget? desktop;
#override
Widget build(BuildContext context) {
switch (getDisplaySize(context)) {
case DisplaySize.mobile:
return mobile;
case DisplaySize.tablet:
return tablet ?? mobile;
case DisplaySize.desktop:
return desktop ?? tablet ?? mobile;
}
}
}
I then use it like this:
SizeAppropriate(
context,
mobile: const Text('mobile'),
desktop: const Text('desktop'),
)
Is the keyword late working here as intended, building only the correct variant, or am I hogging the performance, because all variants are constructed (or am I even creating an anti-pattern)?
Should I use builder functions instead?
Edit:
This answer makes me think I'm correct. Although the last two sentences make me think I'm not correct.
When you do this, the initializer becomes lazy. Instead of running it as soon as the instance is constructed, it is deferred and run lazily the first time the field is accessed. In other words, it works exactly like an initializer on a top-level variable or static field. This can be handy when the initialization expression is costly and may not be needed.
When I do log('mobile') and log('desktop') in the respective widgets being constructed, I only get one type of message in the console.
Edit 2 – minimum working example:
This is my main.dart in a newly generated project (VS Code command >Flutter: New Project – Application). It might be worth noting, that I am testing this on Linux.
import 'package:flutter/material.dart';
import 'dart:developer';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
body: SizeAppropriate(
context,
mobile: const Mobile(),
tablet: const Tablet(),
desktop: const Desktop(),
),
),
);
}
}
class Mobile extends StatelessWidget {
const Mobile({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
log('mobile');
return const Text('mobile');
}
}
class Tablet extends StatelessWidget {
const Tablet({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
log('tablet');
return const Text('tablet');
}
}
class Desktop extends StatelessWidget {
const Desktop({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
log('desktop');
return const Text('desktop');
}
}
enum DisplaySize {
desktop,
tablet,
mobile,
}
DisplaySize getDisplaySize(BuildContext context) {
Size size = MediaQuery.of(context).size;
if (size.width < 768) {
return DisplaySize.mobile;
}
else if (size.width < 1200) {
return DisplaySize.tablet;
}
else {
return DisplaySize.desktop;
}
}
class SizeAppropriate extends StatelessWidget {
// ignore: prefer_const_constructors_in_immutables
SizeAppropriate(
this.context,
{
required this.mobile,
this.tablet,
this.desktop,
Key? key
}
) : super(key: key);
final BuildContext context;
late final Widget mobile;
late final Widget? tablet;
late final Widget? desktop;
#override
Widget build(BuildContext context) {
switch (getDisplaySize(context)) {
case DisplaySize.mobile:
return mobile;
case DisplaySize.tablet:
return tablet ?? mobile;
case DisplaySize.desktop:
return desktop ?? tablet ?? mobile;
}
}
}
late does not do what you want. It's only for the null-safety feature and when you do or don't get warnings about it. Those two texts get built every time regardless of environment, because they need to be there when they are passed to your widget.
If you to only build the appropriate widgets for each size when the size is known, you indeed need indeed pass two builders, one of which you call if appropriate.
For two constant Text widgets, that would be too much overhead, but I am assuming you want "heavier" widget trees for both options in the end.

How to change values within a stateful widget class from a different class?

I have a stateful widget LetterButton()
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text(widget.caption),
onPressed: onChanged,
color: colors[currentIndex],
padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
);
}
In my main.dart file I declare an array of LetterButtons
List<LetterButton> buttonArray;
which I initialize and fill during initState() via the method
void makeButtons() {
for (var letter in alphabet) {
buttonArray.add(
LetterButton(letter),
);
}
}
The buttons in the list are then displayed in the UI
Wrap(
children: buttonArray,
)
How can I change the value of currentIndex (an int in
class LetterButtonState extends State<LetterButton>) or otherwise change all the buttons to the same color from main.dart?
NOTE: I asked a similar question a few days ago, but the answer was a little above my current knowledge, as are responses I've seen to similar Q's here on SO. I have a little understanding of callbacks, and experimented a little with the provider package, but there's such a variety of answers and info available online that it's hard for me to even know what I don't know to be able to answer my question :-)
Create stateful widget with state as public access, so that you can access outside of the package and provide key to constructor. So that you can refer key and get can get state to change value. See the following example
class LetterButton extends StatefulWidget {
LetterButton({GlobalKey key}) : super(key: key);
#override
LetterButtonState createState() => LetterButtonState();
}
class LetterButtonState extends State<LetterButton> {
int value = 0;
//this public method is to update int value
setValue(int value) {
setState(() {
this.value = value;
});
}
#override
Widget build(BuildContext context) {
return Center(
child: Text(value.toString()),
);
}
}
//In Main.dart
GlobalKey<LetterButtonState> _buttonStateKey = GlobalKey();
//while creating widget
LetterButton(key:_buttonStateKey)
//in onTapCallback you can call to update value
_buttonStateKey.currentState?.setValue(10);
Just send your created function to new class by parameter and the new class should be Constarcter with Function lThen you can call the function from a new class.

Flutter Keyboard listen on hide and show

How can I listen whether the keyboard is showing up or hiding?
I tried this example
How to listen to keyboard on screen Flutter?
void _listener(){
if(_myNode.hasFocus){
// keyboard appeared
}else{
// keyboard dismissed
}
}
FocusNode _myNode = new FocusNode()..addListener(_listner);
TextField _myTextField = new TextField(
focusNode: _mynNode,
...
...
);
But unfortunately it doesn't work. Any ideas how it could be possible to listen to the keyboard change?
It seems like it works when I press "Done" on the keyboard. But if i press back on my phone it won't go to "keyboard dismissed" because the focus still exists.. Any help?
KeyboardVisibilityBuilder
Listening for keyboard show/hide events can be achieved with WidgetsBindingObserver mixin. I prepared KeyboardVisibilityBuilder widget that handles the behavior for you. The usage is quite similar to AnimatedBuilder:
return KeyboardVisibilityBuilder(
builder: (context, child, isKeyboardVisible) {
if (isKeyboardVisible) {
// build layout for visible keyboard
} else {
// build layout for invisible keyboard
}
},
child: child, // this widget goes to the builder's child property. Made for better performance.
);
KeyboardVisibilityBuilder implementation:
/// Calls `builder` on keyboard close/open.
/// https://stackoverflow.com/a/63241409/1321917
class KeyboardVisibilityBuilder extends StatefulWidget {
final Widget child;
final Widget Function(
BuildContext context,
Widget child,
bool isKeyboardVisible,
) builder;
const KeyboardVisibilityBuilder({
Key key,
this.child,
#required this.builder,
}) : super(key: key);
#override
_KeyboardVisibilityBuilderState createState() => _KeyboardVisibilityBuilderState();
}
class _KeyboardVisibilityBuilderState extends State<KeyboardVisibilityBuilder>
with WidgetsBindingObserver {
var _isKeyboardVisible = false;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeMetrics() {
final bottomInset = WidgetsBinding.instance.window.viewInsets.bottom;
final newValue = bottomInset > 0.0;
if (newValue != _isKeyboardVisible) {
setState(() {
_isKeyboardVisible = newValue;
});
}
}
#override
Widget build(BuildContext context) => widget.builder(
context,
widget.child,
_isKeyboardVisible,
);
}
Not sure how reliable this is, but there's this property on MediaQueryData:
/// The number of physical pixels on each side of the display rectangle into
/// which the application can render, but over which the operating system
/// will likely place system UI, such as the keyboard, that fully obscures
/// any content.
final EdgeInsets viewInsets;
Checking if viewInsets.vertical is greater than zero in the build() method gave me correct results:
#override
Widget build(BuildContext context) {
bool isKeyboardShowing = MediaQuery.of(context).viewInsets.vertical > 0;
return SafeArea(
child: Scaffold(
body: Column(
children: <Widget>[
Text(isKeyboardShowing ? 'YES!' : 'NO!'),
TextField(),
],
),
),
);
}
It's probably a good idea to combine this with other checks (e.g. input focus), to avoid false positives.
There is a package which mets your requirement completely. Please check that: flutter_keyboard_visibility
In there, by using StreamSubscription, you can listen whether the keyboard becomes goes up/down and react based on that.
what I need is a listener so I convert Andrey Gordeev code to
import 'package:flutter/cupertino.dart';
class KeyboardVisibilityListener extends StatefulWidget {
final Widget child;
final void Function(
bool isKeyboardVisible,
) listener;
const KeyboardVisibilityListener({
Key? key,
required this.child,
required this.listener,
}) : super(key: key);
#override
_KeyboardVisibilityListenerState createState() =>
_KeyboardVisibilityListenerState();
}
class _KeyboardVisibilityListenerState extends State<KeyboardVisibilityListener>
with WidgetsBindingObserver {
var _isKeyboardVisible = false;
#override
void initState() {
super.initState();
WidgetsBinding.instance?.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance?.removeObserver(this);
super.dispose();
}
#override
void didChangeMetrics() {
final bottomInset = WidgetsBinding.instance!.window.viewInsets.bottom;
final newValue = bottomInset > 0.0;
if (newValue != _isKeyboardVisible) {
_isKeyboardVisible = newValue;
widget.listener(_isKeyboardVisible);
}
}
#override
Widget build(BuildContext context) => widget.child;
}
and
I used it to unfocus the input field when hiding the keyboard
home: KeyboardVisibilityListener(
listener: (isKeyboardVisible) {
if (!isKeyboardVisible) {
FocusManager.instance.primaryFocus?.unfocus();
}
},
child: Scaffold(
key: scaffoldKey,
body: layoutp.currentItem.page,
),
),
if (MediaQuery.of(context).viewInsets.bottom > 0.0) {
// keyboard on the screen
}
Simple explanation: MediaQuery to learn the size of the current media. This class use as MediaQueryData media = MediaQuery.of(context);. If any view appears on the screen MediaQuery.of(context).viewInsetsgive some value of the height of that view. As keyboard appears from the bottom on the screen so I use MediaQuery.of(context).viewInsets.bottom and this gives me the height of the keyboard taken on my screen. When the keyboard doesn't appear this value is 0.And this solution definitely works.
Did you pick up the spelling mistake?
FocusNode _myNode = new FocusNode()..addListener(_listner);
Should be:
FocusNode _myNode = new FocusNode()..addListener(_listener);
A widget that calls a callback whenever the user presses or releases a key on a keyboard.
A RawKeyboardListener is useful for listening to raw key events and hardware buttons that are represented as keys. Typically used by games and other apps that use keyboards for purposes other than text entry.
For text entry, consider using a EditableText, which integrates with on-screen keyboards and input method editors (IMEs).
const RawKeyboardListener({
Key key,
#required FocusNode focusNode,
#required ValueChanged<RawKeyEvent> onKey,
#required Widget child
})
Creates a widget that receives raw keyboard events.
For text entry, consider using a EditableText, which integrates with on-screen keyboards and input method editors (IMEs).
Implementation
const RawKeyboardListener({
Key key,
#required this.focusNode,
#required this.onKey,
#required this.child,
}) : assert(focusNode != null),
assert(child != null),
super(key: key);
You can use this library
keyboard_visibility_pro
KeyboardVisibility(
// it will notify
onChanged: (bool visible) {
print(visible);
},
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[TextField()],
),
),
),