setState() within StatefulWidget not working properly - flutter

The thing I'm trying to do is, to change the colour of a RawMaterialButton when the button is clicked. Read about StatefulWidget and it seemed like it should work, but for some reason it doesn't.
flutter: Another exception was thrown: setState() called in constructor: ButtonTest#1a93b(lifecycle state: created, no widget, not mounted)
ButtonTest class:
class ButtonState extends StatefulWidget {
#override
State createState() => ButtonTest();
}
class ButtonTest extends State<ButtonState> implements Cipher {
#override
String icon = '';
#override
String title = '';
bool enabled = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: RawMaterialButton(
shape: CircleBorder(side: BorderSide(color: Colors.black)),
fillColor: enabled ? Colors.blue : Colors.red,
onPressed: () {
setState(() {
this.enabled = true;
});
},
padding: EdgeInsets.all(0)),
);
}
}
Cipher class:
abstract class Cipher {
String icon;
String title;
Widget build(BuildContext context);
}
getCiphers()
getCiphers() {
final List<Cipher> ciphers = new List();
ciphers.add(ButtonTest());
return ciphers;
}
Main class:
void main() => runApp(CipherTools());
class CipherTools extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'CipherTools',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: CipherScreen(
ciphers: getCiphers(),
),
);
}
}
class CipherScreen extends StatelessWidget {
final List<Cipher> ciphers;
CipherScreen({Key key, #required this.ciphers}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Ciphers'),
),
body: ListView.builder(
itemCount: ciphers.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(ciphers[index].title),
// When a user taps on the ListTile, navigate to the DetailScreen.
// Notice that we're not only creating a DetailScreen, we're
// also passing the current todo through to it!
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(cipher: ciphers[index]),
),
);
},
);
},
),
);
}
}
class DetailScreen extends StatelessWidget {
// Declare a field that holds the Todo
final Cipher cipher;
// In the constructor, require a Todo
DetailScreen({Key key, #required this.cipher}) : super(key: key);
#override
Widget build(BuildContext context) {
return cipher.build(context);
}
}
What am I doing wrong here?

Wrap setState() like this.
if(this.mounted) {
setState(() {
this.enabled = true;
});
}

A couple of things:
ButtonState should be called ButtonTest because this is the
StatefulWidget
ButtonTest should be called ButtonTestState because this is the State.
Then in DetailScreen, in the build() method, you could return the StatefulWidget (ButtonTest), like this:
#override
Widget build(BuildContext context) {
return ButtonTest();
}

Related

As a common appbar widget how to change appbar color when page is scrolled Flutter

Good morning friends, I'm trying to make the appbar transparent or white in scrollable
parts.
For me, this solution An efficient way in Flutter to change appbar color when scrolled works, but as the person said, I don't want to use setState continuously and do it in every separate component, so I'm trying to do what is mentioned in the comment. For this reason, I created a common appbar widget so that I can use it in other components. I made the CustomAppBar widget statefull, but I don't know where to add the scrollController. Therefore, I see errors. If anyone has time, can you help?
The code below is the widget where I call CustomAppBar.
import ...
const ExtractionBody({Key? key, required this.goal}) : super(key: key);
final Objective goal;
#override
ExtractionBodyState createState() => ExtractionBodyState();
}
class ExtractionBodyState extends ConsumerState<ExtractionBody> {
#override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: PreferredSize(
preferredSize: const Size.fromHeight(100),
child: CustomAppBar(
icon: IconButton(
icon: const Icon(PhosphorIcons.xBold),
onPressed: () => Navigator.of(context).pushNamedAndRemoveUntil(
HomePage.routeName,
(route) => false,
),
),
),
),
body: ExtractionRequestContent(
goal: widget.goal, scrollController: _scrollController),
);
}
}
Finally, this is my CustomAppBar code. Thank you very much in advance. and have a good weekend everyone
class CustomAppBar extends StatefulHookConsumerWidget {
static String routeName = "/extraction_body";
const CustomAppBar({Key? key, this.icon})
: preferredSize = const Size.fromWidth(50),
super(key: key);
final Widget? icon;
#override
final Size preferredSize; // default is 56.0
#override
CustomAppBarState createState() => CustomAppBarState();
}
class CustomAppBarState extends ConsumerState<CustomAppBar> {
bool isAppbarCollapsing = false;
final ScrollController _scrollController = ScrollController();
#override
void initState() {
super.initState();
_initializeController();
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void _initializeController() {
_scrollController.addListener(() {
if (_scrollController.offset == 0.0 &&
!_scrollController.position.outOfRange) {
//Fully expanded situation
if (!mounted) return;
setState(() => isAppbarCollapsing = false);
}
if (_scrollController.offset >= 9.0 &&
!_scrollController.position.outOfRange) {
//Collapsing situation
if (!mounted) return;
setState(() => isAppbarCollapsing = true);
}
});
}
#override
Widget build(BuildContext context) {
return AppBar(
elevation: 0,
backgroundColor:
isAppbarCollapsing ? AppColors.monochromeWhite : Colors.transparent,
title: Text(context.l10n.buttonCancel),
titleSpacing: 4,
leading: widget.icon,
);
}
}
Thanks!
Instead of define ScrollController in CustomAppBar, pass it in constructor like this:
class CustomAppBar extends StatefulHookConsumerWidget {
static String routeName = "/extraction_body";
const CustomAppBar({Key? key, this.icon, required this.scrollController})
: preferredSize = const Size.fromWidth(50),
super(key: key);
final Widget? icon;
final ScrollController scrollController;
#override
final Size preferredSize; // default is 56.0
#override
CustomAppBarState createState() => CustomAppBarState();
}
and use it like this:
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
final ScrollController scrollController = ScrollController(); //<---- define scrollController here
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(100),
child: CustomAppbar(scrollController: scrollController)),
body: ListView.builder(
controller: scrollController,
itemCount: 10,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 100,
width: 100,
color: Colors.red,
margin: EdgeInsets.all(12),
);
},
),
));
}
}

