Why is the flutter setState not updating the list? - flutter

I have two api's from which I get data, I wanna check if any of the desired field match with each other data coming but it don't seem to work.
I have two api's from which I get data, I wanna check if any of the desired field match with each other data coming but it don't seem to work.
I have two api's from which I get data, I wanna check if any of the desired field match with each other data coming but it don't seem to work.
class Home extends StatefulWidget {
const Home({ Key? key }) : super(key: key);
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
List compaints = [];
List found = [];
List match = [];
var u_imei;
var d_imei;
Future fetch() async {
http.Response response_one;
http.Response response_two;
response_one = await http.get(Uri.parse("https://script.google.com/macros/s/AKfycbxi9kN6NWvoFjkQZE1OVJDPpWmQeYk0V5hNfRKqXS19wjz86SYq_FoQ51fjNQY22bN4/exec"));
response_two = await http.get(Uri.parse("https://script.google.com/macros/s/AKfycbx20kfm1g4Hno9DzO1uccmLgmuIQBkXQcA9tnhcup873TsEMEy9ejszCluhf4FzW-YJqQ/exec"));
if(response_one == 200 && response_two == 200){
if(mounted){
setState(() {
compaints = jsonDecode(response_one.body);
found = jsonDecode(response_two.body);
u_imei = compaints[2];
d_imei = found[1];
if(d_imei == u_imei) {
if(mounted){
print("working");
setState(() {
match.add(d_imei);
});
}
}
});
}
}
}
#override
Widget build(BuildContext context) {
// fetchu();
// fetchd();
// check();
fetch();
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(compaints.length.toString()),
SizedBox(height: 20,),
Text(found.length.toString()),
],
),
);
}
}

There are several issues:
fetch is called in build, which causes rebuild loop. First step to move it to initState.
Response is compared to 200 (response_one == 200). There is property statusCode.
Parsing imei's is not correct. Responses:
[{time: 2022-07-03T16:07:15.491Z, name: Asif, imei: 1234, number: 9014580667}]
[{time: 2022-07-05T08:12:31.029Z, imei: 1234}]
So should be something like this:
u_imei = compaints[0]['imei'];
d_imei = found[0]['imei'];

Calling the fetch method inside the build will loop as the fetch method calls the setState(). Use initState() to call on the load or on refresh indicator while the user pulls to refresh or any other method.
class Home extends StatefulWidget {
const Home({ Key? key }) : super(key: key);
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
List compaints = [];
List found = [];
List match = [];
var u_imei;
var d_imei;
#override
void initState() {
super.initState();
fetch();
}
#override
void dispose() {
super.dispose();
}
Future fetch() async {
http.Response response_one;
http.Response response_two;
response_one = await http.get(Uri.parse("https://script.google.com/macros/s/AKfycbxi9kN6NWvoFjkQZE1OVJDPpWmQeYk0V5hNfRKqXS19wjz86SYq_FoQ51fjNQY22bN4/exec"));
response_two = await http.get(Uri.parse("https://script.google.com/macros/s/AKfycbxEDXZAmieRWk-8kOX-07ta8Q4TIa9Lf_NAiArEWhaU4jXO8d_DM9Jwuc0DRIwmUpPh/exec"));
if(response_one.statusCode == 200 && response_two.statusCode == 200){
if(mounted){
setState(() {
compaints = jsonDecode(response_one.body);
found = jsonDecode(response_two.body);
u_imei = compaints[2];
d_imei = found[1];
if(d_imei == u_imei) {
if(mounted){
print("working");
setState(() {
match.add(d_imei);
});
}
}
});
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(compaints.length.toString()),
SizedBox(height: 20,),
Text(found.length.toString()),
],
),
);
}
}

Related

Range Error (Index) Flutter GETX : invalid value

