flutter, How to judge whether another widget is in the tree - flutter

Actually, I have a parent widget, and It has some of the child widgets in its Column.
like this
Container(
width: double.infinity,
color: Colors.white,
padding: EdgeInsets.fromLTRB(20.0, 50.0, 20.0, 0.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Title(),
SizedBox(height: 80.0,),
confirmLoginType(),
SizedBox(height: 10.0),
LoginButton(),
PolicyTips(
key: IndexGlobalKey.policyTipsKey,
updateState: receiveMessageUpdateState
),
Bottom()
],
),
),
in the PolicyTips, I pass a key to it, and I want to get the key in the LoginButton , but It has always been null when I get currentState.
The code is below:LoginButton
class LoginButton extends StatefulWidget {
LoginButton({Key key}) : super(key: key);
#override
_LoginButtonState createState() => _LoginButtonState();
}
class _LoginButtonState extends State<LoginButton> {
#override
Widget build(BuildContext context) {
policyTipsKey = IndexGlobalKey.policyTipsKey.currentState;
return Container(
child: Text()
)
}
}
what can I do? help me please, thanks.
This is IndexGlobalKey code.
class IndexGlobalKey {
static final GlobalKey<_PolicyTipsState> policyTipsKey = GlobalKey<_PolicyTipsState>();
static GlobalKey<_FormState> phoneLoginKey = GlobalKey<_FormState>();
static GlobalKey<_FormForIdCardLoginState> idCardLoginKey = GlobalKey<_FormForIdCardLoginState>();
}

Build method of _LoginButtonState runs before PolicyTips renders and before IndexGlobalKey.policyTipsKey is actually set. The reason is LoginButton goes before PolicyTips in column. Thats why you get null when you call IndexGlobalKey.policyTipsKey.currentState from build of _LoginButtonState.
To solve this you need to call IndexGlobalKey.policyTipsKey.state right where you use it. For example, when you need to get policy tips state on button tap just use it inside onPressed callback:
class _LoginButtonState extends State<LoginButton> {
#override
Widget build(BuildContext context) {
// An example of your button
return TextButton(
onPressed: () {
final policyTipsState = IndexGlobalKey.policyTipsKey.currentState;
// Here you can use policyTipsState
},
child: Text('button'),
);
}
}

Related

How to add a container to a column on tap in Flutter

I have a situation where I have a listview of containers, and I would like when I double tap each container another container pops up below with information. Currently what I am trying to do is wrap each container within a column and do something like:
onDoubleTap() {showBox = true}, and in the column have code:
children: [post(), showbox == true? infobox() : container()] but I am not sure of the correct implementation. Any help would be great!
you should maintain a list of containers:
class ContainerAdder extends StatefulWidget {
const ContainerAdder({Key? key}) : super(key: key);
#override
_ContainerAdderState createState() => _ContainerAdderState();
}
class _ContainerAdderState extends State<ContainerAdder> {
List<Widget> containers = <Widget>[];
Random random = Random();
List<Color> colors = [
Colors.blue,
Colors.green,
Colors.red,
Colors.orange,
Colors.purple,
Colors.pink,
Colors.teal,
Colors.yellow,
];
addContainer() {
setState(() {
int r = random.nextInt(colors.length);
containers.add(
InkWell(
onDoubleTap: () => addContainer(),
child: Container(
margin: const EdgeInsets.only(bottom: 1.0),
height: 50.0,
color: colors[r],
),
),
);
});
}
#override
void initState() {
super.initState();
addContainer();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: [...containers],
),
);
}
}
As you can notice, the method addContainer() adds a container which is wrapped in an InkWell to have the tap listener. The doubleTap calls the method addContainer().
I simply spread the containers list inside ListView widget.
In the addContainer() method, I wrap the codes inside setState() so as to refresh the tree. You can use any other state management architecture if you so wish.
For the first time, I call addContainer() inside initState(), in order to populate the list with the first element.

Flutter riverpod Change notifier not updating value

