Flutter setState changing, but not rerendering - android

I've created a simple screen that takes a List of letters and renders them in a grid. I have a button with a shuffle method that shuffles this list. Inside my build method, I see that the state is getting updated with the new list and is printing out a shuffled list each time the button is pressed, but the screen doesn't change.
class _LetterContainerState extends State<LetterContainer> {
List<String> _letters = ['D', 'A', 'B', 'C', 'E', 'F', 'G', 'H'];
void shuffle() {
var random = new Random();
List<String> newLetters = _letters;
for (var i = newLetters.length - 1; i > 0; i--) {
var n = random.nextInt(i + 1);
var temp = newLetters[i];
newLetters[i] = newLetters[n];
newLetters[n] = temp;
}
setState(() {
_letters = newLetters;
});
}
#override
Widget build(BuildContext context) {
print('LETTERS');
print(_letters);
List<LetterTile> letterTiles =
_letters.map<LetterTile>((letter) => new LetterTile(letter)).toList();
return new Column(
children: <Widget>[
new FlatButton(onPressed: shuffle, child: new Text("Shuffle")),
new Container(
color: Colors.amberAccent,
constraints: BoxConstraints.expand(height: 200.0),
child: new GridView.count(
crossAxisCount: 4,
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
children: letterTiles,
))
],
);
}
}
EDIT:
import 'package:flutter/material.dart';
class Vowels {
static const values = ['A', 'E', 'I', 'O', 'U'];
static bool isVowel(String letter) {
return values.contains(letter.toUpperCase());
}
}
class LetterTile extends StatefulWidget {
final String value;
final bool isVowel;
LetterTile(value)
: value = value,
isVowel = Vowels.isVowel(value);
#override
_LetterTileState createState() => new _LetterTileState(this.value);
}
class _LetterTileState extends State<LetterTile> {
_LetterTileState(this.value);
final String value;
#override
Widget build(BuildContext context) {
Color color = Vowels.isVowel(this.value) ? Colors.green : Colors.deepOrange;
return new
Card(
color: color,
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text(
this.value,
style: TextStyle(fontSize: 40.0, color: Colors.white)
)
)
);
}
}

If you replace your example LetterTile widget with a Text widget, the shuffling will work again. The reason this is not working is that a State object is only created the first time a widget is instantiated. So by passing the value directly to the state, you ensure that it never updates. Instead reference the value via widget.value:
class LetterTile extends StatefulWidget {
final String value;
final bool isVowel;
LetterTile(this.value) : isVowel = Vowels.isVowel(value);
#override
_LetterTileState createState() => new _LetterTileState();
}
class _LetterTileState extends State<LetterTile> {
#override
Widget build(BuildContext context) {
Color color = Vowels.isVowel(widget.value) ? Colors.green : Colors.deepOrange;
return Card(
color: color,
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text(
widget.value,
style: TextStyle(fontSize: 40.0, color: Colors.white)
)
)
);
}
}
Edit: Some more explanation.
The point of a State object is that it is persistent across builds. The first time you build a particular LetterTile widget, this also creates a new State object. The second time build is called, the framework finds the existing State object and reuses it. This is how you can have resources like timers, network requests, and other bound to an immutable tree of widgets.
In your case, since you passed the letter to the State object, each one would stay associated with whatever the first passed letter was. Instead, by reading them off the widget you always receive the most up to date data when the widget associated with the State object is replaced.

Related

Flutter UI update failure with Riverpod StateNotifierProvider