Here I have an error when I fetch data, I take a reference from youtube then I apply and use data from local but when I run it I get an error as I described. here is my source code
Controller.dart
class ProdukKonvensionalController extends GetxController {
var konvenList = <ProdukKonvenModel>[].obs;
var isLoading = true.obs;
#override
void onInit() {
super.onInit();
fetchKonven();
}
Future<void> fetchKonven() async {
final response =
await http.get(Uri.parse('http://192.168.100.207:8080/konven'));
if (response.statusCode == 200) {
ProdukKonvenModel _produkkonvenModel =
ProdukKonvenModel.fromJson(jsonDecode(response.body));
konvenList.add(ProdukKonvenModel(
kategoriNama: _produkkonvenModel.kategoriNama,
kategoriId: _produkkonvenModel.kategoriId,
kontenId: _produkkonvenModel.kontenId,
kontenMenu: _produkkonvenModel.kontenMenu,
kontenParent: _produkkonvenModel.kontenParent,
kontenUrl: _produkkonvenModel.kontenUrl,
));
isLoading.value = true;
} else {
Get.snackbar("Error Loading Data",
'Server Responded: ${response.statusCode}:${response.reasonPhrase.toString()}');
}
}
}
PageView.dart
class ProdukKonvensionalPage extends StatelessWidget {
const ProdukKonvensionalPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final _controller = Get.find<ProdukKonvensionalController>();
return Scaffold(
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('${_controller.konvenList[0].kategoriNama}'),
],
)),
);
}
}
and the following shows an error
try this
var konvenList = <ProdukKonvenModel>[].obs;
var isLoading = true.obs;
#override
void onInit() {
super.onInit();
fetchKonven();
}
Future<void> fetchKonven() async {
final response = await http.get(Uri.parse('http://192.168.100.207:8080/konven'));
if (response.statusCode == 200) {
ProdukKonvenModel _produkkonvenModel =
ProdukKonvenModel.fromJson(jsonDecode(response.body));
_produkkonvenModel.forEach((element) => konvenList.add(element));
isLoading.value = true;
} else {
Get.snackbar("Error Loading Data",
'Server Responded:
${response.statusCode}:${response.reasonPhrase.toString()}');
}
}
}

Flutter Custom State Management