I'm using riverpod to manage states of some variables in my app like opacity, stroke width and color for my coloring app.
Here's my opacity class inside notifier.dart:
class OpacityChangeNotifier extends ChangeNotifier {
OpacityChangeNotifier([this.opacity = 1.0]);
double opacity;
void changeOpacity(double providedOpacity) {
opacity = providedOpacity;
notifyListeners();
}
void printOpacity() {
print(opacity);
}
}
This is from my OpacityPicker.dart:
final _opacityProvider = ChangeNotifierProvider<OpacityChangeNotifier>((ref) {
return OpacityChangeNotifier();
});
class OpacityPicker extends ConsumerWidget {
const OpacityPicker({Key key}) : super(key: key);
#override
Widget build(BuildContext context, ScopedReader watch) {
return Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
IconButton(
onPressed: () {
context.read(_opacityProvider).changeOpacity(0.1);
context.read(_opacityProvider).printOpacity();
},
icon: Icon(Icons.opacity, size: 20),
),
IconButton(
onPressed: () {
context.read(_opacityProvider).changeOpacity(0.5);
context.read(_opacityProvider).printOpacity();
},
icon: Icon(Icons.opacity, size: 30),
),
IconButton(
onPressed: () {
context.read(_opacityProvider).changeOpacity(1.0);
context.read(_opacityProvider).printOpacity();
},
icon: Icon(Icons.opacity, size: 40),
),
],
),
);
}
}
finally this is my menu_items.dart:
final _opacityChangeProvider =
ChangeNotifierProvider<OpacityChangeNotifier>((ref) {
return OpacityChangeNotifier();
});
class UtilityItems extends ConsumerWidget {
const UtilityItems({Key key}) : super(key: key);
#override
Widget build(BuildContext context, ScopedReader watch) {
final opacityNotifier = watch(_opacityChangeProvider);
return SingleChildScrollView(
child: Container(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
IconButton(
onPressed: () {
print(opacityNotifier.opacity);
},
icon: Icon(Icons.dock_rounded),
)
],
),
OpacityPicker(),
],
),
),
);
}
}
Everything is working fine inside OpacityPicker.dart. When I'm pressing the opacity button the selected opacity is getting printed. But when I'm pressing the Icons.dock_rounded in menu_items.dart shouldn't I get the updated value?
It's showing the default value for opacity which is 1.0
I've looked it up and all I got was some issues related to changeNotifier not working back in September 2020.
What am I missing here?
N.B: I've imported all the files correctly. And I want the value of opacity to change to the user selected one on pressed. So I need my menu_item.dart widget to know that.
Building off #puelo comment, you shouldn't be redefining your ChangeNotifierProvider. The way you have it is two isolated providers with two separate ChangeNotifiers that have no knowledge of eachother.
I would recommend making provider a static member of your ChangeNotifier like so:
class OpacityChangeNotifier extends ChangeNotifier {
OpacityChangeNotifier([this.opacity = 1.0]);
static final provider = ChangeNotifierProvider<OpacityChangeNotifier>((ref) {
return OpacityChangeNotifier();
});
double opacity;
void changeOpacity(double providedOpacity) {
opacity = providedOpacity;
notifyListeners();
}
void printOpacity() {
print(opacity);
}
}
To access:
context.read(OpacityChangeNotifier.provider);
This helps to keep your imports clean as well as avoiding redundant naming and reduces the likelihood someone else working on the project would end up creating another provider for that notifier.
Use that provider instead of defining it twice and that should solve your primary issue.
You should also always use context.read inside function handlers like onPressed. In your menu_items.dart you should refactor as follows:
class UtilityItems extends ConsumerWidget {
const UtilityItems({Key key}) : super(key: key);
#override
Widget build(BuildContext context, ScopedReader watch) {
return SingleChildScrollView(
child: Container(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
IconButton(
onPressed: () {
final opacity = context.read(OpacityChangeNotifier.provider).opacity;
print(opacity);
},
icon: Icon(Icons.dock_rounded),
)
],
),
OpacityPicker(),
],
),
),
);
}
}

setState() doesn't update constructor values in TabBarView tabs

