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;
Related
I have GetxController with the late Map data and I won't fill this in onInit() after searching on the database, but when the page is open the emulator shows the red screen with the not initialize error.
I need the dataMap1 and 2 for showing the PieChart when the screen opens.
I think this occurred because I use the Future function, But I do not know how to fix this.
this is my entire controller code.
import 'package:get/get.dart';
import 'package:hive/hive.dart';
class ReportScreenController extends GetxController{
late Map<String, double> dataMap1;
final Map<String, double> dataMap2 = {
"ورزشی": 5,
"خصوصی": 3,
"اداری": 5,
"دسته بندی نشده": 3,
};
#override
Future<void> onInit() async {
super.onInit();
//categoryScrollController.position.ensureVisible()
await reportFunction();
}
Future<void> reportFunction() async {
//dataMap1
var taskBox = await Hive.openBox('task');
var taskFinish =
taskBox.values.where((task) => task.status == true).toList();
var taskUnFinish =
taskBox.values.where((task) => task.status == false).toList();
double test = double.parse(taskFinish.length.toString());
double test2 = double.parse(taskUnFinish.length.toString());
print(test.toString());
print(test2.toString());
dataMap1.addAll({
'رو زمین مانده': test2,
'تکمیل شده': test,
});
}
}
my view code is
class ReportScreen extends GetView<ReportScreenController> {
const ReportScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: [
background(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15.0),
child: Column(
children: [
const Text(':نمودار فعالیت', style: boldText),
MyPieChart(dataMap: controller.dataMap1),
const Text(':نمودار وظایف', style: boldText),
MyPieChart(dataMap: controller.dataMap2),
],
),
),
],
),
);
}
}
You forgot to initialize dataMap1, simply in onInit() add dataMap1 = {}.
I think you also dont need a late modifier, just use final final Map<String, double> dataMap1 = {};, but everybody is choosing thier weapons.
In addition i think there will be problem with that how you use controller.dataMap1 in your view. Most likely you dont rebuild your view after you finally initialize / populate dataMap1.
Update:
You can change in controller:
late Map<String, double> dataMap1; to final RxMap<String, double> dataMap1 = RxMap();, and in your view:
MyPieChart(dataMap: controller.dataMap1), to Obx(() => MyPieChart(dataMap: controller.dataMap1.value))
I want to show the all images from the API in the carousel and for items, I want to show 2 items per slide.
and it works correctly what I want. but in every last item, it shows me the error of CarouselSlider. I don't know why it gives me this error. I tried to solve it but failed because I don't know why they give me this error in the last item.
here is my code:-
import 'package:flutter/material.dart';
import 'package:carousel_slider/carousel_slider.dart';
class PersonImages extends StatefulWidget {
PersonImages({Key? key}) : super(key: key);
#override
_PersonImages createState() => _PersonImages();
}
class _PersonImages extends State<PersonImages>{
var UsriD = Auth.prefs?.getString('usrid');
var Imagedata;
var img = "";
var user = "";
#override
void initState() {
super.initState();
getImageData();
}
getImageData() async{
var res = await http.get(Uri.https('www.*******.net', '/index.php',{'act':'usrPhotos','Usrid': '${UsriD}'}));
Imagedata = jsonDecode(res.body);
//print(Imagedata);
setState(() {});
print(res.body);
}
#override
Widget build(BuildContext context) {
//print(Imagedata);
return
Imagedata != null? CarouselSlider.builder(
options: CarouselOptions(
//height: 150,
aspectRatio: 2.0,
enableInfiniteScroll: false,
//viewportFraction: 0.8,
enlargeCenterPage: true,
viewportFraction: 1,
),
itemCount: (Imagedata.length / 2).round(),
//itemCount: 3,
itemBuilder: (BuildContext context, int index, int pageViewIndex) {
final int first = index * 2;
final int second = first + 1;
return
Row(
children: [first, second].map((idx) {
return Expanded(
//flex: 2,
child: Container(
child: Container(
margin: EdgeInsets.all(5.0),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
child:
Image.network(
"https://www.*******.net/files/images/${Imagedata[idx]['image']}",
fit: BoxFit.cover,
width: 200,
height: 200,
),
),
),
)
);
}).toList(),
);
}
): const Center(
child: CircularProgressIndicator(),
);
}
}
Here is my error and output :-
please help me if anyone knows how to resolve it.
is anyone knows how to do this so answer my question?
In case of an odd number length, for example 5
you divided it by 2. which is 2.5 Rounded it then the value is 3.
Now when it loops for the third time. It will try and fetch 2*2 + 1. now you will try and find the element by id 5 which doesn't exist. You can try
itemCount: (Imagedata.length / 2).floor(),
Note you may lose the last image in case of an odd length.
Try checking null like
Imagedata[idx]['image']!= null?
Image.network("...${Imagedata[idx]['image']}", )
:Text("cant find image index"),
so I've already have a problem with this code before I ask for some help and I got some.
The help fix my error but I have a new one .
So basically I'm waiting for a variable and this variable is not null because when I print it I can see the value of this variable.
The screen return me the fallback values and I don't know why.
I have two screens one for create my variable and the other for all the graphic stuff.
This is the detail screen:
class DetailScreen extends StatefulWidget {
const DetailScreen({Key? key, required this.mangaImg, required this.mangaTitle, required this.mangalink}) : super(key: key);
final String mangaImg,mangaTitle,mangalink;
#override
_DetailScreenState createState() => _DetailScreenState();
}
class _DetailScreenState extends State<DetailScreen> {
String? mangaGenre,mangaStatus,mangaAuthor,mangaDesc;
List<Map<String,dynamic>>? mangaDetail;
List<Map<String,dynamic>>? mangaDescList;
List<Map<String,dynamic>>? mangaChapters;
Future<void> getMangaInfo() async {
String TempBaseurl = widget.mangalink.split(".com")[0] + ".com";
String TempRoute = widget.mangalink.split(".com")[1];
final webscraper = WebScraper(TempBaseurl);
if (await webscraper.loadWebPage(TempRoute)){
mangaDetail = webscraper.getElement("div.panel-story-info > div.story-info-right > table > tbody > tr > td.table-value", []);
mangaDescList = webscraper.getElement("div.panel-story-info > div.panel-story-info-description", []);
}
mangaGenre = mangaDetail![3]['title'].toString().trim();
mangaStatus = mangaDetail![2]['title'].toString().trim();
mangaAuthor = mangaDetail![1]['title'].toString().trim();
mangaDesc = mangaDescList![0]['title'].toString().trim();
print(mangaDesc);
print(mangaGenre);
print(mangaStatus);
}
#override
Future<void> getMangaInfos()async {
await getMangaInfo();
}
#override
Widget build(BuildContext context) {
Size screensize = MediaQuery.of(context).size;
return Scaffold(
appBar: AppBar(
backgroundColor: Constants.mygreen,
title: Text(widget.mangaTitle),
centerTitle: true,
),
body: Container(
height: screensize.height,
width: screensize.width,
child: SingleChildScrollView(
child: Column(
children: [
MangaInfo(
mangaImg: widget.mangaImg,
mangaStatus: mangaStatus??"Error" ,
mangaAuthor : mangaAuthor??"Error" ,
And this is the graphic screen:
class MangaInfo extends StatelessWidget {
const MangaInfo({Key? key, required this.mangaImg, required this.mangaStatus, required this.mangaAuthor}) : super(key: key);
final String mangaImg, mangaStatus,mangaAuthor;
#override
Widget build(BuildContext context) {
return Container(
height: 300,
width: double.infinity,
child: Column(
children: [
Expanded(child: Center(child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Container(height: 170, width: 130,child: Image.network(mangaImg, fit: BoxFit.cover,)
),
Text("By $mangaAuthor - $mangaStatus"
, style: const TextStyle(
fontFamily: "SFProDisplay",
))
],
))),
const SizedBox(
height: 10,
),
Container(
height: 80,
width: double.infinity,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: const[
MangaInfoBtn(icon:Icons.play_arrow, title: "Read"),
MangaInfoBtn(icon:Icons.library_add_check, title: "Favorites"),
MangaInfoBtn(icon:Icons.list, title: "Chapters"),]
An image of the screen for more details :
So you get the manga information on the method called getMangaInfo, you await said method on the method getMangaInfos But you never call getMangaInfos! you also override it, but I don't think it is a method declared in State class. This leads me to believe that what you actually meant to do was this:
Future<void> initState() async {
await getMangaInfo();
}
The above is NOT POSSIBLE and will result in a compilation error because initstate should never return a future. The next best thing is this:
void initState() {
getMangaInfo();
}
But this means you are not awaiting getMangaInfo
Both initState and build methods are synchronous, which means you can't use the await keyword on them, on the other hand getMangaInfo is asynchronous. Asynchronous methods will be run whenever flutter has some free time, while synchronous methods will run in order always.
So this is the order in which the methods finish running:
initstate -> build -> getMangaInfo
So by the time you build, getMangaInfo is not done.
I propose two solutions:
1. Use setState to tell flutter when to rebuild:
Future<void> getMangaInfo() async {
String TempBaseurl = widget.mangalink.split(".com")[0] + ".com";
String TempRoute = widget.mangalink.split(".com")[1];
final webscraper = WebScraper(TempBaseurl);
if (await webscraper.loadWebPage(TempRoute)){
mangaDetail = webscraper.getElement("div.panel-story-info > div.story-info-right > table > tbody > tr > td.table-value", []);
mangaDescList = webscraper.getElement("div.panel-story-info > div.panel-story-info-description", []);
}
setState(() {
mangaGenre = mangaDetail![3]['title'].toString().trim();
mangaStatus = mangaDetail![2]['title'].toString().trim();
mangaAuthor = mangaDetail![1]['title'].toString().trim();
mangaDesc = mangaDescList![0]['title'].toString().trim();
});
}
With the change above, the new order looks like this:
initstate -> build -> getMangaInfo -> build.
2. Using a future builder:
This method is probably what I would do, but it might be confusing if you don't know what a FutureBuilder is:
Widget build(BuildContext context) {
Size screensize = MediaQuery.of(context).size;
return FutureBuilder(
future: getMangaInfo(),
builder: (context, snapshot) {
return Scaffold(
appBar: AppBar(
backgroundColor: Constants.mygreen,
title: Text(widget.mangaTitle),
centerTitle: true,
),
body: Container(
height: screensize.height,
width: screensize.width,
child: SingleChildScrollView(
child: Column(
children: [
MangaInfo(
mangaImg: widget.mangaImg,
mangaStatus: mangaStatus??"Error" ,
mangaAuthor : mangaAuthor??"Error" ,
I got a class Restrictions and 3 childs.
I want to pass data from CountryDropDown (and from FieldDropDown class which looks almost same, and from DateSelector class but thats maybe later) class to Restrictions and then to UserOffers (with updating List by tapping a button).
For now I can only define variable in Restrictions class and pass it like this. All on Strings.
Restrictions class:
String selectedCountry = "Argentina";
...
children: <Widget>[
Container(
margin: EdgeInsets.only(left: 20),
child: FieldDropDown(),
),
Container(
padding: EdgeInsets.only(top: 10),
margin: EdgeInsets.only(left: 20),
child: CountryDropDown(
),
...
Container(
child: UserOffers(
country: selectedCountry,
field: selectedField,
),
),
UserOffers class:
class UserOffers extends StatefulWidget {
final String country;
final String field;
UserOffers({
this.country,
this.field
}); ...
CountryDropDown class:
static String value;
static String selected;
#override
void initState() {
selected = _CountryDropDownState.selected;
super.initState();
} ...
... ]
.map((label) => DropdownMenuItem(
child: Text(label),
value: label,
))
.toList(),
onChanged: (value) {
setState(() => selected = value);
print(selected);
},
),
),
)
Thank you in advance.
It seems that your problem is related to the state management in Flutter. There are a few approaches that solve this issue, for example:
inherited widget
provider
bloc
redux
If you want to learn more check the official doumentation.
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.