What I am trying to achieve is a small custom state management solution that I believe is powerful enough to run small and large apps. The core is based on the ValueNotifier and ValueListenable concepts in flutter. The data can be accessed anywhere in the app with out context since I am storing the data like this:
class UserData {
static ValueNotifier<DataLoader<User>> userData =
ValueNotifier(DataLoader<User>());
static Future<User> loadUserData() async {
await Future.delayed(const Duration(seconds: 3));
User user = User();
user.age = 23;
user.family = 'Naoushy';
user.name = 'Anass';
return user;
}
}
So by using UserData.userData you can use the data of the user whenever you want. Everything works fine until I encountered a problem of providing a child to my custom data consumer that rebuilds the widget when there is a new event fired. The DataLoader class looks like this:
enum Status { none, hasError, loading, loaded }
class DataLoader<T> {
Status status = Status.none;
T? data;
Object? error;
bool get hasError => error != null;
bool get hasData => data != null;
}
which is very simple. Now the class for consuming the data and rebuilding looks like this:
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:testing/utils/dataLoader/data_loader.dart';
class DataLoaderUI<T> extends StatefulWidget {
final ValueNotifier<DataLoader<T>> valueNotifier;
final Widget noneStatusUI;
final Widget hasErrorUI;
final Widget loadingUI;
final Widget child;
final Future<T> future;
const DataLoaderUI(
{Key? key,
required this.valueNotifier,
this.noneStatusUI = const Text('Data initialization has not started'),
this.hasErrorUI = const Center(child: Text('Unable to fetch data')),
this.loadingUI = const Center(
child: CircularProgressIndicator(),
),
required this.child,
required this.future})
: super(key: key);
#override
State<DataLoaderUI> createState() => _DataLoaderUIState();
}
class _DataLoaderUIState extends State<DataLoaderUI> {
Future startLoading() async {
widget.valueNotifier.value.status = Status.loading;
widget.valueNotifier.notifyListeners();
try {
var data = await widget.future;
widget.valueNotifier.value.data = data;
widget.valueNotifier.value.status = Status.loaded;
widget.valueNotifier.notifyListeners();
} catch (e) {
log('future error', error: e.toString());
widget.valueNotifier.value.error = e;
widget.valueNotifier.value.status = Status.hasError;
widget.valueNotifier.notifyListeners();
}
}
#override
void initState() {
super.initState();
log('init state launched');
if (!widget.valueNotifier.value.hasData) {
log('reloading or first loading');
startLoading();
}
}
//AsyncSnapshot asyncSnapshot;
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<DataLoader>(
valueListenable: widget.valueNotifier,
builder: (context, dataLoader, ui) {
if (dataLoader.status == Status.none) {
return widget.noneStatusUI;
} else if (dataLoader.status == Status.hasError) {
return widget.hasErrorUI;
} else if (dataLoader.status == Status.loading) {
return widget.loadingUI;
} else {
return widget.child;
}
});
}
}
which is also simple yet very effective. since even if the initState function is relaunched if the data is already fetched the Future will not relaunch.
I am using the class like this:
class TabOne extends StatefulWidget {
static Tab tab = const Tab(
icon: Icon(Icons.upload),
);
const TabOne({Key? key}) : super(key: key);
#override
State<TabOne> createState() => _TabOneState();
}
class _TabOneState extends State<TabOne> {
#override
Widget build(BuildContext context) {
return DataLoaderUI<User>(
valueNotifier: UserData.userData,
future: UserData.loadUserData(),
child: Text(UserData.userData.value.data!.name??'No name'));
}
}
The error is in this line:
Text(UserData.userData.value.data!.name??'No name'));
Null check operator used on a null value
Since I am passing the Text widget as an argument with the data inside it. Flutter is trying to pass it but not able to since there is no data yet so its accessing null values. I tried with a normal string and it works perfectly. I looked at the FutureBuilder widget and they use a kind of builder and also the ValueLisnableBuilder has a builder as an arguement. The problem is that I am not capable of creating something like it for my custom solution. How can I just pass the child that I want without having such an error and without moving the ValueLisnable widget into my direct UI widget?
I have found the solution.
Modify the DataLoaderUI class to this:
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:testing/utils/dataLoader/data_loader.dart';
class DataLoaderUI<T> extends StatefulWidget {
final ValueNotifier<DataLoader<T>> valueNotifier;
final Widget noneStatusUI;
final Widget hasErrorUI;
final Widget loadingUI;
final Widget Function(T? snapshotData) child;
final Future<T> future;
const DataLoaderUI(
{Key? key,
required this.valueNotifier,
this.noneStatusUI = const Text('Data initialization has not started'),
this.hasErrorUI = const Center(child: Text('Unable to fetch data')),
this.loadingUI = const Center(
child: CircularProgressIndicator(),
),
required this.child,
required this.future})
: super(key: key);
#override
State<DataLoaderUI<T>> createState() => _DataLoaderUIState<T>();
}
class _DataLoaderUIState<T> extends State<DataLoaderUI<T>> {
Future startLoading() async {
widget.valueNotifier.value.status = Status.loading;
widget.valueNotifier.notifyListeners();
try {
var data = await widget.future;
widget.valueNotifier.value.data = data;
widget.valueNotifier.value.status = Status.loaded;
widget.valueNotifier.notifyListeners();
} catch (e) {
log('future error', error: e.toString());
widget.valueNotifier.value.error = e;
widget.valueNotifier.value.status = Status.hasError;
widget.valueNotifier.notifyListeners();
}
}
#override
void initState() {
super.initState();
log('init state launched');
if (!widget.valueNotifier.value.hasData) {
log('reloading or first loading');
startLoading();
}
}
//AsyncSnapshot asyncSnapshot;
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<DataLoader<T>>(
valueListenable: widget.valueNotifier,
builder: (context, dataLoader, ui) {
if (dataLoader.status == Status.none) {
return widget.noneStatusUI;
} else if (dataLoader.status == Status.hasError) {
return widget.hasErrorUI;
} else if (dataLoader.status == Status.loading) {
return widget.loadingUI;
} else {
return widget.child(dataLoader.data);
}
});
}
}
and use it like this:
DataLoaderUI<User>(
valueNotifier: UserData.userData,
future: UserData.loadUserData(),
child: (user) {
return Text(user!.name ?? 'kk');
});
Take a look at my version of the same sort of state management approach here: https://github.com/lukehutch/flutter_reactive_widget

