setState is not updating the UI - flutter

I am trying to fetch data from the API and I am able to get logs but setState is not working.
Overall what I want to achieve is if there is response show the data on the screen, if there is any error in the API or on server or anything else I want to show it in the snackbar. My moto is to show errors as well.
Below is my model class
import 'http.dart';
class User {
int userId;
int id;
String title;
String body;
User({this.userId, this.id, this.title, this.body});
User.fromJson(Map<String, dynamic> json) {
userId = json['userId'];
id = json['id'];
title = json['title'];
body = json['body'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['userId'] = this.userId;
data['id'] = this.id;
data['title'] = this.title;
data['body'] = this.body;
return data;
}
}
class UserExt {
static getUserInfo(Function(User user) success, Function(String errorMesssage) error) async{
final response = await HTTP.get(api: "https://jsonplaceholder.typicode.com/posts/1");
if(response.isSuccess == true) {
success(User.fromJson(response.response));
} else {
error(response.response);
}
}
}
Below is my http.dart file
import 'dart:html';
import 'package:flutter/cupertino.dart';
import 'package:http/http.dart' as http;
import 'dart:convert' as convert;
import 'package:http/http.dart';
const _timeoutDuration = Duration(seconds: 5);
class HTTP {
static Future<HttpResponse> get({#required String api}) async {
try {
Response response = await http.get(api).timeout(_timeoutDuration);
return _modeledResponse(response);
} catch (error) {
return HttpResponse(isSuccess: false, response: error.toString());
}
}
static Future<HttpResponse> _modeledResponse(Response response) async {
try {
if(response.statusCode == HttpStatus.ok) {
var jsonResponse = convert.jsonDecode(response.body);
return HttpResponse(isSuccess: true, response: jsonResponse);
} else {
return HttpResponse(isSuccess: false, response: response.statusCode.toString());
}
} catch (error) {
return HttpResponse(isSuccess: false, response: error.toString());
}
}
}
class HttpResponse {
final bool isSuccess;
final dynamic response;
HttpResponse({#required this.isSuccess, #required this.response});
}
Below is my screen from where I am calling the API.
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http_request/User.dart';
import 'http.dart';
class ApiCalling extends StatefulWidget {
#override
_ApiCallingState createState() => _ApiCallingState();
}
class _ApiCallingState extends State<ApiCalling> {
bool showLoader = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Stack(
children: <Widget>[
Center(
child: RaisedButton(
child: Text("Call API"),
onPressed: () {
setState(() {
showLoader = true;
});
UserExt.getUserInfo((user){
print("UUUser id = ${user.userId}");
Scaffold.of(context).showSnackBar(SnackBar(content: Text("${user.userId}"),));
setState(() {
showLoader = false;
});
}, (error){
Scaffold.of(context).showSnackBar(SnackBar(content: Text("${error}"),));
setState(() {
showLoader = false;
});
});
},
),
),
Visibility(child: CircularProgressIndicator(backgroundColor: Colors.pink,), visible: showLoader,),
],
),
),
);
}
}
In the current code indicator is not getting show/hide or snackbar is also not getting displayed.

Just made some changes and addons just check the below code :
import 'package:flutter/material.dart';
import 'package:sample_project_for_api/model.dart';
void main() => runApp(ApiCalling());
class ApiCalling extends StatefulWidget {
#override
_ApiCallingState createState() => _ApiCallingState();
}
class _ApiCallingState extends State<ApiCalling> {
bool showLoader = false;
final _scaffoldKey = GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
key: _scaffoldKey,
body: Center(
child: Stack(
children: <Widget>[
Builder(
builder: (context) {
return Column(
children: <Widget>[
RaisedButton(
child: Text(
"this is first api call under the builder widget"),
onPressed: () {
UserExt.getUserInfo((user) {
print("UUUser id = ${user.userId}");
Scaffold.of(context).showSnackBar(SnackBar(
backgroundColor: Colors.redAccent,
content:
Text("This is you user id ${user.userId}"),
));
}, (error) {
Scaffold.of(context).showSnackBar(SnackBar(
duration: Duration(seconds: 2),
backgroundColor: Colors.redAccent,
content: Text("${error.toString()}"),
));
});
},
),
RaisedButton(
child: Text(
"this is second api call under the builder widget"),
onPressed: () {
UserExt.getUserInfo((user) {
print("UUUser id = ${user.userId}");
Scaffold.of(context).showSnackBar(SnackBar(
backgroundColor: Colors.redAccent,
content:
Text("This is you user id ${user.userId}"),
));
}, (error) {
Scaffold.of(context).showSnackBar(SnackBar(
duration: Duration(seconds: 2),
backgroundColor: Colors.redAccent,
content: Text("${error.toString()}"),
));
});
},
)
],
);
},
),
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text("call snacker using the global key"),
onPressed: () {
setState(() {
showLoader = true;
});
UserExt.getUserInfo((user) {
print("UUUser id = ${user.userId}");
_scaffoldKey.currentState.showSnackBar(new SnackBar(
duration: Duration(seconds: 2),
content: new Text(
"This is you user id :${user.userId}")));
setState(() {
showLoader = false;
});
}, (error) {
_scaffoldKey.currentState.showSnackBar(new SnackBar(
duration: Duration(seconds: 2),
content: new Text("${error.toString()}")));
setState(() {
showLoader = false;
});
});
},
),
Secondbutton(),
],
),
),
Visibility(
child: CircularProgressIndicator(
backgroundColor: Colors.pink,
),
visible: showLoader,
),
],
),
),
),
);
}
}
class Secondbutton extends StatefulWidget {
#override
_SecondbuttonState createState() => _SecondbuttonState();
}
class _SecondbuttonState extends State<Secondbutton> {
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text("calling snacker without using the global key"),
onPressed: () {
UserExt.getUserInfo((user) {
print("UUUser id = ${user.userId}");
Scaffold.of(context).showSnackBar(SnackBar(
backgroundColor: Colors.redAccent,
content: Text("This is you user id ${user.userId}"),
));
}, (error) {
Scaffold.of(context).showSnackBar(SnackBar(
duration: Duration(seconds: 2),
backgroundColor: Colors.redAccent,
content: Text("${error.toString()}"),
));
});
},
);
}
}
Your problem was because it was not getting the proper context.