I have a TabBarView with two tabs in main widget. First tab includes gridview with cards. Cards use parent widget (MyHomePage) as listener to listen in-card button clicks.
When i click on button in some card, listener impl. must open second Tab and pass selected Excursion to it. But when I do it, at first iteration, ExcursionEditor(currentExcursion) says, that argument is null, but parent build says, that it is not. If I resize my browser, it calls global rebuild and currentExcursion reach last build value.
So, i cant understand, why MyHomePage build doesn't affect on TabBarView content with arguments passed by constructor
class MyHomePage
import 'package:flutter/material.dart';
import 'package:questbuilder/api/content_manager.dart';
import 'package:questbuilder/model/excursion.dart';
import 'package:questbuilder/pages/tab_editor.dart';
import 'package:questbuilder/pages/tab_my_excursions.dart';
import 'package:questbuilder/widgets/excursion_preview_card.dart';
import 'package:logger/logger.dart';
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with TickerProviderStateMixin
implements ExcursionCardInteractionListener {
Logger logger = Logger();
Excursion currentExcursion;
TabController tabController;
#override
void initState() {
super.initState();
print("INIT STATE FOR HOME PAGE");
tabController = TabController(vsync: this, length: 2);
}
#override
Widget build(BuildContext context) {
var screenSize = MediaQuery.of(context).size;
print("HOME PAGE BUILD currentExcursion = ${currentExcursion?.toJson()}");
return Scaffold(
extendBodyBehindAppBar: true,
appBar: PreferredSize(
preferredSize: Size(screenSize.width, 1000),
child: Container(
color: Colors.black,
child: Padding(
padding: EdgeInsets.fromLTRB(10, 10, 30, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(children: [
Padding(
padding: EdgeInsets.fromLTRB(10, 0, 10, 10),
child: Text('QUESTBUILDER',
style: TextStyle(color: Colors.white))),
SizedBox(width: screenSize.width / 20),
Container(
width: screenSize.width / 6,
child: TabBar(
labelPadding: EdgeInsets.fromLTRB(10, 0, 10, 10),
indicatorColor: Colors.white,
controller: tabController,
tabs: [
Tab(text: "Мои экскурсии"),
Tab(text: "Редактор"),
]))
]),
Padding(
padding: EdgeInsets.fromLTRB(0, 0, 0, 10),
child: Row(
children: [
FlatButton.icon(
label: Text("Создать экскурсию"),
icon: Icon(Icons.add),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(40.0)),
textColor: Colors.white,
color: Colors.green,
onPressed: () {
createExcursion();
}),
SizedBox(
width: 40,
),
InkWell(
onTap: () {},
child: Text(
'Вход',
style: TextStyle(color: Colors.white),
),
)
],
)),
],
),
),
),
),
body: Padding(
padding: EdgeInsets.all(15),
child: TabBarView(
controller: tabController,
children: [
// Set listener to cards in this widget to prerform 'edit' clicks
MyExcursionsTab(this),
ExcursionEditor(currentExcursion)
],
)));
}
// Here i call setState from cards
#override
void editExcursion(Excursion excursion) {
setState(() {
currentExcursion = excursion;
});
tabController.animateTo(1);
}
#override
void dispose() {
tabController.dispose();
super.dispose();
}
void createExcursion() {
ContentManager.client.createExcursion(0).then((value) {
currentExcursion = value;
editExcursion(currentExcursion);
});
}
}
class ExcursionEditor
import 'dart:typed_data';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:questbuilder/api/content_manager.dart';
import 'package:questbuilder/model/excursion.dart';
import 'package:questbuilder/model/excursion_content.dart';
import 'package:questbuilder/model/excursion_data.dart';
import 'package:questbuilder/model/picture.dart';
class ExcursionEditor extends StatefulWidget {
Excursion excursion;
ExcursionEditor(this.excursion);
#override
State<StatefulWidget> createState() => ExcursionEditorState();
}
class ExcursionEditorState extends State<ExcursionEditor> {
ExcursionData currentData;
ExcursionContent currentContent;
Excursion excursion;
List<Picture> pictures = [];
#override
void initState() {
super.initState();
print("INIT EDITOR widget.excrusion = ${widget.excursion?.toJson()}");
// At this point, after call setState() in HomePage widget.excrusion is always null
// until I resize browser, thereby calling global state reset
//
if (widget.excursion != null)
ContentManager.client.getPictureList(widget.excursion.id).then((value) {
pictures.addAll(value);
print(pictures);
});
}
#override
Widget build(BuildContext context) {
excursion = widget.excursion;
print("BUILD EDITOR excursion = ${widget.excursion?.toJson()}");
return excursion != null
? Container()
: Container(
child: Align(
alignment: Alignment.center,
child: Text("Выберите экскурсию для редактирования")));
}
}
Log of first launch and card click build sequence:
HOME PAGE BUILD currentExcursion = null
HOME PAGE BUILD currentExcursion = {id: 1}
INIT EDITOR widget.excrusion = null
BUILD EDITOR excursion = null
After browser window resize
HOME PAGE BUILD currentExcursion = {id: 1}
BUILD EDITOR excursion = {id: 1}
BUILD EDITOR excursion = {id: 1}
HOME PAGE BUILD currentExcursion = {id: 1}
BUILD EDITOR excursion = {id: 1}
After screen resize problem still appear, just replacing null value in editor with old Excursion. New clicks on cards doesn't have effect, setState in callback still not update.
I've tried to bind it on static stream listeners, on TabController listener - it just look like TabBarView late for 1 build cycle of arguments update. Maybe there are some similar questions, but i've done all from thouse answers and got nothing
I am not really sure, but it seems like race condition between setState and _tabController.animateTo(1); because they both try to rebuild the child ExcursionEditor(currentExcursion)
If you print the excursion in ExcursionEditor constructor, you will see the updated value. But at the end the value not reach the build function.
The simple workaround is changing editExcursion to the async function and add a small delay between this 2 actions. Otherwise you can try to use other way to pass data between widgets (like provider)
#override
Future editExcursion(Excursion excursion) async {
setState(() {
currentExcursion = excursion;
});
await Future.delayed(Duration(milliseconds:50));
tabController.animateTo(1);
}