I am building a simple calculator and using Riverpod for state management. Though I can update state, the UI is not being updated with the changes... Can someone tell me what I'm doing wrong ?? Here's the code:
Calculator Model
class CalculatorModel {
final bool shouldAppend;
final String equation;
final String result;
const CalculatorModel(
{this.shouldAppend = true, this.equation = '0', this.result = '0'});
CalculatorModel copyWith({
bool? shouldAppend,
String? equation,
String? result,
}) =>
CalculatorModel(
shouldAppend: shouldAppend ?? this.shouldAppend,
equation: equation ?? this.equation,
result: result ?? this.result);
}
Calculator State Notifier Implementation
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:components/calculator_components/calculator_model.dart';
import 'package:math_expressions/math_expressions.dart';
final calculatorProvider =
StateNotifierProvider<CalculatorStateNotifier, List<CalculatorModel>>(
(ref) => CalculatorStateNotifier());
class CalculatorStateNotifier extends StateNotifier<List<CalculatorModel>> {
CalculatorStateNotifier() : super([const CalculatorModel()]);
void append(String calcInput) {
final equation = () {
return state[0].equation == '0'
? calcInput
: state[0].equation + calcInput;
}();
state[0] = CalculatorModel(equation: equation);
}
}
Click function for calculator buttons. State is getting updated, successfully...
void onClickedButton(String calcInput, WidgetRef ref) {
ref.read(calculatorProvider.notifier).append(calcInput);
ref.watch(calculatorProvider);
print(ref.watch(calculatorProvider)[0].equation);
}
Riverpod not updating UI when called in the presentation layer...
#override
Widget build(BuildContext context, WidgetRef ref) {
Size size = MediaQuery.of(context).size;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
margin: const EdgeInsets.symmetric(vertical: 10),
width: size.width * 0.8,
child: Column(children: [
Expanded(
child: Container(
child: Padding(
padding: const EdgeInsets.only(top: 15.0, right: 22),
child: Consumer(builder: (context, ref, _) {
return buildCalculatorScreen(
ref.watch(calculatorProvider)[0].equation,
ref.watch(calculatorProvider)[0].result);
}),
)),
),
]),
);
}
}
First, you should not use ref.watch on asynchronous calls, including button calls.
Second, Since our state is immutable, we are not allowed to do state[0] = . You need to update your List in some other way, such as using the spread operator or List.of()
More information here:
StateNotifierProvider from Riverpod
state should be immutable, you have to set a new object/array as the new state.
You can do something like this:
final newState = List.from(state);
newState[0] = CalculatorModel(equation: equation);
state = newState;

ReorderableListView.builder() is not reordering upon user input

My expectation from ReoderableListView.builder() is simply, as what the Flutter doc says, to create a widget that allows users to move/drag list-items up and down the list view. However, what I was getting from my emulator was no dragging animation, no reordering of the list (upon user input), and not even call to the onReorder callback.
Stuff I have tried:
Made sure my taskID and taskName lists have the same length
Added debug outputs for itemBuilder and onReorder callback, surprisingly receiving debug output only from itemBuilder callback
Copied and pasted the widget code and its corresponding lists data exactly to other widget classes (or files) and still got the same result
Added the exactly same ValueKey in the Text() inside the list-view.
Tried using the same list data as what the Text() is rendering, taskNames, for the value ValueKey
The only thing I did not try was directly copying and pasting the official example of this widget to my codebase, but the test code I have should already be very similar to the official example, structurally.
checklist.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'add_task.dart';
class Checklist extends StatefulWidget {
const Checklist({Key? key}) : super(key: key);
#override
State<Checklist> createState() => _ChecklistState();
}
class _ChecklistState extends State<Checklist> {
final List<int> taskID = <int>[0, 1, 2, 4, 6];
final List<String> taskNames = <String>['A', 'B', 'C', 'D', 'E'];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Row(
children: [
const Text("TODO"),
ElevatedButton(
onPressed: () {
},
child: const Text("Google Calendar"),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.black12),
),
),
]
),
),
body: ReorderableListView.builder(
itemCount: taskNames.length,
itemBuilder: (BuildContext context, int index) {
print("B");
return ListTile(
key: ValueKey(taskID[index]),
tileColor: Colors.black12,
title: Text('Entry ${taskNames[index]}')
);
},
onReorder: (int oldIndex, int newIndex) {
print("A");
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final int elTid = taskID.removeAt(oldIndex);
final String elTnm = taskNames.removeAt(oldIndex);
taskID.insert(newIndex, elTid);
taskNames.insert(newIndex, elTnm);
});
},
),
);
}
}
Your code does work. It moves only on long press and drag. Not on normal drag.. On normal drag it will tend to scroll the list