From the official Documentation of flutter https://api.flutter.dev/flutter/material/Scaffold/of.html
When the Scaffold is actually created in the same build function, the context argument to the build function can't be used to find the Scaffold (since it's "above" the widget being returned in the widget tree). In such cases, the following technique with a Builder can be used to provide a new scope with a BuildContext that is "under" the Scaffold:
So basically the problem is with your context of Scaffold,so instead of using context of Direct parent that instantiate the Scaffold, use the context of the child.
Below code will work.
class _ApiCallingState extends State<ApiCalling> {
bool showLoader = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (context)=>
Center(
child: Stack(
children: <Widget>[
Center(
child: RaisedButton(
child: Text("Call API"),
onPressed: () {
setState(() {
showLoader = true;
});
UserExt.getUserInfo((user) {
print("UUUser id = ${user.userId}");
print("context==$context");
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(" User Id${user.userId}"),
));
setState(() {
showLoader = false;
});
}, (error) {
setState(() {
showLoader = false;
});
Scaffold.of(context).showSnackBar(SnackBar(
content: Text("${error}"),
));
});
},
),
),
Visibility(
child:
Center(
child:CircularProgressIndicator(
backgroundColor: Colors.pink,
),
),
visible: showLoader,
)
],
),
),
)
);
}
}

declare a global key
final _scaffoldKey = GlobalKey();
and in UI
_scaffoldKey.currentState.showSnackBar(snackbar);

Related

flutter api returning data but its not workin in listviewbuilder

I'm tryig to solve an error in flutter list view builder once im calling my api functions its showing me the exact data i want, but when im giving the variable to listview builder it showing null value on that variable.
// import 'package:asanhisab/models/ledger_model.dart';
import 'package:asanhisab/utils/helper.dart';
import 'package:flutter/material.dart';
import 'package:odoo_api/odoo_api.dart';
import 'package:asanhisab/services/remoteservices.dart';
class GeneralLedgerScreen extends StatefulWidget {
const GeneralLedgerScreen({Key? key}) : super(key: key);
#override
State<GeneralLedgerScreen> createState() => _GeneralLedgerScreenState();
}
class _GeneralLedgerScreenState extends State<GeneralLedgerScreen> {
List? ledger;
bool isloaded = false;
#override
void initState() {
super.initState();
getLedgerData();
}
getLedgerData() async {
var ledger = await RemoteServices().getLedgerData();
if (ledger != null) {
setState(() {
isloaded = true;
});
}
logger.d(ledger.length);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('General Ledger'),
),
body: Scrollbar(
// child: Center(child: CircularProgressIndicator()),
child: Center(
child: Visibility(
visible: isloaded,
// ignore: sort_child_properties_last
child: ListView.builder(
itemCount: ledger?.length,
itemBuilder: (context, index) {
logger.d(ledger);
return Card(
child: ListTile(
leading: const Icon(Icons.payment),
title: Text(index.toString()),
subtitle: const Text('Name'),
iconColor: Colors.blue,
isThreeLine: true,
trailing: const Text('1000'),
onTap: () {},
),
);
},
),
replacement: const CircularProgressIndicator(),
),
),
),
floatingActionButton: FloatingActionButton.extended(
label: const Text('Create'),
icon: const Icon(Icons.add),
backgroundColor: Colors.blue,
onPressed: () {
// Get.to(() => const CreateAccountHead());
},
),
);
}
}
getLedgerData() async {
var client = OdooClient(storage.read('link'));
var authres = await client.authenticate(storage.read('email'),
storage.read('password'), storage.read('dbname'));
if (authres.isSuccess) {
var data = await client
.callKW('ah_general_ledger.ah_account_head', 'search_read', []);
if (data.getStatusCode() == 200) {
return data.getResult();
} else {
logger.d('Error Occured while fetching data');
// errorAlert(context, 'Error Occured while fetching data');
}
} else {
logger.d('Error Occured');
// errorAlert(context, 'Error Occured');
}
return null;
}
I'm tryig to solve an error in flutter list view builder once im calling my api functions its showing me the exact data i want, but when im giving the variable to listview builder it showing null value on that variable.
This is because your are redefined ledger in getLedgerData(), you should use your attribute instead.
getLedgerData() async {
ledger = await RemoteServices().getLedgerData(); // remove var here at the beginning
if (ledger != null) {
setState(() {
isloaded = true;
});
}
logger.d(ledger.length);
}
You can also use private field for your ledger and isloaded by adding _ caracter as prefix.