The render frames goes crazy with multiple image in scroll

Hello I was trying to do something like the Instagram Stories, but when I use more than 6 pictures the Flutter Performance of Android Studio goes crazy, also my Pc. And I was wondering if there is something to prevent it.
Main Widget
class Story extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: <Widget>[
AddStory(),
PeopleStories(),
],
),
);
}
}
Second Widget
class PeopleStories extends StatelessWidget {
final List<String> storiesPic = [
'https://images.unsplash.com/photo-1536896407451-6e3dd976edd1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1234&q=80',
'https://images.unsplash.com/photo-1553457055-88e354f1257c?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1234&q=80',
'https://images.unsplash.com/photo-1545064189-2d901f702a28?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1234&q=80',
'https://images.unsplash.com/photo-1501943416256-08140ba03763?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1436&q=80',
'https://images.unsplash.com/photo-1572151510493-38eb612d23be?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1268&q=80',
'https://images.unsplash.com/photo-1548536154-b47a70d27d8e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1234&q=80',
// 'https://images.unsplash.com/photo-1582129617595-c657a072c9b9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=3662&q=80',
// 'https://images.unsplash.com/photo-1559870904-406aeea0a89c?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=934&q=80'
];
#override
Widget build(BuildContext context) {
return Row(
children: _buildStoriesList(),
);
}
List<StoryDesign> _buildStoriesList() {
List<StoryDesign> widgets = [];
int i = 0;
storiesPic.forEach((pic) {
++i;
widgets.add(StoryDesign(pic));
});
return widgets;
}
}
Design of the widget, if there is something it could do the multiple rendering.
class StoryDesign extends StatelessWidget {
StoryDesign(this.profilePic);
final String profilePic;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(
left: kMediumSeparation / 2,
top: kCommonSeparation,
right: kMediumSeparation / 2,
bottom: kHugeSeparation),
child: InkWell(
onTap: () => print('profile bubble tapped'),
borderRadius: BorderRadius.all(Radius.circular(35.0)),
child: CircleAvatar(
backgroundImage: NetworkImage(profilePic),
radius: 35.0,
backgroundColor: kDefaultColorLoading,
),
),
);
}
}
I think the most probable reason could be the big size of your images.
Also use CachedNetworkImage to load your images faster.
I think it is because of one the image is to big, this one https://images.unsplash.com/photo-1582129617595-c657a072c9b9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=3662&q=80

How to use two global keys for two widgets sharing the same state class