Can we change a widgets variable with InkWell onTap function?

I have a custom written stateful widget that wrapped with InkWell and I want to change the widgets variable when onTap function gets activated. Is there any way to achieve that?
Here is my custom written widget
import 'package:flutter/material.dart';
class DrawerListTile extends StatefulWidget {
final tileIcon;
final tileText;
bool isSelected = false;
DrawerListTile({this.tileIcon, this.tileText});
#override
State<DrawerListTile> createState() => _DrawerListTileState();
}
class _DrawerListTileState extends State<DrawerListTile> {
#override
Widget build(BuildContext context) {
return ListTile(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
selected: widget.isSelected,
selectedTileColor: Colors.black12,
selectedColor: Colors.black54,
leading: Icon(widget.tileIcon),
title: Text(widget.tileText),
);
}
}
And here is my InkWell widget
InkWell(
onTap: () => setState(() {
//Here is the part that I want to change the DrawerListTile's isSelected value
}),
child: DrawerListTile(
tileText: "Some Text", tileIcon: Icons.credit_card_rounded),
),
I know that I can write the onTap function inside the DrawerListTile but it is not useful in my situation so is there any way to achieve what I want?
You can do something like the below solution ... you can use your isSelected variable for this purpose.
The parent view:
class MainView extends StatefulWidget {
const MainView({Key? key}) : super(key: key);
#override
State<MainView> createState() => _MainViewState();
}
class _MainViewState extends State<MainView> {
String text = DateTime.now().toString();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('App'),
),
body: InkWell(
child: Center(child: TargetWidget(text: text)),
onTap: () {
setState(() {
text = DateTime.now().toString();
});
},
),
);
}
}
The child view:
class TargetWidget extends StatefulWidget {
String text;
TargetWidget({Key? key, required this.text}) : super(key: key);
#override
State<TargetWidget> createState() => _TargetWidgetState();
}
class _TargetWidgetState extends State<TargetWidget> {
#override
Widget build(BuildContext context) {
return Container(
child: Text(widget.text),
);
}
}
You should pass your variable as a parameter to your DrawerListTile(),You can create a model class that will hold all the variables you need and pass them to the widget. Thus, whenever you call the setState function, new parameters are sent to the widget and the widget is updated.
Ex:
InkWell(
onTap: () => setState(() {
//Here is the part that I want to change the DrawerListTile's isSelected value
yourFirstVariable = something;
yourSecondVariable = something;
}),
child: DrawerListTile(
tileText: "Some Text",
tileIcon: Icons.credit_card_rounded,
drawerVariables: DrawerModel(
demoVar1 = yourFirstVariable,
demoVar2 = yourSecondVariable...
),
),
),
class DrawerModel {
final var demoVar1;
final var demoVar2;
DrawerModel ({required this.demoVar1, required this.demoVar1,});
}
class DrawerListTile extends StatefulWidget {
final tileIcon;
final tileText;
final DrawerModel drawerVariables;
bool isSelected = false;
DrawerListTile({this.tileIcon, this.tileText, this.drawerVariables});
#override
State<DrawerListTile> createState() => _DrawerListTileState();
}
class _DrawerListTileState extends State<DrawerListTile> {
#override
Widget build(BuildContext context) {
return ListTile(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
selected: widget.isSelected,
selectedTileColor: Colors.black12,
selectedColor: Colors.black54,
leading: Icon(widget.tileIcon),
title: Text(widget.tileText),
);
}
}