Duplicate GlobalKey detected in tree widget scaffoldState

I was trying to update data (add, edit, delete) from my list using API, when I clicked something to update the list e.g deleting an item it showed a successfully updated. but in the Debug Console, there's an error that says
The following assertion was thrown while finalizing the widget tree:
Duplicate GlobalKey detected in the widget tree. The following GlobalKey
was specified multiple times in the widget tree. This will lead to
parts of the widget tree being truncated unexpectedly, because the
second time a key is seen, the previous instance is moved to the new
location. The key was:
[LabeledGlobalKey #47c37] This was determined by noticing that after the widget with the above global key was moved out
of its previous parent, that previous parent never updated during this
frame, meaning that it either did not update at all or updated before
the widget was moved, in either case implying that it still thinks
that it should have a child with that global key.
And when I tried going back to the Previous screen it just showed Blank Screen
Here's the code
Model
import 'dart:convert';
class Nasabah {
int id;
String nama_debitur;
String alamat;
String no_telp;
String no_ktp;
String no_selular;
Nasabah({
this.id = 0,
this.nama_debitur,
this.alamat,
this.no_telp,
this.no_ktp,
this.no_selular,
});
factory Nasabah.fromJson(Map<String, dynamic> json) => Nasabah(
id: json["id"],
nama_debitur: json["nama_debitur"],
alamat: json["alamat"],
no_telp: json["no_telp"],
no_ktp: json["no_ktp"],
no_selular: json["no_selular"],
);
factory Nasabah.fromMap(Map<String, dynamic> map) => Nasabah(
id: map["id"],
nama_debitur: map["nama_debitur"],
alamat: map["alamat"],
no_telp: map["no_telp"],
no_ktp: map["no_ktp"],
no_selular: map["no_selular"],
);
Map<String, dynamic> toJson() => {
"id": id,
"nama_debitur": nama_debitur,
"alamat": alamat,
"no_telp": no_telp,
"no_ktp": no_ktp,
"no_selular": no_selular,
};
#override
String toString() {
return 'Nasabah{id: $id, nama_debitur: $nama_debitur, alamat: $alamat, no_telp: $no_telp, no_ktp: $no_ktp, no_selular: $no_selular}';
}
}
class NasabahResult {
String status;
List<Nasabah> data = new List<Nasabah>();
NasabahResult({
this.status,
this.data,
});
factory NasabahResult.fromJson(Map<String, dynamic> data) => NasabahResult(
status: data["status"],
data: List<Nasabah>.from(
data["data"].map((item) => Nasabah.fromJson(item))),
);
}
NasabahResult nasabahResultFromJson(String jsonData) {
final data = json.decode(jsonData);
return NasabahResult.fromJson(data);
}
String nasabahToJson(Nasabah nasabah) {
final jsonData = nasabah.toJson();
return json.encode(jsonData);
}
Service
import 'package:flutter_auth/Models/nasabah.dart';
import 'dart:convert';
import 'package:http/http.dart' show Client;
class ApiService {
final String baseUrl = '192.168.100.242:8080';
Client client = Client();
Future<List<Nasabah>> getNasabah() async {
final response = await client.get('http://$baseUrl/api/mstdebitur');
print(response.body);
if (response.statusCode == 200) {
final nasabah = (json.decode(response.body) as List)
.map((e) => Nasabah.fromMap(e))
.toList();
return nasabah;
} else {
throw Exception('Failed to load post');
}
}
Future<bool> createNasabah(Nasabah data) async {
String url = new Uri.http("$baseUrl", "/api/mstdebitur/").toString();
final response = await client.post(url,
headers: {"Content-Type": "application/json"},
body: nasabahToJson(data));
if (response.statusCode == 201) {
return true;
} else {
return false;
}
}
Future<bool> updateNasabah(Nasabah data) async {
String url =
new Uri.http("$baseUrl", "/api/mstdebitur/${data.id}").toString();
final response = await client.put(url,
headers: {"Content-Type": "application/json"},
body: nasabahToJson(data));
if (response.statusCode == 200) {
return true;
} else {
return false;
}
}
Future<bool> deleteNasabah(int id) async {
String url = new Uri.http("$baseUrl", "/api/mstdebitur/$id").toString();
final response = await client.delete(url);
if (response.statusCode == 200) {
return true;
} else {
return false;
}
}
}
Home
import 'package:flutter/material.dart';
import 'package:flutter_auth/Models/nasabah.dart';
import 'package:flutter_auth/network/nasabah_service.dart';
import 'FormAddNasabah.dart';
import 'ListNasabah.dart';
GlobalKey<ScaffoldState> _scaffoldState = GlobalKey<ScaffoldState>();
// ignore: must_be_immutable
class DataNasabah extends StatefulWidget {
DataNasabah({Key key}) : super(key: key);
String title;
#override
_DataNasabahState createState() => _DataNasabahState();
}
class _DataNasabahState extends State<DataNasabah> {
ApiService apiService;
ListNasabah _listNasabah;
List<Nasabah> data;
#override
void initState() {
super.initState();
apiService = ApiService();
_listNasabah = new ListNasabah(apiService: apiService);
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldState,
appBar: AppBar(
title: Text(
'Data Nasabah',
style: TextStyle(color: Colors.white),
),
actions: <Widget>[
GestureDetector(
onTap: () async {
var result = await Navigator.push(
_scaffoldState.currentContext,
MaterialPageRoute(builder: (BuildContext context) {
return FormAddNasabah(nasabah: null);
}),
);
if (result != null) {
setState(() {});
}
},
child: Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Icon(
Icons.add,
color: Colors.white,
),
),
)
],
),
body: _listNasabah.createViewList(),
);
}
}
List
import 'package:flutter/material.dart';
import 'package:flutter_auth/Models/nasabah.dart';
import 'package:flutter_auth/network/nasabah_service.dart';
import 'package:flutter_auth/screens/Menu/DataNasabah/FormAddNasabah.dart';
import 'package:flutter_auth/screens/Menu/DataNasabah/NasabahHome.dart';
import 'package:rflutter_alert/rflutter_alert.dart';
class ListNasabah {
ApiService apiService;
ListNasabah({this.apiService});
Widget createViewList() {
return SafeArea(
child: FutureBuilder(
future: apiService.getNasabah(),
builder: (BuildContext context, AsyncSnapshot<List<Nasabah>> snapshot) {
if (snapshot.hasError) {
return Center(
child: Text(
'Something wrong with message: ${snapshot.error.toString()}',
textAlign: TextAlign.center,
),
);
} else if (snapshot.connectionState == ConnectionState.done) {
List<Nasabah> nasabah = snapshot.data;
return nasabahListView(nasabah);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
Widget nasabahListView(List<Nasabah> listnasabah) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: ListView.builder(
shrinkWrap: true,
itemBuilder: (context, index) {
Nasabah nasabah = listnasabah[index];
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
nasabah.nama_debitur,
style: Theme.of(context).textTheme.bodyText1,
),
Text(nasabah.alamat),
Text(nasabah.no_ktp),
Text(nasabah.no_telp),
Text(nasabah.no_selular),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FlatButton(
onPressed: () {
apiService.deleteNasabah(nasabah.id).then((value) =>
Navigator.of(context).pushNamed('start'));
},
child: Text(
"Hapus",
style: TextStyle(color: Colors.red),
),
),
FlatButton(
onPressed: () async {
var result = await Navigator.push(context,
MaterialPageRoute(builder: (context) {
return FormAddNasabah(
nasabah: nasabah,
);
}));
},
child: Text(
'Edit',
style: TextStyle(color: Colors.blue),
),
),
],
)
],
),
),
),
);
},
itemCount: listnasabah.length,
),
);
}
}
Form
import 'package:flutter/material.dart';
import 'package:flutter_auth/Models/nasabah.dart';
import 'package:flutter_auth/network/nasabah_service.dart';
import 'package:flutter_auth/screens/Menu/DataNasabah/NasabahHome.dart';
import 'package:rflutter_alert/rflutter_alert.dart';
class FormAddNasabah extends StatefulWidget {
Nasabah nasabah;
FormAddNasabah({this.nasabah});
#override
_FormAddNasabahState createState() => _FormAddNasabahState();
}
class _FormAddNasabahState extends State<FormAddNasabah> {
ApiService apiService;
TextEditingController _contNama = TextEditingController();
TextEditingController _contAlamat = TextEditingController();
TextEditingController _contNoTelp = TextEditingController();
TextEditingController _contNoKtp = TextEditingController();
TextEditingController _contNoSelular = TextEditingController();
#override
void initState() {
super.initState();
apiService = ApiService();
if (widget.nasabah != null) {
_contNama.text = widget.nasabah.nama_debitur;
_contAlamat.text = widget.nasabah.alamat;
_contNoTelp.text = widget.nasabah.no_telp;
_contNoKtp.text = widget.nasabah.no_ktp;
_contNoSelular.text = widget.nasabah.no_selular;
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
iconTheme: IconThemeData(color: Colors.white),
title: Text(
widget.nasabah == null ? "Tambah Nasabah" : "Edit Nasabah",
style: TextStyle(color: Colors.white),
),
),
body: Stack(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
_buildTextField(_contNama, "Nama Nasabah"),
_buildTextField(_contAlamat, "Alamat"),
_buildTextField(_contNoTelp, "No Telp"),
_buildTextField(_contNoKtp, "No KTP"),
_buildTextField(_contNoSelular, "No Selular"),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: RaisedButton(
child: Text(widget.nasabah == null ? "Tambah" : "Edit"),
color: Colors.blue,
onPressed: () {
int id = 0;
if (widget.nasabah != null) {
id = widget.nasabah.id;
}
Nasabah nasabah = Nasabah(
id: id,
nama_debitur: _contNama.text,
alamat: _contAlamat.text,
no_telp: _contNoTelp.text,
no_ktp: _contNoKtp.text,
no_selular: _contNoSelular.text);
if (widget.nasabah == null) {
apiService.createNasabah(nasabah);
} else {
apiService.updateNasabah(nasabah);
}
Navigator.of(context)
.pushNamed('start')
.whenComplete(() => Navigator.pop(context));
},
),
)
],
),
)
],
),
);
}
Widget _buildTextField(TextEditingController _cont, String label) {
return TextField(
controller: _cont,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: label,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
);
}
}
Thank you for your attention!

