What's the correct way to use provider with an api call?
My current setup, although I don't know if it's correct, is I have a response model and a Response class and the response model gets passed to the response class. the ui is using a provider. Heres's the code:
class InfoProvider extends ChangeNotifier {
Future<Response> getSomeInfo(SomeInfo someInfo) async {
try {
final responseJson = await _provider.post('/info', someInfo.toJson());
ResponseModel someResponse = ResponseModel.fromJson(responseJson['payload']);
return Response.success(someResponse);
} catch (e) {
if (e.toString() == 'refresh_token_not_found') return Response.unauthenticated();
return Response.error(e.getError());
}
}
Future<Response> fetchInfo() async {
try {
final responseJson = await _provider.get('info');
if (responseJson['payload'].isEmpty) return Response.success([]);
AllInfoResponse alLInfoResponse = AllInfoResponse.fromJson(responseJson['payload']);
_allInfo = alLInfoResponse.AllInfoResponse;
return Response.success(alLInfoResponse);
} catch (e) {
if (e.toString() == 'refresh_token_not_found') return Response.unauthenticated();
return Response.error(e.toString());
}
}
}
The ui has a future builder like this: future: Provider.of<InfoProvider>(context).getSomeInfo(),
So that all works, but how can I add some more items to the ui?
Here's my model:
class ResponseModel {
final List<SingleResponseModel> ResponseModel;
ResponseModel({this.ResponseModel});
factory ResponseModel.fromJson(List<dynamic> json) => ResponseModel(
ResponseModel: List<SingleResponseModel>.from(json.map((x) => SingleResponseModel.fromJson(x))),
);
}
class SingleResponseModel {
final String id;
final String title;
SingleResponseModel({
this.id,
this.title,
});
factory SingleResponseModel.fromJson(Map<String, dynamic> json) {
return SingleResponseModel(
id: json['_id'],
title: json['title'],
);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['title'] = this.title;
return data;
}
}
The best way to do this in your scenario is to wrap MaterialApp inside MyApp() in your main.dart file with MultiProvider. Try something like this: https://pub.dev/packages/provider#multiprovider You can place a ChangeNotifierProvider inside it.
Here is how you can access the values of ResponseModel in your UI:
final model=Provider.of<ResponseModel>(context,listen:false);
Don't forget to add getters and setters so that you can use notifyListeners() if you have any ui that depends on it.
This is how your model should look like:
class ResponseModel extends ChangeNotifier {
List<SingleResponseModel> _myModel;
get myModel => _myModel;
set myModel(List<SingleResponseModel> myModel) {
_myModel = myModel;
notifyListeners();
}
}
Here is how you can display your data in a Text Widget (Ideally, you should use Selector instead of Consumer so that the widget only rebuilds if the value its listening to changes):
#override
Widget build(BuildContext context) {
//other widgets
Selector<ResponseModel, int>(
selector: (_, model) => model.myModel,
builder: (_, model, __) {
return ListView.builder(
itemCount: model.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(model[index].title),
subtitle:Text(model[index].id),
);
},
);
}
)
}
Hope this helps! Good Luck!
Related
snap shot from the code
Hello
I am trying to get Data from API
And put it inside List<Map<String,dynamic>>
I use a several ways but they doesn't work
In the image the data saved in the variable but when i use foreach it ignore the index 0
I try to print index 0 and it is ignore it and ignore print (extracted data [0]) and i can't print the length of that list it ignore the print
Their is no errors
Snapshot of the console
I am using flutter 2.8.0
And dart 2.15.0
This is your UI code
class ApiExample extends StatefulWidget {
const ApiExample({Key? key}) : super(key: key);
#override
_ApiExampleState createState() => _ApiExampleState();
}
class _ApiExampleState extends State<ApiExample> {
final items = initAndSet();
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<StackHelp>>(
future: items,
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(child: Text(snapshot.data.toString()));
}
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) => ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(snapshot.data![index].logo!),
),
title: Text(snapshot.data![index].name!),
),
);
}
return Center(
child: Text("Waiting..."),
);
},
),
);
}
}
This is your API code
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:stackoverflow/model/stackHelp.dart';
Future<List<StackHelp>> initAndSet() async {
const url = "http://muhammeddevxd.pythonanywhere.com/api/ecommerce";
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
final Iterable decodeJson = jsonDecode(response.body);
return decodeJson.map((item) => StackHelp.fromJson(item)).toList();
} else {
throw SocketException("No Internet Connection");
}
}
This is your model class
class StackHelp {
int? id;
String? name;
String? logo;
StackHelp({this.id, this.name, this.logo});
StackHelp.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['name'];
logo = json['logo'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['name'] = this.name;
data['logo'] = this.logo;
return data;
}
}
final result is this
Whenever flutter is acting strange, I recommend running the following commands (close the debug session first):
Command 1
flutter clean
Command 2
flutter pub get
If it's still acting strange, it means that the problem is in the code.
Now, your code runs perfectly on my computer, so I'm not sure what the problem is. Although we can try another aproach with it:
Future<void> initAndSet() async {
var url = 'http://muhammeddevxd.pythonanywhere.com/api/ecommerce';
final response = await http.get(Uri.parse(url));
var extractedData =
List<Map<String, dynamic>>.from(jsonDecode(response.body));
extractedData.forEach((element) {
print(element);
});
}
!! Don't forget to import 'dart:convert' and 'package:http/http.dart' as http.
I keep getting the error mentioned above during runtime of my flutter app. Basically what I am trying to achieve is to fetch data from an api and display it in a form of a SliverList by using a FutureBuilder.
This was working perfectly fine until I changed my code for the list from FutureBuilder<List> to FutureBuilder<List> to make use of the class EntertainerEvent which has all the fields I need for display from the json file.
How can I resolve this because it seems like the contructor or the application itslef is not picking up the data when I make use of a custom class.
This is the code for the EntertainerEvent class:
class EntertainerEvent {
final int eventId;
final int entertainerId;
final int eventTypeId;
final int categoryId;
final String eventName;
final String description;
final String imagePoster;
final String location;
final DateTime startDate;
final DateTime endDate;
final double entreeFee;
const EntertainerEvent({required this.eventId, required this.entertainerId, required this.eventTypeId,
required this.categoryId, required this.eventName, required this.description, required this.imagePoster,
required this.location, required this.startDate, required this.endDate, required this.entreeFee});
factory EntertainerEvent.fromJson(Map<String, dynamic> event) {
return EntertainerEvent(
eventId: event['EventID'],
entertainerId: event['EntertainerID'],
eventTypeId: event['EventTypeID'],
categoryId: event['CategoryID'],
eventName: event['EventName'],
description: event['Description'],
imagePoster: event['ImagePoster'],
location: event['Location'],
startDate: event['StartDate'],
endDate: event['EndDate'],
entreeFee: event['EntryFee'],
);
}
}
Below is the code for fetching data from the api:
Future<List<EntertainerEvent>> fetchEvents() async {
var result = await http.get(Uri.parse(apiUrl));
if (result.statusCode == 200) {
var content = result.body;
var arr = json.decode(content) as List;
return arr.map((eve) => new EntertainerEvent.fromJson(eve)).toList();
} else {
print('Not loaded');
throw Exception('Unable to fetch data from the Rest API');
}
}
late Future<List<EntertainerEvent>> _fetchEvents;
#override
void initState() {
_fetchEvents = fetchEvents();
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<EntertainerEvent>>(
future: _fetchEvents,
builder: (BuildContext context, AsyncSnapshot snapshot) {
var childCount = 0;
if (snapshot.connectionState != ConnectionState.done) {
childCount = 1;
} else {
childCount = snapshot.data.length;
}
return SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
if (snapshot.hasData) {
List<EntertainerEvent> someData = snapshot.data;
print('data here');
//Do some stuff
}
}, childCount: childCount),
);
});
}
I do not know what exactly is it that I am missing because this code works if I use the type dynamic instead of the custom class EntertainerEvent.
Thank you all in advance!
Wrap it with hasData:
if(snapshot.hasData){
return SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
List<EntertainerEvent> someData = snapshot.data;
print('data here');
//Do some stuff
}, childCount: childCount),
);}
return CircularProgressIndicator();
I am implementing login with provider but I am not getting data on dashboard page.
Model Class
class LoginModel {
Data data;
int status;
String message;
LoginModel({this.data, this.status, this.message});
LoginModel.fromJson(Map<String, dynamic> json) {
data = json['data'] != null ? new Data.fromJson(json['data']) : null;
status = json['status'];
message = json['message'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
if (this.data != null) {
data['data'] = this.data.toJson();
}
data['status'] = this.status;
data['message'] = this.message;
return data;
}
}
class Data {
String customerId;
String customerMobileNo;
String customerToken;
String otp;
Data({this.customerId, this.customerMobileNo, this.customerToken, this.otp});
Data.fromJson(Map<String, dynamic> json) {
customerId = json['customerId'];
customerMobileNo = json['customerMobileNo'];
customerToken = json['customerToken'];
otp = json['otp'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['customerId'] = this.customerId;
data['customerMobileNo'] = this.customerMobileNo;
data['customerToken'] = this.customerToken;
data['otp'] = this.otp;
return data;
}
}
Provider Class
class AuthProvider extends ChangeNotifier {
Future<LoginModel> generateOTP(String mobileNumber) async {
var result;
Response response = await post(
AppUrl.login,
body: {
'mobileNo': mobileNumber,
},
);
if(response.statusCode==200) {
final responseData = json.decode(response.body);
var userData = responseData['data'];
print(responseData);
LoginModel authUser = LoginModel.fromJson(userData);
notifyListeners();
}
else {
print("Something went wrong");
}
return result;
}
}
Display Page
class Dashboard extends StatelessWidget {
#override
Widget build(BuildContext context) {
final userTest = Provider.of<AuthProvider>(context);
return Scaffold(
body: Center(
child: ListView(
shrinkWrap: true,
children: [
Text(userTest.authUser.data.customerToken),
],
),
),
);
}
Error
The following NoSuchMethodError was thrown while handling a gesture:
The getter 'customerToken' was called on null.
Receiver: null
Tried calling: customerToken
How can I access the property of LoginModel class. Can anyone solve my query please help me, I tried a lott but I can not get value.
You cannot notify your listeners with normal Future functions (though, I am not sure because you didn't provide the full code of your provider class). You will have to put your generateOTP() function in a changenotifier class that will help you to notify your listeners whenever required and make it scoped to your widget.
You are getting this error because you are not storing your token anywhere or you are not invoking your storage token before using it. So first, please try to store your token and invoke it before using it.
LoginModel authUser = LoginModel.fromJson(userData); you are initializing value to method variable LoginModel authUser not a class variable authUser try removing LoginModel from before 'authUser'.
In Dashboard you should also check for null value, you can do that like this userTest?.authUser?.data?.customerToken ?? ''
i fetch data from server using flutter http request and load more data when user scroll to bottom of screen. i receive this error "Unhandled Exception: type 'List' is not a subtype of type 'Product'". Please help, i struggle all day without success.
model.dart file
class Product {
final int id;
final String accountName,
callNumber,
whatsappNumber,
businessLocation,
caption;
final List<Images> productPhoto;
Product({
this.id,
this.accountName,
this.callNumber,
this.whatsappNumber,
this.businessLocation,
this.caption,
this.productPhoto,
});
// this is static method
factory Product.fromJson(Map<String, dynamic> json) {
return Product(
id: json['id'],
accountName: json['account_name'],
callNumber: json['call_number'],
whatsappNumber:
json['whatsapp_number'] != null ? json['whatsapp_number'] : null,
businessLocation: json['business_location'],
caption: json['caption'],
productPhoto:
(json['post_photos'] as List).map((i) => Images.fromJson(i)).toList(),
);
}
}
class Images {
final String filename;
Images({this.filename});
factory Images.fromJson(Map<String, dynamic> json) {
return Images(
filename: json['filename'],
);
}
}
explore.dart file (i import models.dart to this file)
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:windowshoppi/models/global.dart';
import 'package:windowshoppi/models/product.dart';
import 'package:http/http.dart' as http;
class Explore extends StatefulWidget {
#override
_ExploreState createState() => _ExploreState();
}
class _ExploreState extends State<Explore> {
ScrollController _scrollController = ScrollController();
List<Product> data;
String nextUrl;
#override
void initState() {
// TODO: implement initState
super.initState();
this.fetchProduct(http.Client(), ALL_PRODUCT_URL);
_scrollController.addListener(() {
// print(_scrollController.position.pixels);
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
if (nextUrl != null) {
this.fetchProduct(http.Client(), nextUrl);
}
// print(nextUrl);
}
});
}
Future<List<Product>> fetchProduct(http.Client client, url) async {
final response = await client.get(url);
if (response.statusCode == 200) {
Map<String, dynamic> mapResponse = json.decode(response.body);
nextUrl = mapResponse['next'];
if (mapResponse["count"] != "") {
final products = mapResponse["results"].cast<Map<String, dynamic>>();
final listOfProducts = await products.map<Product>((json) {
return Product.fromJson(json);
}).toList();
// return listOfProducts;
setState(() {
data.add(listOfProducts);
});
} else {
return [];
}
} else {
throw Exception('failed to load data from internet');
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('http get'),
),
body: ListView.builder(
controller: _scrollController,
itemCount: data == null ? 0 : data.length,
itemBuilder: (BuildContext context, int index) {
return Card(
child: Container(
height: 200,
color: Colors.blue,
child: Text(data[index].caption),
),
);
},
),
);
}
}
Have a look at this part of the code.
final listOfProducts = await products.map<Product>((json) {
return Product.fromJson(json);
}).toList();
In the .map() method you are casting it to type < Product >. So judging by the error you have mentioned, "Unhandled Exception: type 'List' is not a subtype of type Product"
I think the json data being returned contains a List, instead of the product fields. I would highly recommend you to once check the json data being returned, and double-check if you are targeting the correct JSON tree nodes.
Let me know if this solved the issue.
this is my cloud firestore looks like:
Error Message: Unhandled Exception: Converting object to an encodable
object failed: Photography
used jsonSerialization for my database
import 'package:json_annotation/json_annotation.dart';
part 'Model.g.dart';
#JsonSerializable()
class Photography{
String couplePhoto;
String female;
String image_url;
String info;
String male;
AllImages all_images;
Photography();
factory Photography.fromJson(Map<String, dynamic> json) => _$PhotographyFromJson(json);
Map<String,dynamic> toJson() => _$PhotographyToJson(this);
}
#JsonSerializable()
class AllImages {
List<String> imageUrl = List<String>();
AllImages();
factory AllImages.fromJson(Map<String, dynamic> json) => _$AllImagesFromJson(json);
Map<String,dynamic> toJson() => _$AllImagesToJson(this);
}
By running flutter pub run build_runner build in the project root, I generated JSON serialization code for my Photography and AllImages whenever they are needed.
Model.g.dart
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'Model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Photography _$PhotographyFromJson(Map<String, dynamic> json) {
return Photography()
..couplePhoto = json['couplePhoto'] as String
..female = json['female'] as String
..image_url = json['image_url'] as String
..info = json['info'] as String
..male = json['male'] as String
..all_images = json['all_images'] == null
? null
: AllImages.fromJson(json['all_images'] as Map<String, dynamic>);
}
Map<String, dynamic> _$PhotographyToJson(Photography instance) =>
<String, dynamic>{
'couplePhoto': instance.couplePhoto,
'female': instance.female,
'image_url': instance.image_url,
'info': instance.info,
'male': instance.male,
'all_images': instance.all_images
};
AllImages _$AllImagesFromJson(Map<String, dynamic> json) {
return AllImages()
..imageUrl = (json['imageUrl'] as List)?.map((e) => e as String)?.toList();
}
Map<String, dynamic> _$AllImagesToJson(AllImages instance) =>
<String, dynamic>{'imageUrl': instance.imageUrl};
After that, I created the DB class,
How to use the model class?
class DB {
final db = Firestore.instance;
// Stream<QuerySnapshot> initStream() {
// return db.collection('photography').snapshots();
// }
getPhotography() async {
return db.collection('photography')
.document("0yUc5QBGHNNq6WK9CyyF")
.setData(jsonDecode(jsonEncode(Photography)));
}
}
DB db = DB();
my photography_bloc class
class PhotographyBloc extends BlocBase{
//PhotographyBloc(){
// db.initStream().listen((data) => inFirestore.add(data));
//}
PhotographyBloc(){
init();
}
Photography photography;
//final _firestoreController = StreamController<Photography>();
//Stream<Photography> get outFirestore => _firestoreController.stream;
//Sink<Photography> get inFirestore => _firestoreController.sink;
final _firestoreController = StreamController<Photography>();
Stream<Photography> get outFirestore => _firestoreController.stream;
Sink<Photography> get inFirestore => _firestoreController.sink;
void init() async{
photography = db.getPhotography();
inFirestore.add(photography);
}
#override
void dispose() {
_firestoreController.close();
}
}
my StreamBuilder Widget
How to get data using JSON serialization
child: StreamBuilder<Photography>(
stream: bloc.outFirestore,
initialData: null,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(
children: buildItem(snapshot.data, bloc));
// children: snapshot.data.documents
// .map<Widget>((doc) => buildItem(doc, bloc))
// .toList());
} else {
return SizedBox();
}
}),
builderItem() method,
buildItem(Photography doc, PhotographyBloc bloc) {
...
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: FadeInImage.assetNetwork(
placeholder: "assets/images/photography.jpg",
image: doc.couplePhoto,
// image: doc.data['couplePhoto'],
fit: BoxFit.fill,
),
),
According to the package source :
/// Writes to the document referred to by this [DocumentReference].
///
/// If the document does not yet exist, it will be created.
///
/// If [merge] is true, the provided data will be merged into an
/// existing document instead of overwriting.
Future<void> setData(Map<String, dynamic> data, {bool merge = false}) {
return Firestore.channel.invokeMethod<void>(
'DocumentReference#setData',
<String, dynamic>{
'app': firestore.app.name,
'path': path,
'data': data,
'options': <String, bool>{'merge': merge},
},
);
}
You must give a <String, dynamic> Map to setData(x) method.
So in your case you should maybe do it like this :
getPhotography() async {
return db.collection('photography')
.document("0yUc5QBGHNNq6WK9CyyF")
.setData(photography.toJson());
}