Passing variable value from state to another widget

I am struggling with one simple case and it would be lovely if someone could help here.
Let's say that I have some stateful widget. In its state I define a variable (might be of any type) and this variable is later changed through setState() method, where I dynamically assign its value based on some certain criteria. Evertyhing until this point is conducted within one class.
What if I would like to access the value of this variable from another class (totally different page) and if its value changes, rebuild it? Could you please also give me some examples?
Thanks in advance!
That's exactly why State MANAGEMENT exists.
To manage your state through your app.
There are many different options to follow
See:
https://flutter.dev/docs/development/data-and-backend/state-mgmt/options
You can use provider package in that case.
In yaml file add,
provider: ^4.3.2+4
class HomeApp extends StatefulWidget {
#override
_HomeAppState createState() => _HomeAppState();
}
class _HomeAppState extends State<HomeApp> {
Counter _counterProvider;
#override
void initState() {
super.initState();
_counterProvider = Provider.of(context, listen: false);
}
void updateCounter() {
_counterProvider.setCount();
}
#override
Widget build(BuildContext context) {
Counter _counterProvider = Provider.of(context);
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child: Text(
_counterProvider.count.toString(),
style: TextStyle(
fontSize: 22,
),
),
),
RaisedButton(
onPressed: updateCounter,
child: Text('Click'),
),
],
),
),
);
}
}
// class for storing data(Counter.dart)
import 'package:flutter/material.dart';
class Counter extends ChangeNotifier { // create a common file for data
int _count = 0;
int get count => _count;
void setCount() {
_count++;
notifyListeners();
}
}

Flutter size button

I'm making a FlatButton for sizes so the user is going to select his own size.
how can I make the button border goes bold when the user presses the button?
-the buttons are created by ListView.builder so I can't set local variables for them.
you can create a variable which hold the button number who's border you want to set bolder and on click you can change value of that variable.
following example clear your idea.
import 'package:flutter/material.dart';
class TextFieldInput extends StatefulWidget {
#override
_TextFieldInputState createState() => _TextFieldInputState();
}
class _TextFieldInputState extends State<TextFieldInput> {
final List<int> list = [1,2,3,4,5,6,7,8,9,0];
int number = -1;
#override
Widget build(BuildContext context) {
return Center(
child: Container(
child: ListView.builder(
itemCount: (list.length).ceil(),
itemBuilder: (context, int index){
return new FlatButton(
key: Key(index.toString()),
child: new Text(list[index].toString()),
shape: Border.all(
width: number==index ? 5.0 : 3.0
),
onPressed: (){
setState(() {
number = index;
});
}
);
}
)
),
);
}
}

Flutter - How can I set the state of a Checkbox widget in a dynamic list of Checkbox