How do I show a snackbar from a StateNotifier in Riverpod?

I have the following class that is working fine.
class CartRiverpod extends StateNotifier<List<CartItemModel>> {
CartRiverpod([List<CartItemModel> products]) : super(products ?? []);
void add(ProductModel addProduct) {
bool productExists = false;
for (final product in state) {
if (product.id == addProduct.id) {
print("not added");
productExists = true;
}
else {
}
}
if (productExists==false)
{
state = [
...state, new CartItemModel(product: addProduct),
];
print("added");
}
}
void remove(String id) {
state = state.where((product) => product.id != id).toList();
}
}
The code above works perfectly. In my shopping cart, I want to limit the order of products to just 1 unit, that is why I am doing the code above. It works as I expected.
The only thing now is that, I'd like to show a snackbar alerting the user that he or she can only order 1 unit of each product.
How do I add a snackbar inside my StateNotifier?
DON'T show snackbar here.
You need to listen to the needed value in the widget tree as the follows:
#override
Widget build(BuildContext context, WidgetRef ref) {
ref.listen(cartListPageStateNotifierProvider, (value) {
// show snackbar here...
});
...
}
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/all.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'cart_list_page.freezed.dart';
final cartListPageStateNotifierProvider =
StateNotifierProvider((ref) => CartListPageStateNotifier(ref.read));
final cartListProvider = StateProvider((ref) {
return <CartListItemModel>[];
}); // this holds the cart list, at initial, its empty
class CartListPage extends StatefulWidget {
#override
_CartListPageState createState() => _CartListPageState();
}
class _CartListPageState extends State<CartListPage> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
List<CartListItemModel> productsToBeAddedToCart = [
CartListItemModel(id: 1, name: "Apple"),
CartListItemModel(id: 2, name: "Tomatoes"),
CartListItemModel(id: 3, name: "Oranges"),
CartListItemModel(id: 4, name: "Ginger"),
CartListItemModel(id: 5, name: "Garlic"),
CartListItemModel(id: 6, name: "Pine"),
];
#override
Widget build(BuildContext context) {
return ProviderListener<CartListState>(
provider: cartListPageStateNotifierProvider.state,
onChange: (context, state) {
return state.maybeWhen(
loading: () {
final snackBar = SnackBar(
duration: Duration(seconds: 2),
backgroundColor: Colors.yellow,
content: Text('updating cart....'),
);
return _scaffoldKey.currentState.showSnackBar(snackBar);
},
success: (info) {
final snackBar = SnackBar(
duration: Duration(seconds: 2),
backgroundColor: Colors.green,
content: Text('$info'),
);
_scaffoldKey.currentState.hideCurrentSnackBar();
return _scaffoldKey.currentState.showSnackBar(snackBar);
},
error: (errMsg) {
final snackBar = SnackBar(
duration: Duration(seconds: 2),
backgroundColor: Colors.red,
content: Text('$errMsg'),
);
_scaffoldKey.currentState.hideCurrentSnackBar();
return _scaffoldKey.currentState.showSnackBar(snackBar);
},
orElse: () => SizedBox.shrink(),
);
},
child: Scaffold(
key: _scaffoldKey,
body: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
children: [
SizedBox(height: 40),
Expanded(
child: ListView.builder(
itemCount: productsToBeAddedToCart.length,
itemBuilder: (context, index) {
final item = productsToBeAddedToCart[index];
return ListTile(
title: Text("${item.name}"),
leading: CircleAvatar(child: Text("${item.id}")),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(Icons.add),
onPressed: () => context
.read(cartListPageStateNotifierProvider)
.addProduct(item),
),
SizedBox(width: 2),
IconButton(
icon: Icon(Icons.remove),
onPressed: () => context
.read(cartListPageStateNotifierProvider)
.removeProduct(item),
),
],
),
);
},
),
)
],
),
),
),
);
}
}
class CartListPageStateNotifier extends StateNotifier<CartListState> {
final Reader reader;
CartListPageStateNotifier(this.reader) : super(CartListState.initial());
addProduct(CartListItemModel product) async {
state = CartListState.loading();
await Future.delayed(Duration(seconds: 1));
var products = reader(cartListProvider).state;
if (!products.contains(product)) {
reader(cartListProvider).state.add(product);
return state =
CartListState.success("Added Successfully ${product.name}");
} else {
return state = CartListState.error(
"cannot add more than 1 product of the same kind");
}
}
removeProduct(CartListItemModel product) async {
state = CartListState.loading();
await Future.delayed(Duration(seconds: 1));
var products = reader(cartListProvider).state;
if (products.isNotEmpty) {
bool status = reader(cartListProvider).state.remove(product);
if (status) {
return state =
CartListState.success("removed Successfully ${product.name}");
} else {
return state;
}
}
return state = CartListState.error("cart is empty");
}
}
#freezed
abstract class CartListState with _$CartListState {
const factory CartListState.initial() = _CartListInitial;
const factory CartListState.loading() = _CartListLoading;
const factory CartListState.success([String message]) = _CartListSuccess;
const factory CartListState.error([String message]) = _CartListError;
}
#freezed
abstract class CartListItemModel with _$CartListItemModel {
factory CartListItemModel({final String name, final int id}) =
_CartListItemModel;
}