I am trying to create my own custom segment in flutter. That segment has two buttons, one for teachers and other for students. What I am trying to do, it's encapsulate the buttons in one Stateful Widget to handle the setState of both buttons, because I want the buttons to be an AnimatedContainer and if I rebuild the childrens (the buttons) from the parent the transition doesn't works.
Note that the buttons are Stack positioned and I reorder the content to get the tapped button over the other (that will has effect when I set more width in the tapped button, now this is not created yet).
Here is my code:
import 'package:flutter/cupertino.dart';
import '../../app_localizations.dart';
import '../../styles.dart';
GlobalKey<_ButtonState> teachersButtonKey = GlobalKey();
GlobalKey<_ButtonState> studentsButtonKey = GlobalKey();
String _globalTappedButtonId = 'teachersButton';
class FiltersAppBarSegment extends StatefulWidget {
#override
_FiltersAppBarSegmentState createState() => _FiltersAppBarSegmentState();
}
class _FiltersAppBarSegmentState extends State<FiltersAppBarSegment> {
List<Widget> buildStackChildren(SegmentChangedCallBack handleSegmentChanged) {
if (_globalTappedButtonId == 'teachersButton') {
return <Widget>[
Container(
key: UniqueKey(),
child: _Button(
key: studentsButtonKey,
id: 'studentsButton',
label: 'seeStudents',
rightPosition: 1,
onSegmentChanged: handleSegmentChanged,
),
),
Container(
key: UniqueKey(),
child: _Button(
key: teachersButtonKey,
id: 'teachersButton',
label: 'amTeacher',
rightPosition: null,
onSegmentChanged: handleSegmentChanged,
),
),
];
} else {
return <Widget>[
Container(
key: UniqueKey(),
child: _Button(
key: driverButtonKey,
id: 'driverButton',
label: 'amDriver',
rightPosition: null,
onSegmentChanged: handleSegmentChanged,
),
),
Container(
key: UniqueKey(),
child: _Button(
key: studentsButtonKey,
id: 'studentButton',
label: 'amStudent',
rightPosition: 1,
onSegmentChanged: handleSegmentChanged,
),
),
];
}
}
void handleSegmentChanged(String clickedButtonId) {
teachersButtonKey.currentState._handleButtonTapped();
studentsButtonKey.currentState._handleButtonTapped();
}
#override
Widget build(BuildContext context) {
return Container(
height: 42,
padding: EdgeInsets.symmetric(horizontal: 20),
child: Stack(children: buildStackChildren(handleSegmentChanged)),
);
}
}
class _Button extends StatefulWidget {
final String id;
final String label;
final double rightPosition;
final void onSegmentChanged;
_Button({
Key key,
this.id,
this.label,
this.rightPosition,
this.onSegmentChanged,
}) : super(key: key);
#override
_ButtonState createState() => _ButtonState();
}
class _ButtonState extends State<_Button> {
bool _tapped;
double _topPosition;
double _width;
double _height;
double _getTopPosition() => _tapped ? 0 : 5;
double _getHeight() => _tapped ? 42 : 32;
Gradient _getGradient() {
if (_tapped) {
return Styles.darkAccentColorGradient;
} else {
return Styles.darkAccentColorGradientDisabled;
}
}
void _handleButtonTapped() {
setState(() {
_globalTappedButtonId = widget.id;
_tapped = (widget.id == _globalTappedButtonId);
_topPosition = _getTopPosition();
_height = _getHeight();
});
}
#override
void initState() {
super.initState();
_tapped = (widget.id == _globalTappedButtonId);
_topPosition = _getTopPosition();
_height = _getHeight();
}
#override
Widget build(BuildContext context) {
return Positioned(
top: _topPosition,
right: widget.rightPosition,
child: GestureDetector(
onTap: () {
widget.onSegmentChanged('test');
},
child: AnimatedContainer(
duration: Duration(seconds: 1),
curve: Curves.fastOutSlowIn,
width: _width,
height: _height,
decoration: BoxDecoration(
gradient: _getGradient(),
borderRadius: BorderRadius.circular(13),
),
child: Center(
child: Text(
AppLocalizations.of(context).translate(widget.label),
style: Styles.bodyWhiteText,
textAlign: TextAlign.center,
),
),
),
),
);
}
}
I'm sure you have already found a solution to your problem by now, but this question is one of the first search results when looking at this error.
As you already know, per the Flutter doc on GlobalKey:
"You cannot simultaneously include two widgets in the tree with the
same global key. Attempting to do so will assert at runtime."
You can define your own individual keys like:
import 'package:flutter/widgets.dart';
class TestKeys{
static final testKey1 = const Key('__TESTKEY1__');
static final testKey2 = const Key('__TESTKEY2__');
...
}
And then reference them in the widget with key: TestKeys.testKey1
This was described in this question here so perhaps it can help someone with the need for a similar use case.
There are also a few solutions listed in this GitHub issue