Why state change error occurs on flutter_riverpod during initialization

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final dataProvider = StateNotifierProvider<DataNotifier, List<int>>((ref) {
return DataNotifier();
});
class DataNotifier extends StateNotifier<List<int>> {
DataNotifier() : super([]);
Future<void> getData() async {
state = [];
await Future.delayed(const Duration(seconds: 2));
state = [1, 2];
}
}
void main() => runApp(ProviderScope(child: App()));
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Home(),
);
}
}
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.white,
child: Center(
child: ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => SecondPage()),
);
},
child: const Text('Next page'),
),
),
),
);
}
}
class SecondPage extends ConsumerStatefulWidget {
const SecondPage({Key? key}) : super(key: key);
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends ConsumerState<SecondPage> {
#override
void initState() {
super.initState();
ref.read(dataProvider.notifier).getData();
}
#override
Widget build(BuildContext context) {
final numbers = ref.watch(dataProvider);
return Scaffold(
appBar: AppBar(),
body: ListView.builder(
itemBuilder: (_, index) {
return Text('data: $index');
},
itemCount: numbers.length,
),
);
}
}
I am new to riverpod and I noticed this error while changing state.
In the above code when I tap the "next page" button at the fresh start for the first time it works as expected but when I go back and again tap the "next page" button, an error shown below is thrown:
StateNotifierListenerError (At least listener of the StateNotifier Instance of 'DataNotifier' threw an exception
when the notifier tried to update its state.
Does anyone know why this occurs and how can I prevent it.
You can solve the issue using autoDispose
final dataProvider = StateNotifierProvider.autoDispose<DataNotifier, List<int>>(
(ref) => DataNotifier(),
);
For Future I prefer using FutureProvider.
More about riverpod

Understanding ScopedModel and states in flutter

I hope someone can help me understand ScopedModel in flutter
I have a simple project where when I change a models value, a page wrapper will redirect me to some other page.
In this sample code below, all I want is when I tap login button in the login page, I want the pageWrapper to redirect me to HomePage().
I do notice that after clicking the login button and then I click on the hot reload, I get redirected to the HomePage. which tells me that the model has been updated. Its just that the PageWrapper did not redraw. If thats the case, what should I do so the PageWrapper will redraw when a change has occurred?
Here is a sample code
main.dart
void main() => runApp(MyApp(
model: UserInfoModel(),
));
class MyApp extends StatelessWidget {
final UserInfoModel model;
const MyApp({Key key, #required this.model}): super(key: key);
#override
Widget build(BuildContext context) {
return ScopedModel<UserInfoModel>(
model: model,
child: MaterialApp(
title: 'scoped model demo',
home: PageWrapper(),
),
);
}
}
page_wrapper.dart
class PageWrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScopedModelDescendant<UserInfoModel>(
builder: (context, child, userInfo){
if (userInfo.isLoggedIn){
return Home();
}else{
return Login();
}
}
);
}
}
login.dart
class Login extends StatefulWidget {
#override
_LoginState createState() => _LoginState();
}
class _LoginState extends State<Login> {
#override
Widget build(BuildContext context) {
return ScopedModelDescendant<UserInfoModel>(
builder: (context, child, userInfoModel){
return Scaffold(
appBar: AppBar(
title: Text("Login"),
),
body: Container(
child: Center(
child: FlatButton.icon(onPressed: (){
userInfoModel.isLoggedIn=true;
}, icon: Icon(Icons.person,size: 120,),
label: Text("Login"))
),
),
);
});
}
}
home.dart
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Home"),
),
body: Container(
child: Center(
child: Text("you're home"),
),
),
);
}
}
user_info_model.dart
class UserInfoModel extends Model{
String firstName;
String lastName;
bool isLoggedIn=false;
UserInfoModel({
this.firstName,
this.lastName
});
}
You can copy paste run full code below
Step 1: You can interact UserInfoModel with get and set , you can see code below
Step 2: You need notifyListeners()
code snippet
if (userInfo.isLoggedIn) {
return Home();
...
onPressed: () {
userInfoModel.isLoggedIn = true;
},
class UserInfoModel extends Model {
String firstName;
String lastName;
bool _isLoggedIn = false;
UserInfoModel({this.firstName, this.lastName});
bool get isLoggedIn => _isLoggedIn;
set isLoggedIn(bool newSetting) {
_isLoggedIn = newSetting;
notifyListeners();
}
}
working demo
full code
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
void main() => runApp(MyApp(
model: UserInfoModel(),
));
class MyApp extends StatelessWidget {
final UserInfoModel model;
const MyApp({Key key, #required this.model}) : super(key: key);
#override
Widget build(BuildContext context) {
return ScopedModel<UserInfoModel>(
model: model,
child: MaterialApp(
title: 'scoped model demo',
home: PageWrapper(),
),
);
}
}
class PageWrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScopedModelDescendant<UserInfoModel>(
builder: (context, child, userInfo) {
if (userInfo.isLoggedIn) {
return Home();
} else {
return Login();
}
});
}
}
class Login extends StatefulWidget {
#override
_LoginState createState() => _LoginState();
}
class _LoginState extends State<Login> {
#override
Widget build(BuildContext context) {
return ScopedModelDescendant<UserInfoModel>(
builder: (context, child, userInfoModel) {
return Scaffold(
appBar: AppBar(
title: Text("Login"),
),
body: Container(
child: Center(
child: FlatButton.icon(
onPressed: () {
userInfoModel.isLoggedIn = true;
},
icon: Icon(
Icons.person,
size: 120,
),
label: Text("Login"))),
),
);
});
}
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Home"),
),
body: Container(
child: Center(
child: Text("you're home"),
),
),
);
}
}
class UserInfoModel extends Model {
String firstName;
String lastName;
bool _isLoggedIn = false;
UserInfoModel({this.firstName, this.lastName});
bool get isLoggedIn => _isLoggedIn;
set isLoggedIn(bool newSetting) {
_isLoggedIn = newSetting;
notifyListeners();
}
}