Flutter pagination loading the same data as in page one when scrolling

I'm building a list of news from an api that has next page results as in the image attached.
The api has only two pages with 10 list items each page.
Data is being passed to the widget. My problem is that when I scroll down the view, it loads the same 10 list items from page one.
This is the api I'm using enter link description here
Rest API
//newsModal.dart
class NewsNote {
String banner_image;
String title;
String text;
String sport;
NewsNote(this.banner_image, this.title, this.text, this.sport);
NewsNote.fromJson(Map<String, dynamic> json) {
banner_image = json['banner_image'];
title = json['title'];
text = json['text'];
sport = json['sport'];
}
}
//page news
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:jabboltapp/models/newsModal.dart';
class JabNews extends StatefulWidget {
#override
_JabNewsState createState() => _JabNewsState();
}
class _JabNewsState extends State<JabNews> {
ScrollController _scrollController = ScrollController();
bool isLoading = false;
String url = "https://jabbolt.com/api/news";
List<NewsNote> _newsNotes = List<NewsNote>();
Future<List<NewsNote>> fetchNewsNotes() async {
if (!isLoading) {
setState(() {
isLoading = true;
});
var response = await http.get(url);
var newsNotes = List<NewsNote>();
if (response.statusCode == 200) {
url = jsonDecode(response.body)['next'];
var newsNotesJson = json.decode(response.body)["results"];
for (var newsNoteJson in newsNotesJson) {
newsNotes.add(NewsNote.fromJson(newsNoteJson));
}
setState(() {
isLoading = false;
_newsNotes.addAll(newsNotes);
});
} else {
setState(() {
isLoading = false;
});
}
return newsNotes;
}
}
#override
void initState() {
fetchNewsNotes().then((value) {
setState(() {
_newsNotes.addAll(value);
});
});
this.fetchNewsNotes();
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
fetchNewsNotes();
}
});
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
Widget _buildProgressIndicator() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Opacity(
opacity: isLoading ? 1.0 : 00,
child: CircularProgressIndicator(),
),
),
);
}
Widget _buildList() {
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
if (index == _newsNotes.length) {
return _buildProgressIndicator();
} else {
return Padding(
padding: EdgeInsets.all(8.0),
child: Card(
child: ListTile(
title: Text((_newsNotes[index].title)),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPage(_newsNotes[index])));
},
),
),
);
}
},
controller: _scrollController,
itemCount: _newsNotes.length,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: dGrey,
appBar: AppBar(
title: Text(
"News",
style: TextStyle(
color: textGrey,
fontFamily: 'bison',
fontSize: 32.0,
letterSpacing: 1.2,
),
),
backgroundColor: Colors.transparent,
elevation: 0,
),
body: Container(
child: _buildList(),
),
);
}
}
You need to add the page number concatenation in the URL
https://jabbolt.com/api/news?page=2