State and Scroll position restore Flutter

I have an app which fetches posts from a site using a API and then displays it. There are three navigation options, which are basically filters.
The problem is, whenever I switch to another navigation tab (I'm using bottom navigation bar), it ends up rebuilding the whole page, meaning it will fetch all that data again and it might potentially contain new data.
What I want to do is to keep restore this data in a way that is fast and my initState() doesn't get called(because that is what fetches the data). I did try using all the different kind of keys but I cant get it to work.
Main page:
class AppHomePage extends StatefulWidget {
AppHomePage({Key? key}) : super(key: key);
#override
_AppHomePageState createState() => _AppHomePageState();
}
List<Widget> _navs = [
BestPostsRoute(key: PageStorageKey("bestP")),
HotPostsRoute(key: PageStorageKey("hotP")),
NewPostsRoute(key: PageStorageKey("newP"))
];
class _AppHomePageState extends State<AppHomePage> {
int _currentIndex = 0;
onTap(index) => {
setState(() => {_currentIndex = index})
};
#override
Widget build(BuildContext context) {
return Scaffold(
/* appbar ... */
body: _navs.elementAt(_currentIndex),
bottomNavigationBar: BottomNavigationBar(
items: [
/* nav items */
],
currentIndex: _currentIndex,
onTap: onTap,
),
);
}
}
One of the three pages(the code is similar in all three):
/* imports... */
class HotPostsRoute extends StatefulWidget {
HotPostsRoute({Key? key}) : super(key: key);
#override
_HotPostsRouteState createState() => _HotPostsRouteState();
}
class _HotPostsRouteState extends State<HotPostsRoute> {
late PostInstance postInstance;
List<Post> _posts = [];
bool _loaded = false;
fetchPosts(String? after) async {
var stream = postInstance.front.hot(limit: 10, after: after);
await for (UserContent post in stream) {
Submission submission = post as Submission;
Post pPost = Post(submission);
pPost.parse().then((value) => setState(() {
_posts.add(pPost);
}));
}
setState(() {
_loaded = true;
});
}
#override
void initState() {
super.initState();
if (mounted) {
setState(() {
redditInstance =
Provider.of<PostInstanceState>(context, listen: false)
.getInstance;
});
fetchPosts("");
}
}
// Fetches and generates posts
Widget _buildPosts() {
return ListView.builder(
itemCount: _posts.length + 1,
itemBuilder: (ctx, index) {
if (index < _posts.length) {
return _buildPost(_posts.elementAt(index));
} else {
fetchPosts(_posts.last.fullname);
return SpinKitDualRing(color: Colors.white);
}
},
);
}
// A singular post
Widget _buildPost(Post post) {
print(post.object);
return PostCard(post, key: ObjectKey(post.object)); // .object just creates a map of all fields
}
#override
Widget build(BuildContext context) {
setState(() {});
return Container(
child: _loaded ? _buildPosts() : SpinKitDualRing(color: Colors.white),
);
}
}
So I kept searching and eventually a post on Medium led me to the IndexedStack Widget.
Its a widget that is made from the Stack widget and basically loads and stores the state of all its childrens. Unlike Stack, it shows its children one at a time and thus is perfect to use with BottomNavigationBar.
Here's the Blog post for anyone looking out.

How to use the return of function to fill in a DropDownMenu in flutter?

I have this method that returns a list filled with items from an api and I want to use it to fill a dropdownmenuItem using this
data.map((item) {
return DropdownMenuItem(
child: Text(
item['animals_id']),
value: item['animals_id'],
);
}).toList(),
the methode in question that i used to fill that list with from api is
getIaDetailsByAnimalID() async {
var response = await http.get(
Uri.parse(URL +
'/api/optifarm.ia_detail/?query={animals_id,name,order_animal,date_animal,date_identification_animal,sexe_animal,choix,sur_chaleur,prix,product_id}'),
headers: {'Cookie': 'session_id=${v.session}'});
List<IaDetails> data = [];
var mJson = json.decode(response.body)['result'];
if (response.statusCode == 200) {
for (var item in mJson) {
data.add(IaDetails.fromJson(item));
}
return data;
}
}
I want to use the same data in this "List data = [];" to fill the dropdownmenuItems
I'm still new to dart/flutter development so I appreciate any help given
Is your dropdown list based on a stateful widget? If it is, store the data as a property of the state, then just update it within a setState callback.
Something along these lines should work for you:
class DropDownMenu extends StatefulWidget {
const DropDownMenu({Key? key}) : super(key: key);
#override
_DropDownMenuState createState() => _DropDownMenuState();
}
class _DropDownMenuState extends State<DropDownMenu> {
List<IaDetails> data = [];
#override
Widget build(BuildContext context) {
return Column(
children: data.map((item) {
return DropdownMenuItem(
child: Text(item['animals_id']),
value: item['animals_id'],
);
}).toList(),
);
}
void getIaDetailsByAnimalID() async {
var response = await http.get(
Uri.parse(URL +
'/api/optifarm.ia_detail/?query={animals_id,name,order_animal,date_animal,date_identification_animal,sexe_animal,choix,sur_chaleur,prix,product_id}'),
headers: {'Cookie': 'session_id=${v.session}'});
List<IaDetails> newData = [];
var mJson = json.decode(response.body)['result'];
if (response.statusCode == 200) {
for (var item in mJson) {
newData.add(IaDetails.fromJson(item));
}
// Updating this.data within setState will rebuild the
// widget tree based on the new data.
setState(() {
data = newData;
});
}
}
}

Flutter, problem using InitState method and problem using setState

I have tried many things to get to call an API and get data, this has been satisfactory but I have not been able to do it from the correct method, when I do it from build it works perfectly but it is not the right place, when I try to do it in initState it simply does not it works, it doesn't even execute the print ();
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class Post {
final List<dynamic> data;
Post({this.data});
factory Post.fromJson(Map<String, dynamic> json) {
return Post(
data: json['response'],
);
}
}
class LoaderPublicity extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return _LoaderPublicity();
}
}
class _LoaderPublicity extends State<LoaderPublicity> {
List allPublicity;
#override
void initState() {
super.initState();
getPublicity().then((value) {
print(allPublicity);
});
print(allPublicity);
}
//Obtener todas las publicidades desde el api
Future<void> getPublicity() async {
var response = await http.post(
'http://contablenift.co:3008/consult/getGeneralPublicity',
body: {'actividad': "Turistico", 'location': "No"});
print('????????2');
if (response.statusCode == 200) {
setState(() {
allPublicity = Post.fromJson(json.decode(response.body)).data;
});
}
}
#override
Widget build(BuildContext context) {
getPublicity();
// TODO: implement build
return Container(
child: Column(
children: <Widget>[Text('Aqui va la publicidad')],
),
);
}
}
**
Solved the problem, after going to the entire development team we realized that my phone did not have internet, happy day**
The code below should work, I've added a callback in the initState method.
class LoaderPublicity extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return _LoaderPublicity();
}
}
class _LoaderPublicity extends State<LoaderPublicity> {
List allPublicity;
#override
void initState() {
super.initState();
getPublicity().then((value) {
print(allPublicity);
print(value);
});
}
//Obtener todas las publicidades desde el api
Future<void> getPublicity() async {
var response = await http.post(
'http://contablenift.co:3008/consult/getGeneralPublicity',
body: {'actividad': "Turistico", 'location': "No"})
print('????????2');
if (response.statusCode == 200) {
setState(() {
allPublicity = Post.fromJson(json.decode(response.body)).data;
});
}
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[Text('Aqui va la publicidad')],
),
);
}
}