flutter: child widget not rebuilt after parent rebuild

Version:
Flutter-Version: 1.12.14 channel dev
Dart-Version: 2.7.0
Question:
I wan write a Todo App. when i click floatbutton add a new Todo, but in some cases its not work well.
The problem in Scaffold.body, detials in code.
it work well when i use TodoPage(todoList: _todoList).
_pageList.elementAt(_activeIndex) is not work when i submit textfield .
I found the print('Build Home')print after submit but print('Build TodoPage') not print.
why???
My Code:
import 'package:flutter/material.dart';
void main() => runApp(App());
class App extends StatelessWidget{
#override
Widget build(BuildContext context){
return MaterialApp(
title: 'TodoList',
home: Home(),
);
}
}
class Home extends StatefulWidget{
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home>{
List<String> _todoList = ['a', 'b', 'c'];
TextEditingController _controller;
List<Widget> _pageList;
int _activeIndex;
Widget _curPage;
#override
void initState(){
super.initState();
_activeIndex = 0;
_pageList = [TodoPage(todoList: _todoList,), OtherPage()];
_curPage = _pageList[_activeIndex];
_controller = TextEditingController();
}
#override
Widget build(BuildContext context){
print('build Home');
return Scaffold(
appBar: AppBar(title: Text('Todo'),),
body: _pageList.elementAt(_activeIndex), // this is not work
// body: TodoPage(todoList: _todoList,), // this is work well
floatingActionButton: FloatingActionButton(
onPressed: _openDlg,
child: Icon(Icons.add),
),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(icon: Icon(Icons.list), title: Text('Todo')),
BottomNavigationBarItem(icon: Icon(Icons.favorite), title: Text('Other')),
],
currentIndex: _activeIndex,
selectedItemColor: Colors.blue,
onTap: _onMenuTap,
),
);
}
_onMenuTap(int index){
setState(() {
_activeIndex = index;
});
}
_openDlg(){
showDialog(
context: context,
builder: (BuildContext context){
return SimpleDialog(
children: <Widget>[
TextField(
controller: _controller,
),
SimpleDialogOption(
child: FloatingActionButton(child: Text('submit'), onPressed: _addTodo,),
)
],
);
}
);
}
_addTodo(){
print(_controller.text);
setState(() {
_todoList.add(_controller.text);
});
}
}
class TodoPage extends StatefulWidget{
TodoPage({Key key, this.todoList}): super(key: key);
List<String> todoList;
_TodoPageState createState() => _TodoPageState();
}
class _TodoPageState extends State<TodoPage>{
#override
void initState(){
super.initState();
}
#override
Widget build(BuildContext context){
print('build TodoPage');
return Column(
children: _buildTodoList(),
);
}
List <Widget> _buildTodoList(){
return widget.todoList.map((todo){
return Text(todo, style: TextStyle(fontSize: 30),);
}).toList();
}
}
class OtherPage extends StatelessWidget{
#override
Widget build(BuildContext context){
return Center(child: Text('Other Page'));
}
}
That is logical.
You are reusing an existing instance of a Widget, and widgets are immutable.
As such, the framework notice that the instance of the widget did not change and doesn't call build to optimize performances.
Your problem being, you violated the rule of widgets being immutable, which makes this optimization break your app.
What you did:
class MyState extends State<MyStatefulWidget> {
SomeWidget myWidget = SomeWidget()..someProperty = "initial value";
void onSomething() {
setState(() {
myWidget.someProperty = "new value";
});
}
#override
Widget build(BuildContext context) {
return myWidget;
}
}
What you should instead do:
class MyState extends State<MyStatefulWidget> {
SomeWidget myWidget = SomeWidget(someProperty: "initial value");
void onSomething() {
setState(() {
myWidget = SomeWidget(someProperty: "new value");
});
}
#override
Widget build(BuildContext context) {
return myWidget;
}
}
Alternatively, just don't cache the widget instance at all.