Flutter Stateful Widget State not Initializing

I'm making a command and control application using Flutter, and have come across an odd problem. The main status page of the app shows a list of stateful widgets, which each own a WebSocket connection that streams state data from a connected robotic platform. This worked well when the robots themselves were hardcoded in. However now that I'm adding them dynamically (via barcode scans), only the first widget is showing status.
Further investigation using the debugger shows that this is due to the fact that a state is only getting created for the first widget in the list. Subsequently added widgets are successfully getting constructed, but are not getting a state. Meaning that createState is not getting called for anything other than the very first widget added. I checked that the widgets themselves are indeed being added to the list and that they each have unique hash codes. Also, the IOWebSocketChannel's have unique hash codes, and all widget data is correct and unique for the different elements in the list.
Any ideas as to what could be causing this problem?
Code for the HomePageState:
class HomePageState extends State<HomePage> {
String submittedString = "";
StateContainerState container;
List<RobotSummary> robotList = [];
List<String> robotIps = [];
final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
void addRobotToList(String ipAddress) {
var channel = new IOWebSocketChannel.connect('ws://' + container.slsData.slsIpAddress + ':' + container.slsData.wsPort);
channel.sink.add("http://" + ipAddress);
var newConnection = new RobotSummary(key: new UniqueKey(), channel: channel, ipAddress: ipAddress, state: -1, fullAddress: 'http://' + container.slsData.slsIpAddress + ':' + container.slsData.wsPort,);
scaffoldKey.currentState.showSnackBar(new SnackBar(
content: new Text("Adding robot..."), duration: Duration(seconds: 2),));
setState(() {
robotList.add(newConnection);
robotIps.add(ipAddress);
submittedString = ipAddress;
});
}
void _onSubmit(String val) {
// Determine the scan data that was entered
if(Validator.isIP(val)) {
if(ModalRoute.of(context).settings.name == '/') {
if (!robotIps.contains(val)) {
addRobotToList(val);
}
else {
scaffoldKey.currentState.showSnackBar(new SnackBar(
content: new Text("Robot already added..."), duration: Duration(seconds: 5),));
}
}
else {
setState(() {
_showSnackbar("Robot scanned. Go to page?", '/');
});
}
}
else if(Validator.isSlotId(val)) {
setState(() {
_showSnackbar("Slot scanned. Go to page?", '/slots');
});
}
else if(Validator.isUPC(val)) {
setState(() {
_showSnackbar("Product scanned. Go to page?", '/products');
});
}
else if (Validator.isToteId(val)) {
}
}
#override
Widget build(BuildContext context) {
container = StateContainer.of(context);
return new Scaffold (
key: scaffoldKey,
drawer: Drawer(
child: CategoryRoute(),
),
appBar: AppBar(
title: Text(widget.topText),
),
bottomNavigationBar: BottomAppBar(
child: new Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(icon: Icon(Icons.camera_alt), onPressed: scan,),
IconButton(icon: Icon(Icons.search), onPressed: _showModalSheet,),
],
),
),
body: robotList.length > 0 ? ListView(children: robotList) : Center(child: Text("Please scan a robot.", style: TextStyle(fontSize: 24.0, color: Colors.blue),),),
);
}
void _showModalSheet() {
showModalBottomSheet(
context: context,
builder: (builder) {
return _searchBar(context);
});
}
void _showSnackbar(String message, String route) {
scaffoldKey.currentState.showSnackBar(new SnackBar(
content: new Text(message),
action: SnackBarAction(
label: 'Go?',
onPressed: () {
if (route == '/') {
Navigator.popUntil(context,ModalRoute.withName('/'));
}
else {
Navigator.of(context).pushNamed(route);
}
},),
duration: Duration(seconds: 5),));
}
Widget _searchBar(BuildContext context) {
return new Scaffold(
body: Container(
height: 75.0,
color: iam_blue,
child: Center(
child: TextField(
style: TextStyle (color: Colors.white, fontSize: 18.0),
autofocus: true,
keyboardType: TextInputType.number,
onSubmitted: (String submittedStr) {
Navigator.pop(context);
_onSubmit(submittedStr);
},
decoration: new InputDecoration(
border: InputBorder.none,
hintText: 'Scan a tote, robot, UPC, or slot',
hintStyle: TextStyle(color: Colors.white70),
icon: const Icon(Icons.search, color: Colors.white70,)),
),
)));
}
Future scan() async {
try {
String barcode = await BarcodeScanner.scan();
setState(() => this._onSubmit(barcode));
} on PlatformException catch (e) {
if (e.code == BarcodeScanner.CameraAccessDenied) {
setState(() {
print('The user did not grant the camera permission!');
});
} else {
setState(() => print('Unknown error: $e'));
}
} on FormatException{
setState(() => print('null (User returned using the "back"-button before scanning anything. Result)'));
} catch (e) {
setState(() => print('Unknown error: $e'));
}
}
}
Code snippet for the RobotSummary class:
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
import 'package:test_app/genericStateSummary_static.dart';
import 'dart:convert';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:test_app/StateDecodeJsonFull.dart';
import 'dart:async';
import 'package:test_app/dataValidation.dart';
class RobotSummary extends StatefulWidget {
final String ipAddress;
final String _port = '5000';
final int state;
final String fullAddress;
final WebSocketChannel channel;
RobotSummary({
Key key,
#required this.ipAddress,
#required this.channel,
this.state = -1,
this.fullAddress = "http://10.1.10.200:5000",
}) : assert(Validator.isIP(ipAddress)),
super(key: key);
#override
_RobotSummaryState createState() => new _RobotSummaryState();
}
class _RobotSummaryState extends State<RobotSummary> {
StreamController<StateDecodeJsonFull> streamController;
#override
void initState() {
super.initState();
streamController = StreamController.broadcast();
}
#override
Widget build(BuildContext context) {
return new Padding(
padding: const EdgeInsets.all(20.0),
child: new StreamBuilder(
stream: widget.channel.stream,
builder: (context, snapshot) {
//streamController.sink.add('{"autonomyControllerState" : 3, "pickCurrentListName" : "69152", "plannerExecutionProgress" : 82, "pickUpcCode" : "00814638", "robotName" : "Adam"}');
return getStateWidget(snapshot);
},
),
);
}
#override
void dispose() {
streamController.sink.close();
super.dispose();
}
}
Based on what Jacob said in his initial comments, I came up with a solution that works and is a combination of his suggestions. The code solution he proposed above can't be implemented (see my comment), but perhaps a modification can be attempted that takes elements of it. For the solution I'm working with now, the builder call for HomePageState becomes as follows:
Widget build(BuildContext context) {
List<RobotSummary> tempList = [];
if (robotList.length > 0) {
tempList.addAll(robotList);
}
container = StateContainer.of(context);
return new Scaffold (
key: scaffoldKey,
drawer: Drawer(
child: CategoryRoute(),
),
appBar: AppBar(
title: Text(widget.topText),
),
bottomNavigationBar: BottomAppBar(
child: new Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(icon: Icon(Icons.camera_alt), onPressed: scan,),
IconButton(icon: Icon(Icons.search), onPressed: _showModalSheet,),
],
),
),
body: robotList.length > 0 ? ListView(children: tempList) : Center(child: Text("Please scan a robot.", style: TextStyle(fontSize: 24.0, color: iam_blue),),),
);
}
The problem is you are holding on to the StatefulWidgets between build calls, so their state is always the same. Try separating RobotSummary business logic from the view logic. Something like
class RobotSummary {
final String ipAddress;
final String _port = '5000';
final int state;
final String fullAddress;
final WebSocketChannel channel;
StreamController<StateDecodeJsonFull> streamController;
RobotSummary({
#required this.ipAddress,
#required this.channel,
this.state = -1,
this.fullAddress = "http://10.1.10.200:5000",
}) : assert(Validator.isIP(ipAddress));
void init() => streamController = StreamController.broadcast();
void dispose() => streamController.sink.close();
}
And then in your Scaffold body:
...
body: ListView.builder(itemCount: robotList.length, itemBuilder: _buildItem)
...
Widget _buildItem(BuildContext context, int index) {
return new Padding(
padding: const EdgeInsets.all(20.0),
child: new StreamBuilder(
stream: robotList[index].channel.stream,
builder: (context, snapshot) {
//streamController.sink.add('{"autonomyControllerState" : 3, "pickCurrentListName" : "69152", "plannerExecutionProgress" : 82, "pickUpcCode" : "00814638", "robotName" : "Adam"}');
return getStateWidget(snapshot); // not sure how to change this.
},
),
);
}