Sorry if this isn't perfectly clear. I welcome any suggestions for clarification as I'm new to Flutter and also to asking questions on StackOverflow.
I'll post what is and isn't working. Basically I want to have a Perk widget with a varying number of Checkbox widgets in it. When I create the widgets within the build method I can set the state of them (checked or unchecked) but dynamically creating Checkbox widgets using a for loop in a list and passing that list to a Row makes the Checkboxes unresponsive.
DOESN'T WORK (but I don't know why and it's what I'm going for)
import 'package:flutter/material.dart';
import 'package:gloomhaven_enhancement_calc/data/constants.dart';
class Perk extends StatefulWidget {
final int numOfChecks;
final String details;
final List<String> icons;
Perk(this.numOfChecks, this.details, this.icons);
#override
State<StatefulWidget> createState() => PerkState();
}
class PerkState extends State<Perk> {
int _numOfChecks;
String _details;
List<String> _icons;
bool _isChecked = false;
List<Checkbox> checkList = [];
#override
void initState() {
super.initState();
_numOfChecks = widget.numOfChecks;
_details = widget.details;
_icons = widget.icons;
setState(() {
void _itemChange(bool val) {
// setState(() {
_isChecked = val;
// });
print('clliccked' + val.toString());
}
for (int x = 0; x < _numOfChecks; x++) {
checkList.add(Checkbox(
value: _isChecked,
onChanged: (bool value) => _itemChange(value),
),);
}
});
}
Widget build(BuildContext context) {
return Container(
color: Colors.grey.withOpacity(0.75),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
// here I pass the list of checkboxes created above, but they
// don't respond to user clicks
Row(children: checkList),
Expanded(
child: Text(
_details,
style: TextStyle(fontFamily: secondaryFontFamily),
),
),
],
));
}
}
WORKS (but I have to hard-code in the number of checkboxes - I need to be able to create them dynamically based on numOfChecks parameter)
import 'package:flutter/material.dart';
import 'package:gloomhaven_enhancement_calc/data/constants.dart';
class Perk extends StatefulWidget {
final int numOfChecks;
final String details;
final List<String> icons;
Perk(this.numOfChecks, this.details, this.icons);
#override
State<StatefulWidget> createState() => PerkState();
}
class PerkState extends State<Perk> {
int _numOfChecks;
String _details;
List<String> _icons;
bool _isChecked = false;
List<Checkbox> checkList = [];
#override
void initState() {
super.initState();
_numOfChecks = widget.numOfChecks;
_details = widget.details;
_icons = widget.icons;
}
void checkboxChecked(bool val) {
// setState(() {
_isChecked = val;
// });
print('clliccked' + val.toString());
}
Widget build(BuildContext context) {
return Container(
color: Colors.grey.withOpacity(0.75),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Row(children:
<Widget>[
// here I create checkboxes based on the number but it's
// obviously a hack just to make it work
Checkbox(
value: _isChecked,
onChanged: (bool value) => checkboxChecked(value),
),
_numOfChecks > 1 ? Checkbox(
value: _isChecked,
onChanged: (bool value) => checkboxChecked(value),
) : Container(),
_numOfChecks > 2 ? Checkbox(
value: _isChecked,
onChanged: (bool value) => checkboxChecked(value),
) : Container(),
_numOfChecks > 3 ? Checkbox(
value: _isChecked,
onChanged: (bool value) => checkboxChecked(value),
) : Container(),
_numOfChecks > 4 ? Checkbox(
value: _isChecked,
onChanged: (bool value) => checkboxChecked(value),
) : Container(),
]
),
Expanded(
child: Text(
_details,
style: TextStyle(fontFamily: secondaryFontFamily),
),
),
],
));
}
}
BONUS
I need to also save each checkbox state in sharepreferences. I was going to just create a separate entry for each one but I'm sure there's a more efficient way. They don't need to do anything, just need to remember if they're checked or unchecked and persist that through restarts.
DOESN'T WORK section has a lot of mistakes.
You have to store details of checkboxes (titles, icons, count) inside of Perk class. You need only titles and icons. Then you can retrieve checkboxes count by calling titles.length.
State of checked items has to be inside of State class (List checkList).
Change type of _checkList to List and initialize it in initState.
#override
void initState() {
super.initState();
...
_checkList = List(widget.titles.length);// or List.generate(widget.numOfChecks, (i) => false);
}
To build a number of widgets (or other items) you can use this approach:
Row(
children: List.generate(
_numOfChecks,
(i) => Checkbox(
value: _checkList[i],
onChanged: (bool value) => setState(() { _checkList[i] = value; }),
)
).toList();
)
I hope my answer will help you to find right solution.

Categories

Resources